Konrad Kocik
Konrad Kocik

Reputation: 58

Using Python's __get__ descriptor for non-class attributes

Using __get__ descriptor I would like to achieve something like that:

class Wrapper:
    def __init__(self, wrapped_value):
        self._wrapped_value = wrapped_value

    def __get__(self, instance, owner):
        return self._wrapped_value

wrapper = Wrapper('foo')
assert type(wrapper) == type('foo')

It turned out, that __get__ descriptor gets called only if Wrapper instance is a class attribute of some other class, it does not get called when Wrapper instance is a standalone object (not bound to any class attribute).

Is there a way to make __get__ descriptor work in non-class attributes?

The main goal is to implement wrapper, which when used acts like value it wraps (I know it doesn't sound useful at first glance but there are some use cases in which this would be helpful). So maybe there is other way to achieve this without using __get__ descriptor?

Upvotes: 1

Views: 590

Answers (1)

snakecharmerb
snakecharmerb

Reputation: 55619

If you want your Wrapper class to control access to the wrapped classes attributes you can use the __getattr__ magic method.

Say we have a class Foo:

class Foo(object):

    def bar(self):
        return 'bar'

    def baz(self):
        return 'baz'

Let's say that we are interacting with some code that we do not control that requires the output of Foo.bar() to be upper case, and we don't want to explicitly call .upper() in our code.

We can create a wrapper class that intercepts calls to Foo.bar(), but transparently allows access to Foo's other methods (this is essentially the Adapter pattern).

class Wrapper(object):

    def __init__(self, wrapped):
        self._wrapped = wrapped

    def __getattr__(self, name):
        # If 'name' isn't an attribute of this class,
        # get it from the wrapped instance.
        return getattr(self._wrapped, name)

    def bar(self):
        # Intercept calls to the wrapped instance's bar() method.
        return self._wrapped.bar().upper()



>>> wrapper = Wrapper(Foo())
>>> print wrapper.baz()
baz
>>> print wrapper.bar()
BAR

This Wrapper class would not be reported as a Foo by type or isinstance checks, but otherwise it can be used in place of Foo as long as the calling code relies on duck-typing rather than (unpythonic) explicit type-checking.

Intercepting magic methods such as __str__ has to be done explicitly. This is because these methods are always looked up directly on an instance's class, so __getattr__ and __getattribute__ are bypassed.

So to override Foo.__str__ you would need to do:

class Foo(object):

    ...

    def __str__(self):
        return 'I am a Foo'


class Wrapper(object):

    ...

    def __str__(self):
        return str(self._wrapped)

>>> wrapper = Wrapper(Foo())
>>> print wrapper
I am a Foo

Upvotes: 2

Related Questions