Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make len() work with different methods on different instances of a class, without modifying the class?

Is there a way to make len() work with instance methods without modifying the class?

Example of my problem:

>>> class A(object):
...     pass
...
>>> a = A()
>>> a.__len__ = lambda: 2
>>> a.__len__()
2
>>> len(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: object of type 'A' has no len()

Note:

  • different instances of A will have different __len__ methods attached
  • I cannot change the class A
like image 520
ARF Avatar asked Apr 11 '16 19:04

ARF


2 Answers

No. Python always looks up special methods through the object's class. There are several good reasons for this, one being that repr(A) should use type(A).__repr__ instead of A.__repr__, which is intended to handle instances of A instead of the A class itself.

If you want different instances of A to compute their len differently, consider having __len__ delegate to another method:

class A(object):
    def __len__(self):
        return self._len()

a = A()
a._len = lambda: 2
like image 56
user2357112 supports Monica Avatar answered Sep 25 '22 02:09

user2357112 supports Monica


Special methods such as __len__ (double-underscore or "dunder" methods) must be defined on the class. They won't work if only defined on the instance.

It is possible to define non-dunder methods on an instance. However, you must convert your function to an instance method by adding a wrapper to it, which is how self gets passed in. (This would normally be done when accessing the method, as a method defined on the class is a descriptor that returns a wrapper.) This can be done as follows:

a.len = (lambda self: 2).__get__(a, type(a))

Combining these ideas, we can write a __len__() on the class that delegates to a len() that we can define on the instance:

class A(object):
     def __len__(self):
         return self.len()

a = A()
a.len = (lambda self: 2).__get__(a, type(a))

print(len(a))  # prints 2

You can actually simplify this in your case because you don't need self in order to return your constant 2. So you can just assign a.len = lambda: 2. However, if you need self, then you need to make the method wrapper.

like image 38
kindall Avatar answered Sep 22 '22 02:09

kindall