Is there a way by just calling the class's name I call a function?

Viewed 65

For example, I am making a class that defines a pygame rectangle.

class rect():
   def __init__(self,x,y,w,h):
       self.pos = pygame.Rect(x,y,w,h)
r = rect(10,10,10,10)
r.pos.move(10,10)

Is there any way in the code that I can get just the name of the variable that calls the class to call self.pos. So that I can remove the .pos

r = rect(10,10,10,10)
r.move(10,10)
1 Answers

That's basic composition / delegation. The canonical approach here is to make the delegatee private (in Python: prefix it's name with a single leading underscore - it's only a convention but it's a very strong one) and write public methods to delegate operations, ie:

# naming convention: class names should be CamelCase
class Rect():
   def __init__(self, x, y, w, h):
       # naming convention: the single leading underscore
       # denotes an implementation attribute or method
       self._pos = pygame.Rect(x, y, w, h)

   # explicit delegation  
   def move(self, x, y):
       self._pos.move(x, y)


r = Rect(10,10,10,10)
r.move(10,10)

Now if you have dozens of methods to delegate, Python also provides a hook for automatic delegation: the __getattr__(self, name) magic method:

class Rect():
   def __init__(self, x, y, w, h):
       self._pos = pygame.Rect(x, y, w, h)

   # implicit delegation:
   def __getattr__(self, name):
       try:
           return getattr(self._pos, name)
       except AttributeError:
           # here we hide the fact we're delegating to another object
           raise AttributeError(
               "'{}'.object has no attribute '{}'".format(
                   type(self).__name__, name
               ))


r = Rect(10,10,10,10)
r.move(10,10)

The downsides of this automagic delegation are that 1/ it can provide access to some delegatee attributes you don't want to expose, 2/ delegated methods are neither explicitely visible nor discoverable thru inspection and 3/ you get some additional overhead.

First issue can be solved by keeping a whitelist of delegatee attributes you want to allow access to. There's alas no simple solution for the second point, and no solution at all for the third, so it's still better to go for explicit delegation (at least for the important parts) and keep automagic delegation for things like generic proxy classes etc.

NB: if this is a reocurring pattern in your code, you can still set up some "semi-automagic" delegation system using either a custom descriptor or a custom metaclass or a class decorator, ie (custom descriptor example):

class delegate_to():
    def __init__(self, delegatee, attr=None):
        self._delegatee = delegatee
        self._attr = attr
        self._name = None
        self._owner = None

    def __set_name__(self, owner, name):
        self._owner = owner
        self._name = name
        if self._attr is None:
            self._attr = name

    def _raise(self):
        msg = "'{}' object has no attribute '{}'".format(
            self._owner.__name__,
            self._attr
            )
        raise AttributeError(msg)

    def __get__(self, obj=None, cls=None):
        if obj is None:
            return self
        delegatee = getattr(obj, self._delegatee)
        try:
            return  getattr(delegatee, self._attr)
        except AttributeError:
            self._raise(obj, cls)

    def __set__(self, value):
        raise AttributeError("Attribute is read-only")

    def __repr__(self):
        return "<Delegatee {} ({}) object for {}>".format(
            self._name, self._attr, self._owner.__name__
        )

# exemple use :
class Delegatee():
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def move_to(self, x, y):
        self.x = x
        self.y = y

    def move_by(self, x, y):
        self.x += x
        self.y += y

    @property
    def position(self):
        return self.x, self.y



class Composite():
    def __init__(self, x, y):
        self._delegatee = Delegatee(x, y)

    move_to = delegate_to("_delegatee")
    move_by = delegate_to("_delegatee")
    position = delegate_to("_delegatee")

And you can automate a bit further with a class decorator that will create the delegate_to descriptors:

class delegator():
    def __init__(self, delegatee, *names):
        if not names:
            raise ValueError("needs one or more name to delegate")
        self.delegatee = delegatee
        self.names = names

    def create_delegator(self, owner, name, attrname=None):
        if not attrname:
            attrname = name
        delegator =  delegate_to(self.delegatee, attrname)
        delegator.__set_name__(owner, name)
        return delegator

    def __call__(self, cls):
        for name in self.names:
            if isinstance(name, tuple):
                name, attr = name
            else:
                attr = name
            delegator = self.create_delegator(name, attr)
            setattr(cls, name, delegator)
        return cls


@delegator("_delegatee", "move_to", "move_by", ("pos", "position"))
class Composite2():
    def __init__(self, x, y):
        self._delegatee = Delegatee(x, y)

Now that's a lot of code and indirection levels for something that is basically very simple, so it only makes sense if you really have a lot of delegations to set up - from experience, the simplest the code, the easier it is to read, understand, test and debug (python zen: "simple is better than complex"), and "smart" solutions often become a PITA in the long term (been here, done that, now I know better).

Related