Reputation: 630
I'm trying to have a class act in every way like a tuple that's an attribute of the class, so len(instance) would be the same as len(instance.tup), instance[3] would return instance.tup[3], etc. Here's the class:
class mytup(object):
def __init__(self, a):
self.tup = tuple(a)
def __getattr__(self, nm):
f = types.MethodType(lambda self:getattr(self.tup, nm)(), self, type(self))
f.__func__.func_name = nm
setattr(self, nm, f)
return f
I can
mt = mytup(range(10))
But if I try to:
In [253]: len(mt)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-253-67688b907b8a> in <module>()
----> 1 len(mt)
TypeError: object of type 'mytup' has no len()
mt does in fact have a __len__ that I can call:
In [254]: mt.__len__
Out[254]: <bound method mytup.__len__ of <__main__.mytup object at 0x2e85150>>
In [255]: mt.__len__()
Out[255]: 10
(I even renamed it __len__). As near as I can tell, this should look just as if I did:
def __len__(self, *a):
return self.tup.__len__(*a)
But python won't let me len(mt) (or mt[2] or mt [1:5] for that matter).
Upvotes: 6
Views: 2907
Reputation: 123541
New-style classes look-up "special methods"—those that start and end with two underscore characters—on an instance's class not the instance involved, so when len()
is called it tries to call typeof(mt).__len__()
. So the proper way to do what you want would be to use one of the Abstract Base Classes for Containers in the collections
module (since Python 3.3)
import collections.abc
class MyTuple(collections.abc.Sequence):
def __init__(self, a):
self.tup = tuple(a)
def __len__(self):
return len(self.tup)
def __getitem__(self, index):
return self.tup[index]
mt = MyTuple(range(10))
print(len(mt)) # -> 10
print(mt[4]) # -> 4
Upvotes: 3
Reputation: 96286
The reason this isn't working as you have hoped is because doing:
setattr(self, nm, f)
Is not equivalent to
def __len__(self, *a):
return self.tup.__len__(*a)
In the latter case, your method is a property of the class because it is defined in class scope. It would be the equivlanet of setattr(cls, nm, f)
. If you check MyTup.__dict__
you will see it there. However, in the former case, __len__
is a property of the instance. So it will be in my_instance.__dict__
. len
checks the class for a __len__
method, and doesn't find one. Hence the error. Your __getattr__
is never actually called, and even if it were, it wouldn't allow you to use len
. You can use an_instanec.__len__
diretly, though.
Upvotes: 0
Reputation: 16224
len
does not use __getattr__
to get the __len__
function - it calls __len__
directly.
Calling x.__len__
is like calling getattr(x, '__len__')
- which will return the x.__len__
method object.
len
works behind the scene, so it can access this method directly, without invoking the __getattr__
helper.
__getattr__
to see what is printed when calling len
(hint: nothing). Upvotes: 0