Ruan
Ruan

Reputation: 229

How to fix 'RecursionError: maximum recursion depth exceeded' when calling object?

I'm trying to save instances of classes to strings in Python 3.7.

I'm trying this because I've started to make a text-based game that has a dictionary of co-ordinates and instances of classes for the world (I followed this tutorial: https://letstalkdata.com/2014/08/how-to-write-a-text-adventure-in-python/) and I want to add a save option. If you just pickle the world dictionary it said where in the RAM the rooms were, which wasn't very helpful. But then I read about repr() and exec() somewhere and I am now trying to use it to save and load instances of a test class. But when I try to print my newly created instance of the class it gives me a RecursionError: maximum recursion depth exceeded.


import traceback

def change_char(s, p, r):
    l = list(s)
    l[p] = r
    return "".join(l)

class Class:
    def __init__(self, i1, i2, *i3):
        (filename,line_number,function_name,text)=traceback.extract_stack()[-2]
        self.name = text[:text.find('=')].strip()
        self.i1 = i1
        self.i2 = i2
        self.i3 = []
        for iv in i3:
            self.i3.append(iv)

    def l(self, l):
        s = ''
        i = 1
        for e in l:
            if i < len(l):
                s = s + repr(e) + ", "
            else:
                s = s + repr(e)
            i += 1
        return s

    @property
    def print_vals(self):
        print('i1 : {1}\ni2 : {2}\ni3 : {3}'.format(self.i1, self.i2, self.l(self.i3)))

    def __repr__(self):
        return '{0} = Class({1}, {2}, {3})'.format(self.name, repr(self.i1), repr(self.i2), self.l(self.i3))

    @property
    def save(self):
        return repr(self)

def Classload(st):
    name = st[:st.find('=')].strip()
    exec('global '+name+'\n'+st)
    exec('global '+name+'\n'+name+'.name = '+name)

c = Class(1, "str", "Hello", 'world!')

print(repr(c))
i = c.save
i = change_char(i, 0, 'i')
print(i)
Classload(i)
print(c)
print(i)
print(repr(c))
print(repr(i))

I expect the output to be:

c = Class(1, 'str', 'Hello', 'world!')
i = Class(1, 'str', 'Hello', 'world!')
c = Class(1, 'str', 'Hello', 'world!')
i = Class(1, 'str', 'Hello', 'world!')
c = Class(1, 'str', 'Hello', 'world!')
i = Class(1, 'str', 'Hello', 'world!')

But I get:

c = Class(1, 'str', 'Hello', 'world!')
i = Class(1, 'str', 'Hello', 'world!')
c = Class(1, 'str', 'Hello', 'world!')
Traceback (most recent call last):
  File "C:\Users\HP\Desktop\test.py", line 107, in <module>
    print(i)
  File "C:\Users\HP\Desktop\test.py", line 63, in __repr__
    return '{0} = Class({1}, {2}, {3})'.format(self.name, repr(self.i1), repr(self.i2), self.l(self.i3))
  File "C:\Users\HP\Desktop\test.py", line 63, in __repr__
    return '{0} = Class({1}, {2}, {3})'.format(self.name, repr(self.i1), repr(self.i2), self.l(self.i3))
  File "C:\Users\HP\Desktop\test.py", line 63, in __repr__
    return '{0} = Class({1}, {2}, {3})'.format(self.name, repr(self.i1), repr(self.i2), self.l(self.i3))
  [Previous line repeated 245 more times]
RecursionError: maximum recursion depth exceeded

How do I fix this?

Upvotes: 0

Views: 5453

Answers (2)

Guy
Guy

Reputation: 647

With regard to your comments about pickle - in the docs (https://docs.python.org/3.7/library/pickle.html#comparison-with-marshal) it says:

The pickle module keeps track of the objects it has already serialized, so that later references to the same object won’t be serialized again.

Presumably why there are the memory references. It looks a lot easy to just do the following:

if __name__ == '__main__':
    import pickle

    initial = Class(1, 'hello', 'world', '!')

    dumped = pickle.dumps(initial)
    returned = pickle.loads(dumped)

    print(f'initial: {initial}')
    print(f'returned: {returned}')
    print(f'i1: {returned.i1}, i2: {returned.i2}, i3: {returned.i3}') 

# initial: Class(1, 'hello', 'world', '!')
# returned: Class(1, 'hello', 'world', '!')
# i1: 1, i2: hello, i3: ['world', '!']

Note, I had also removed the bits in your code for the class name though, which is why the printout is a touch different.

Upvotes: 0

syntonym
syntonym

Reputation: 7384

The name of your object is the object itself. If you print what you execute it looks something like this:

global i
i = Class(1, 'str', 'Hello', 'world!')
global i
i.name = i

When you call repr on i it will try to represent i.name as a string, but to convert i.name (which is i) to convert t a string it calls repr on it. repr will then try to represent i.name.name (which is i.name which is i) as a string, ... until you exceed the recursion depth.

In your classload you rather want something like

i.name = 'i'

On a more general note using repr and exec as a save system is not a good idea. It is very finicky which basically boils down to repr not being designed as machine readable (but human readable) and exec being almost never the right choice for anything. Instead you can use de/serializer (i.e. code that translates python objects to bytes and bytes to python objects) like pickle or json or protobuf or xml. I don't really understand your objection to pickle, because it is basically designed for your usecase.

Upvotes: 2

Related Questions