Search code examples
pythoninstance-methods

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

Solution

  • It is actually possible without modifying the class based on this answer by Alex Martelli:

    class A(object):
        def __init__(self, words):
            self.words = words
    
        def get_words(self):
            return self.words
    
    
    a = A("I am a")
    b = A("I am b")
    
    def make_meth(inst, _cls, meth, lm):
        inst.__class__ = type(_cls.__name__, (_cls,), {meth: lm})
    
    make_meth(a, A, "__len__", lambda self: 12)
    make_meth(b, A, "__len__", lambda self: 44)
    
    print(len(b))
    print(len(a))
    print(a.get_words())
    print(b.get_words())
    

    If we run the code:

    In [15]: a = A("I am a")    
    In [16]: b = A("I am b")    
    In [17]: make_meth(a, A, "__len__", lambda self: 12)
    In [18]: make_meth(b, A, "__len__", lambda self: 44) 
    In [19]: print(len(b))
    44    
    In [20]: print(len(a))
    12
    
    In [21]: print(a.get_words())
    I am a    
    In [22]: print(b.get_words())
    I an b
    

    As per the last part of the last part of the linked answer, you can add any methods on a per instance basis using inst.specialmethod once you have used inst.__class__ = type(... :

    In [34]: b.__class__.__str__ =  lambda self: "In b"
    
    In [35]: print(str(a))
    <__main__.A object at 0x7f37390ae128>
    
    In [36]: print(str(b))
    In b