0

Suppose I want to run the following code using Python 3.6.3:

class Foo:
    def bar(self):
        return 1

    def __len__(self):
        return 2

class FooWrapper:
    def __init__(self, foo):
        self.bar = foo.bar
        self.__len__ = foo.__len__



f = Foo()
print(f.bar())
print(f.__len__())
print(len(f))

w = FooWrapper(Foo())
print(w.bar())
print(w.__len__())
print(len(w))

Here's the output:

1
2
2
1
2
TypeError: object of type 'FooWrapper' has no len()

So __len__() works, but len() does not? What gives and how go I properly copy __len__ method from Foo to FooWrapper?

By the way, the following behavior is universal for all 'special' methods, not only __len__: for example, __iter__ or __getitem__ do not work either (unless called directly)

RX_DID_RX
  • 4,113
  • 4
  • 17
  • 27

1 Answers1

2

The reason this happens is because the special methods have to be on an object's class, not on the instance.

len will look for __len__ in the FooWrapper class. BTW, although this looks like it "works", you are actually adding foo.__len__, i.e. , the methoud already bound to the foo instance of Foo to your FooWrapper object. That might be the intent, but you have to be aware of this.

The easiest way for this to work is to make FooWrapper have itself a __len__ method that will call the wrapped instance's __len__:

class FooWrapper:
    def __init__(self, foo):
        self.foo = foo
        self.bar = foo.bar
        self.__len__ = foo.__len__
    def __len__(self):
         return len(self.foo)

Does that mean that for any and all special methods hace to explicitly exist in the wrapper class? Yes, it does - and it is one of the pains for creating proxies that behave just the same as the wrapped object.

That is because the special methods' checking for existence and calling is done directly in C, and not using Python's lenghty lookup mechanisms, as tat would be too inefficient.

It is possible to create a wrapper-class factory thing that would inspect the object and create a brand new wrapper class,with all meaningful special methods proxied, though - but I think that would be too advanced for what you have in mind right now.

You'd better just use explicit special methods, or explicit access to the wrapped object in the remainder of the code. (Like, when you will need to use __iter__ from a wrapped object, instead of doing just for x in wrapper, do for x in wrapper.wrapped )

jsbueno
  • 99,910
  • 10
  • 151
  • 209