python: unexplainable infinite recursion with __repr__ - python

Here's a piece of code, which goes into an infinite recursion loop, which consists only of __repr__ function, seemingly calling itself. But I really can't see, how it calls itself. Moreover, I can't even understand, how it was called:
class MyList(list): #this is storage for MyDict objects
def __init__(self):
super(MyList, self).__init__()
class MyDict(dict):
def __init__(self, mylist):
self.mylist = mylist #mydict remembers mylist, to which it belongs
def __hash__(self):
return id(self)
def __eq__(self, other):
return self is other
def __repr__(self):
return str(self.mylist.index(self)) #!!!this is the crazy repr, going into recursion
def __str__(self):
return str(self.__repr__())
mylist = MyList()
mydict = MyDict(mylist)
mydict.update({1:2})
print str(mylist.index(mydict)) #here we die :(
Execution of this code results in:
Traceback (most recent call last):
File "test_analogue.py", line 20, in <module>
print str(mylist.index(mydict))
File "test_analogue.py", line 13, in __repr__
return str(self.mylist.index(self))
File "test_analogue.py", line 13, in __repr__
...
... (repetition of last 2 lines for ~666 times)
...
File "test_analogue.py", line 13, in __repr__
return str(self.mylist.index(self))
RuntimeError: maximum recursion depth exceeded while calling a Python object
Do you understand, how str(mylist.index(mydict)) managed to call __repr__? I'm completely puzzled. Thanks!

>> mylist.index('foo')
ValueError: 'foo' is not in list
You never actually added mydict to mylist, so the index method tries to raise this error. The error contains the repr of the dict. The repr of the dict, of course, tries to look up its index in the list that it isn't in, and this raises an exception, whose error message is calculated using the repr of the dict, which of course, tries to look up its index in the list that it isn't in, and...

Related

Calling print(self) under __str__ throws a RecursionError

I have defined a class called spam:
class spam():
def __str__(self):
print(self)
a = spam()
print(a)
The print statement in the end gives me the following error:
Traceback (most recent call last):
File "<pyshell#73>", line 1, in <module>
print(a)
File "<pyshell#51>", line 3, in __str__
print(self)
File "<pyshell#51>", line 3, in __str__
print(self)
File "<pyshell#51>", line 3, in __str__
print(self)
#same lines repeated several times
RecursionError: maximum recursion depth exceeded
What is going on here? What happens when I say print(self) under str(self)? What is causing the recursion?
print calls str on the non-string object to be able to print it, which calls your __str__ member method.
Here is your recursion.
You define a __str__ method when you are able to convert your object to an "equivalent" string. If not, just leave the default (which prints the object type & address)
Note that __str__ should return something, not print. If you have some representative attribute, you could use it to return something interesting.
class spam():
def __init__(self,value):
self.__value = value
def __str__(self):
return "object '{}' with value {}".format(self.__class__.__name__, self.__value)
a = spam(10)
print(a)
prints:
object 'spam' with value 10

Getting a class to act as a tuple

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).
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
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.
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.
Try to add a print statement in your __getattr__ to see what is printed when calling len (hint: nothing).

override __getattr__ for methods and not variables

i want the next code to work
class A(object):
def __getattr__(self, item):
print item
return self.item
def x(self):
print 4
a = A()
a.x()
and the output will ber
x
4
i know its not working becuase x is like a static variable and not an instance variable.
I saw this __getattr__ for static/class variables in python and it doesn't seem to work in my case
how can it be done?
thx
There are a couple of obvious problems with your code:
class A(object):
def __getattr__(self, item): # 1
print item
return self.item # 2
def x(self): # 1 again
print 4
__getattr__ will only be invoked if item cannot be found the normal way. For item == 'x', therefore, it is never invoked.
Which is probably just as well, since self.item looks for the attribute item, not the attribute corresponding to whatever is assigned to item. This doesn't exist, so would invoke __getattr__. If you try A().y() you'll get RuntimeError: maximum recursion depth exceeded while calling a Python object.
Instead, I think you want to use __getattribute__, which is always invoked. You need to be careful not to get the same runtime error, though; here I avoid it by calling the superclass implementation of __getattribute__, the naïve way of calling getattr(self, item) would fail:
class A(object):
def __getattribute__(self, item):
print item
return super(A, self).__getattribute__(item)
def x(self):
print 4
Which gives:
>>> A().x()
x
4
>>> A().y()
y
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in __getattribute__
AttributeError: 'A' object has no attribute 'y'
Note that both __getattr__ and __getattribute__ apply equally to attributes and methods (which are, more or less, just callable attributes).

How to have a list in python which returns a default item on an invalid index or no index at all

I am trying to create a custom python shell where I initialize some objects on startup. Those objects could be individual items or list. the user of the shell could then call methods from those objects to perform some functions:
For Ex, this code is run on the start of the python shell:
# Init the objects on startup of shell
if NeedObjList:
a = [ObjClass(x,y,z) ObjClass(p,q,r)]
else:
a = ObjClass(x, y, z)
Now in the shell, user will have to know that the initialized variable 'a' is a list or a class object. There can be a message displayed on startup of the python shell. But in case it is not seen, user might try to access the variable as a list when it was an object and vice versa.
I was wondering if the list itself can be created as a smart list. i.e. if the user supplies an index to the list, the object at the index is returned. Otherwise in case of an invalid index or no index being given, the list returns the 0th element (or any fixed element for that matter)
An example of the use case:
class example:
def __init__(self):
self.a=1
self.b=2
def cl_print(self):
print "works : {0} {1}".format(self.a, self.b)
if NeedObjList:
a = [example() example()]
else:
a = example()
Now in the shell, this file is imported on start (using "python -i"):
Case 1: Object list was created
>>>a[0].cl_print()
works : 1 2
>>> a.cl_print() # will fail
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute 'cl_print'
Case 2: Object was created
>>> a.cl_print()
works : 1 2
>>> a[0].cl_print()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: hello instance has no attribute '__getitem__'
I wanted to try and see if I could make things work for user without them having to do a try-except.
Making __getitem__() return the instance:
class Example(object):
def __init__(self):
self.a=1
self.b=2
def cl_print(self):
print("works : {0} {1}".format(self.a, self.b))
def __getitem__(self, item):
return self
and delegating attribute lookup of not found attributes to
the first list member with __getattr__() would work:
class MyList(list):
def __getattr__(self, attr):
return getattr(self[0], attr)
With a list:
>>> a = MyList([Example(), Example()])
>>> a.cl_print()
works : 1 2
>>> a[0].cl_print()
works : 1 2
With an instance:
>>> a = Example()
>>> a.cl_print()
works : 1 2
>>> a[0].cl_print()
works : 1 2
You can implement __iter__, __len__, and __getitem__ on your ObjClass to make it act like a list of one item:
class ObjClass(object):
def __iter__(self):
yield self
def __len__(self):
return 1
def __getitem__(self, i):
if i == 0: return self
raise IndexError("list index out of range")
Use try ... except:
tokens=['aaaa', 'bbb', 'ccc', 'ddd', 'eee']
try:
a = tokens[N]
except IndexError:
a = tokens[0]
If N-th element exists in list, then it will be returned, otherwise 0 element (or some other) will be returned.

iterator on custom classes in python throws error in python [duplicate]

This question already has answers here:
How to build a basic iterator?
(10 answers)
Closed 9 years ago.
I am a wring a custom class called queue that uses iterator. I have an Iterator class in a seperate file called iterator.py. I get the error below when I try to iterate using for loop.
from iterator import Iterator
class Abstractstruc(object):
def __init__(self):
assert False
def __str__(self):
return "<%s: %s>" %(self.__class__.__name__,self.container)
class Queue(Abstractstruc,Iterator):
def __init__(self, objecttype=object):
self.container=[]
self.size=0
def add(self, data):
self.container.append(data)
def remove(self):
self.container.pop(0)
def __getitem__(self,index):
return self.container[index]
def __iter__(self):
return Iterator(self.container)
if __name__=='__main__':
q=Queue(int)
q.add(5)
q.add(4)
q.add(6)
for i in q:
print i
iterator.py
class Iterator(object):
def __init__(self, wrapped):
self.wrapped = wrapped
self.offset = 0
def __next__(self):
if self.offset>=len(self.wrapped):
raise StopIteration
else:
item = self.wrapped[self.offset]
self.offset+=1
return item
I get this error message
<Queue: [5, 4, 6]>
<Queue: [4, 6]>
4
Traceback (most recent call last):
File "queue.py", line 78, in <module>
for i in q:
TypeError: iter() returned non-iterator of type 'Iterator'
I do not understand why it is not returning an iterator. what fixes needed here?
Iterators must themselves implement __iter__. They can just return self. From docs, note that custom iterator objects must support __iter__ to support for and in statements. Also, as #Robᵩ noted, since you are using Python 2 instead of 3, you need to implement next(), not __next__().
That's because next() method should not be magic, you don't need double underscores. As mentioned before, Python 3 is different.
def next(self):

Categories