als9xd
als9xd

Reputation: 854

Hide/Encapsulate all attributes from an inherited class while keeping functionality

Is there a way to hide or nest the Cat attributes within Animal while still being able to run the jump function? Specifically I would like to the output of vars(test) to only be the age. I'm sure I could hard code which specific attributes to not output by defining a custom __str__ but I will have a lot of attributes for Cat/Animal and I don't want to manually add an exception for each individual attribute. I also won't have access to the Cat class.

def Jump():
    print('Jumped!')

class Cat:
    def __init__(self):
        self.feet = 4
        self.jump = Jump

class Animal(Cat):
    def __init__(self):
        Cat.__init__(self)
        self.age = 3

test = Animal()
test.jump()
print(vars(test))

Output:

Jumped {'feet': 4, 'jump': , 'age': 3}

This is just code to illustrate what I'm trying to do. In reality Cat represents a published python module and Animal represents my custom one.

Upvotes: 0

Views: 45

Answers (1)

abarnert
abarnert

Reputation: 365667

If you change your relationship so that Animal no longer is-a Cat, but instead has-a Cat, then you're right, animal.jump() will no longer work.

There are many ways around this. Given how odd this design already is, in so many different ways, I have no idea which is most appropriate, so I'll just list a whole bunch of them.

The first group are ways to explicitly delegate just jump.

The "normal" way:

class Animal:
    def __init__(self):
        self.cat = Cat()
    def jump(self):
        return self.cat.jump()

Copy the per-instance function:

class Animal:
    def __init__(self):
        self.cat = Cat()
        self.jump = self.cat.jump

Delegate to the per-instance function:

class Animal:
    def __init__(self):
        self.cat = Cat()
        self.jump = lambda: self.cat.jump()

Per-instance bound method:

class Animal:
    def __init__(self):
        self.cat = Cat()
        self.jump = (lambda self: self.cat.jump()).__get__(self)

Dynamic lookup:

class Animal:
    def __init__(self):
        self.cat = Cat()
    def __getattr__(self, name):
        if name == 'jump':
            return getattr(self.cat, name)
        raise AttributeError

Dynamic bound method generation:

class Animal:
    def __init__(self):
        self.cat = Cat()
    def __getattr__(self, name):
        if name == 'jump':
            return (lambda self: getattr(self.cat, name)()).__get__(self)
        raise AttributeError

Of course all of these only delegate jump specifically. What if you wanted to delegate to all Cat functions, methods, and maybe other attributes, without necessarily knowing what they are in advance? Well, it should be obvious how to adapt most of these, so I'll just show two.

Dynamic lookup:

class Animal:
    def __init__(self):
        self.cat = Cat()
    def __getattr__(self, name):
        return getattr(self.cat, name)

Semi-static inspection that does complicated reflection on the different possible kinds of things we might want to delegate:

class Animal:
    def __init__(self):
        self.cat = cat
        for name, value in inspect.getmembers(self.cat):
            if name.startswith('_'): continue
            if inspect.ismethod(value):
                value = (lambda self: value()).__get__(self)
            elif callable(value):
                value = lambda: value()
            else:
                value = copy.copy(value)
            setattr(self, name, value)

Upvotes: 1

Related Questions