This may be highly abnormal, but I feel like it may be a useful thing. I have a range of classes that do different jobs in my package. I want to keep them seperate to keep the logic modular, and allow advanced users to use the classess directly, but I also want users to have a main convenience class that gives them quick access to the methods defined in these other classes. So to provide an example, currently this works...
class Tail:
def wag_tail(self):
print('Wag Wag')
class Dog:
def __init__(self):
self.tail = Tail()
my_dog = Dog()
my_dog.tail.wag_tail()
>> Wag Wag
But... is it possible to adjust my Dog
class so that this also works?
my_dog.wag_tail()
>> Wag Wag
Editing for clarity.
I want to achieve the above, but automatically, without necessarily having to define a new method in Dog
e.g. you could manually ensure access via def wag_tail(self): self.tail.wag_tail()
, but what if I wanted to avoid writing a convenience access method every time I add a method to my Tail
class. Is there a way to set things up such that Tail
methods are always accessible from Dog
?
CodePudding user response:
I have done something similar. You can pass in the owning class to the __init__
of the method class and have it append it's method back to the owning class.
Here any class that inherits from PassThrough
will run through it's own attributes and append any public methods to a parent class. You just have to pass the self
of the owning class.
class PassThrough:
def __init__(self, parent=None):
if not parent:
return
for method_name in dir(self):
if not method_name.startswith('_'):
method = getattr(self, method_name)
setattr(parent, method_name, method)
class Tail(PassThrough):
def wag_tail(self):
print('Wag Wag')
class Dog:
def __init__(self):
self.tail = Tail(self)
dog = Dog()
dog.wag_tail()
# prints: Wag Wag
You can also pass methods upward through multiple layers by using super()
. Here we create a Tongue
class, and then a Head
class that has a tongue. Then add a head to the Dog
class.
class Tongue(PassThrough):
def lick(self):
print('Lick Lick')
class Head(PassThrough):
def __init__(self, parent):
self.tongue = Tongue(self)
# this passes the appended method from Tongue to the parent.
super().__init__(parent)
def shake_head(self):
print('Fwop fwop')
class Dog:
def __init__(self):
self.tail = Tail(self)
self.head = Head(self)
dog = Dog()
dog.lick()
dog.shake_head()
CodePudding user response:
This is what the __getattr__
special method is made for:
class Tail:
def wag_tail(self):
print('Wag Wag')
class Dog:
def __init__(self):
self.tail = Tail()
# Only new code is this two-line method definition
def __getattr__(self, name):
return getattr(self.tail, name)
__getattr__
is called when an attribute (including method) is looked up on an instance and the attribute is not found; when that happens, the __getattr__
is invoked with an argument of the name being looked for. The getattr
built-in function can then be used to look it up on contained object(s).
If you have many such contained classes, you'll need to define some absolute ordering for checking, and check them one-by-one, so if you needed to check for behaviors of self.tongue
and self.paw
for instance, in that order, you might implement it as:
def __getattr__(self, name):
for internalname in ('tail', 'tongue', 'paw'):
internalattr = getattr(self, internalname)
try:
return getattr(internalattr, name)
except AttributeError:
continue # Try the next
raise AttributeError(name)
One caveat: When looking up special methods (those that begin and end with __
, usually used to implement features that Python uses implicitly via syntax or built-in functions, e.g. __len__
for len(x)
and __add__
for x y
), Python typically bypasses instance lookup, along with the special attribute access interception methods (__getattr__
and __getattribute__
), so you can't use this to handle operator overloading and the like. In that case, you'll have to hand-write (or programmatically generate code for and eval
) the proxying methods individually.