Reputation: 7694
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:
A
will have different __len__
methods attachedA
Upvotes: 19
Views: 2247
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
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
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
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
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