Joe Todd
Joe Todd

Reputation: 897

'Inherit' @property from another python class

Suppose I have a two classes which cannot be linked via inheritance, but instances of which can be linked:

class model:

    def __init__(self):
        self._alpha = [0,1,2,3]

    @property
    def alpha(self):
        return self._alpha

    @alpha.setter
    def alpha(self, inList):
        self._alpha = inList


class solver:

    def __init__(self, inModel):
        self.model = inModel

Is there a way for me to define solver.alpha to be model.alpha without having to write out a bunch more boilerplate stuff:

class solver:

    def __init__(self, inModel):
        self.model = inModel

    @property
    def alpha(self):
        return self.model.alpha

    @alpha.setter
    def alpha(self, inList):
        self.modle.alpha = inList

Upvotes: 1

Views: 60

Answers (2)

Roy Cohen
Roy Cohen

Reputation: 1570

Another sulotion is to "redirect" the attribute access from solver to model by overriding __getattr__:

class solver:
    def __init__(self, inModel):
        self.model = inModel
    
    def __getattr__(self, name):
        # note that this only gets called if the normal attribute access failed
        # so normal instance variables will still work.
        return getattr(self.model, name)

Usage:

m = model()
s = solver(m)
print(s.alpha)  # [0, 1, 2, 3]

This only works when getting attributes, in order to expand it to including setting (and also deleting) you need to override __setattr__ (and __delattr__) but those don't have the nice feature of only getting called when the normal attribute access failes so you need to check for that:

def __setattr__(self, name, value):
    try:
        object.__setattr__(self, name, value)  # try normal attribute access
    except AttributeError:
        setattr(self.model, name, value)

With this sulotion you don't need to list every attribute from model you want to have on solver, but you can't controll it either. Another limitation is that this will only work for one object, e.g. you can't have two models where some of the attributes are from one model and some attributes are from the other model.


Edit:
You might want to reflect the changes in the object's attribute access by overriding __dir__. You'd want to merge the default dir with self.model's dir. I did that by turning them into sets and using the builtin set union

def __dir__(self):
    return set(object.__dir__(self)) | set(dir(self.model))

Upvotes: 1

Roy Cohen
Roy Cohen

Reputation: 1570

You can write a descriptor class to automate the process:

class PathDescriptor:
    def __init__(self, *paths):
        self.paths = paths
    
    def __get__(self, obj, objtype=None):
        for path in self.paths:
            obj = getattr(obj, path)
        return obj
    
    def __set__(self, obj, value):
        for path in self.paths[:-1]:
            obj = getattr(obj, path)
        setattr(obj, self.paths[-1], value)

Example:

class solver:
    def __init__(self, inModel):
        self.model = inModel

    alpha = PathDescriptor('model', 'alpha')

m = model()
s = solver(m)
print(s.alpha)  # [0, 1, 2, 3]

Upvotes: 1

Related Questions