ARF
ARF

Reputation: 7694

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:

Upvotes: 19

Views: 2247

Answers (5)

Mark VY
Mark VY

Reputation: 1673

You did not specify whether you're using Python 2.x or 3.x, and in this case it depends! In 3.x, your question has already been answered by other people. In 2.x, your question actually has an easy answer: stop deriving from object! (Of course, if you follow this approach, you can't upgrade to 3.x until you find another way to do this, so don't do what I'm suggesting unless you have no plans to upgrade any time soon.)

Type "copyright", "credits" or "license()" for more information.
>>> class A:pass

>>> a=A()
>>> a.__len__ = lambda: 2
>>> len(a)
2
>>> 

Enjoy :)

Upvotes: 1

Padraic Cunningham
Padraic Cunningham

Reputation: 180482

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

Upvotes: 6

kindall
kindall

Reputation: 184345

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.

Upvotes: 10

user2357112
user2357112

Reputation: 281673

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

Upvotes: 27

Daniel
Daniel

Reputation: 42778

You have to define special methods at definition time of the class:

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

a = A()
a.get_len = lambda: 2
print len(a)

Upvotes: 5

Related Questions