How to fix infinite loop spawning from __str__ method? - python

File "C:\Users\kevin\Documents\Programs\ParLumen\trait.py", line 76, in __str__
ret_str = super().__str__()
File "C:\Users\kevin\Documents\Programs\ParLumen\trait.py", line 39, in __str__
ret_str += f'{self.name}\n'
RecursionError: maximum recursion depth exceeded
There are many, many more lines of this, however it's just the same two lines repeating, and then the final, different line is, "RecursionError: maximum recursion depth exceeded"
I'm not really sure why this is occurring.
Here is the code causing the issue:
from abc import ABC, abstractmethod
from enum import Enum, unique
from parlumen.game import *
# Base Trait class
class Trait(ABC):
def __init__(self, name, short_desc="No Desc", long_desc=None):
self.name = name
# self.value = value # may restrict this with req. 'avail_values()' func
"""
if short_desc is not "No Desc":
self.short_desc = short_desc
"""
self.short_desc = short_desc
if long_desc is None:
self.long_desc = self.short_desc
else:
self.long_desc = long_desc
#abstractmethod
def __str__(self):
ret_str = ""
ret_str += f'{self.name}\n'
ret_str += f'{self.long_desc}'
return ret_str
# Area of Effect Definition
#unique
class Focus(Enum):
NoFocus = 'None'
Origin = 'Origin'
Reversed = 'Reversed'
class AreaOfEffect(Trait):
def __init__(self, area, focus=None):
super().__init__(self, "Area of Effect")
self.area = area
if focus is None :
self.focus = Focus.NoFocus
else:
self.focus = focus
self.long_desc = f"Gives an attack or spell an area of effect of self.area"
def __str__(self):
ret_str = super().__str__()
ret_str += f"\nArea of Effect: {self.area}, Focus: {self.focus}"
return ret_str
aoe = AreaOfEffect(3, Focus.Origin)
print(aoe)
More specifically, the issue seems to pertain my __str__ method for Trait seems to not like having self.name or self.long_desc -- that is when the issue occurs.
This is sort of a mess in general, but I'm really trying to figure this out before continuing on. Strange that it only happens for Trait and not the subclass AreaOfEffect's __str__

You need to change:
super().__init__(self, "Area of Effect")
To:
super().__init__("Area of Effect")
you are setting name to an object instance, which then calls str recursively when trying to format name.

Related

Can't loop through dict_keys in #property method in python 3?

So I have the following code:
#property
def mod_list(self) -> List[Modifier]:
mods = []
print(len(self.statuses)) #Prints 0??? Update method prints the actual number when called??? Also means it *is* getting called properly when it's getting accessed
for status in self.statuses: # I've tried calling the keys() method on the dict but that doesn't work either
print("hello") #Doesn't print, indicating that it isn't looping
mods.extend(status.mods) # Note: statuses dict uses StatusEffect objects as keys, with values being the number of turns left before that status is removed; StatusEffects all possess a 'mods' property that is initialized to '[]' and can only be made up of modifiers
return mods
I don't understand why it can't access the keys of the dict? Even if I remove the decorator and call it instead of accessing it?
Especially when this method works properly?
def update(self):
deletion = []
print(len(self.statuses)) #Prints actual number of keys????
for name in self.statuses.keys():
print(name.name, self.statuses[name]) #Prints normally whenever update is called???
if hasattr(name, "turn_effect"):
name.turn_effect(self.entity)
self.statuses[name] -= 1
if self.statuses[name] < 1:
deletion.append(name)
...
for status in deletion:
del self.statuses[status]
Why isn't it working properly? And how do I fix it?
Edit: I managed to recreate the issue below, I think it might have to do with 'deepcopy' in the spawn method since I couldn't recreate the issue from scratch until I implemented and used the spawn method.
from __future__ import annotations
from typing import Dict, List
from copy import copy, deepcopy
class Entity:
def __init__(self, name:str, **kwargs:Component):
self.name = name
self.components:Dict[str, Component] = {}
for name, component in kwargs.items():
self.add_component(name, component)
def add_component(self, name:str, component:Component):
self.components[name] = component
component.entity = self
def update(self):
for comp in self.components.values():
comp.update()
def spawn(self):
return deepcopy(self)
class Component:
__entity: Entity
#property
def entity(self) -> Entity:
return self.__entity
#entity.setter
def entity(self, entity:Entity):
if hasattr(self, "__entity") and self.__entity is not None:
self.entity.remove_component(self)
self.__entity = entity
def update(self):
"""Placeholder method for component update methods"""
class StatusList(Component):
entity: Entity
def __init__(self) -> None:
self.statuses:Dict[StatusEffect, int] = {}
def add_status(self, status:StatusEffect, turns:int=1):
self.statuses[status] = turns
def update(self):
deletion = []
print(len(self.statuses.keys()))
for name in self.statuses.keys():
print(name.name, self.statuses[name])
if hasattr(name, "turn_effect"):
name.turn_effect(self.entity)
self.statuses[name] -= 1
if self.statuses[name] < 1:
deletion.append(name)
for status in deletion:
del self.statuses[status]
#property
def mod_list(self) -> List[Modifier]:
mods = []
print(len(self.statuses))
for status in self.statuses:
print("hello")
mods.extend(status.mods)
return mods
class StatusEffect:
name:str
turn_effect: function
mods:List[Modifier] = []
def apply(self, entity:Entity, turns:int=1):
if "status_list" in entity.components.keys():
entity.components["status_list"].add_status(self.copy(), turns)
def copy(self): #I specifically defined this method in the original code in case I need to modify it in the future
return copy(self)
class StatList(Component):
entity: Entity
stat_record: List[Stat] = []
def __init__(self, **stats:Stat) -> None:
for name, stat in stats.items():
stat.stat_list = self
stat.name = name
self.stat_record.append(stat)
def get_stat(self, name:str) -> Optional[Stat]:
for stat in self.stat_record:
if name == stat.name:
return stat
def get_stat_name(self, stat:Stat) -> Optional[str]:
if stat in record:
return stat.name
class Stat:
name:str
base_value:int
def __init__(self, base:int=0):
self.base_value = base
#property
def entity(self) -> Entity:
return self.stat_list.entity
#property
def current_value(self) -> int:
value = self.base_value
for mod in self.get_modifiers():
value += mod.value
return int(value)
def get_modifiers(self):
for component in self.entity.components.values():
if hasattr(component, "mod_list"):
for mod in component.mod_list:
if mod.stat == self.name:
yield mod
class Modifier:
stat: str
value: Union[int, float]
def __init__(self, stat:str, value:Union[int, float]):
self.stat = stat
self.value = value
rage = StatusEffect()
rage.name = "Rage"
rage.turn_effect = lambda entity : print(f"{entity.name} is enraged")
rage.mods = [
Modifier("atk", 5)
]
player = Entity(
name="Player",
stat_list=StatList(atk=Stat(5)),
status_list=StatusList()
).spawn()
rage.apply(player, 10)
while True:
player.update()
player.components["stat_list"].get_stat("atk").current_value
input()
Unfortunately, using copy() in the spawn method would result in entities created that way sharing status effects, stats, etc., which really defeats the purpose of spawning new entities
Edit 2: Modified spawn method to use copy and to copy all components, have to add guard clauses now but it works.

Overloading addition function and the result creates a new type of class

I need to overload the addition function so that it takes in the first point and the end point as the left and right side of the equation and outputs the equation. This is what my code looks right now. I'm not sure how to involve the line class?
import math
class Point:
'''Class that creates points. Attributes: Eastings and Northings'''
def __init__(self,x,y):
self.eastings = x
self.northings = y
def getCoords(self):
self.coords = (self.eastings,self.northings)
return self.coords
def setCoords(self,other_x,other_y):
self.eastings = float(other_x)
self.northings = float(other_y)
def __str__(self):
return f"{self.eastings},{self.northings}"
def __add__(self,new_point):
pass
#creates a line (new class)
class Line(Point):
'''Class that creates line object based on two points'''
def __init__(self,start,end):
self.start = start #Type:Point (x1,y1)
self.end = end #Type:Point (x2,y2)
self.latitude = abs(self.end.eastings - self.start.eastings)
self.departure = abs(self.end.northings - self.start.northings)
self.distance = math.sqrt((self.latitude)**2 + (self.departure)**2)
self.azimuth = math.degrees(math.atan2(self.departure,self.latitude))
def __getitem__(self,key):
if key == 0:
ans = self.start
elif key == 1:
ans = self.end
else:
print("invalid index")
return ans
#test code
a = Point(0,0)
b = Point(1,1)
c = Point(1,0.5)
line1 = a+b
print((type(line1))
The test code is supposed to print out the type as class line.
There's nothing that says the __add__() method has to return a the same type as the instance — which means you could this:
class Point:
...
def __add__(self, other):
if isinstance(other, Point):
return Line(self, other) # Line from this Point to the other.
else:
raise TypeError(f"Can't add a non-Point to a Point}")
Be careful doing this however, because the Line class will inherit the method (so you probably need to modify its version of it).
Add a self.getCoords() call to your Point.__init__() method.
Add return Line(self, new_point) to your Point.__add__() method.
Testing:
a = Point(0,0)
b = Point(1,1)
c = Point(1,0.5)
line1 = a+b
print(type(line1)) # I have removed a round bracket on the left
Output: <class '__main__.Line'>
In case you want to run a block of code, where a function/method has not one line in it, you have to add a pass to it. Otherwise you will get an error, because the structure requires it. Or you comment the function declaration out.

AttributeError: 'str' object has no attribute 'get_grade'

hello was following alone in a tutorial and the code that he has is the exact same that i have here but mine doesn't seem to work. when he ran the code his worked completely fine and i am running into errors. code it be that i need to add the parent to the subject such as subject(person)? or is something just wrong. The number get_average function should just return the number but it is having problems with that. appreciate the help
class Person():
def __init__(self, first, last, grade):
self.first = first
self.last = last
self.grade = grade
def get_grade(self):
return self.grade
class Subject():
def __init__(self, subject, number_students):
self.subject = subject
self.number_students = number_students
self.students = []
def add(self, name):
if len(self.students) < self.number_students:
self.students.append(name)
return True
return False
def average(self):
number = 0
for i in self.students:
number += i.get_grade()
return number
p1 = Person("dustin", "white", 83)
subs = Subject("science", 10)
subs.add(p1.first)
print(subs.students)
print(subs.average())
The error arises because you run subs.add(p1.first), which is a type str. p1.first does not have the method get_grade. What you want to run is: subs.add(p1) (the object which will have get_grade). Also, you can remove the redundant parentheses when defining the classes. You can write class Subject(): as class Subject:.
You can then change you add code to:
def add(self, student):
if len(self.students) < self.number_students:
self.students.append(student.first) # changed here
return True
return False

RecursionError: maximum recursion depth exceeded while calling a Python object(Algorithmic Change Required)

I am learning about Class Inheritance and overriding methods in python. To implement my learning, I wrote this code to let me better understand how Inheritance and Overriding works. But as I ran the code, I faced this error
"RecursionError: maximum recursion depth exceeded while calling a Python object"
I have tried to increase the recursion limit to 10000, but doing so, Python interpreter stopped working in my local machine. Can anyone help me with how I can overcome the error to have my expected output?
As I am new to the community, I may lack the appropriate presentation of the problem. Feel free to ask for more detailed information about the problem.
# Increasing Recursion Limit
import sys
sys.setrecursionlimit(10000)
import random
# Parent Class
class Unique_id_creator():
def __init__(self, birthdate, birthmonth, birthyear):
self.date = birthdate
self.month = birthmonth
self.year = birthyear
def random_digits(self):
last_two_digits = random.randrange(10, 99)
return self.random_digits()
def unique_id(self):
id = int(self.date + self.month + self.year + self.random_digits())
return self.unique_id()
# Child Class
class Unique_id_distributer(Unique_id_creator):
def __init__(self, name, birthdate, birthmonth, birthyear):
Unique_id_creator.__init__(self, birthdate, birthmonth, birthyear)
self.name = name
def unique_id(self):
Unique_id_creator.unique_id(self)
return "Dear "+ str(self.name) + ", Your Unique Id is: "+ str(self.unique_id())
citizen1 = Unique_id_distributer("hasan", "01", "11", "2000")
print(citizen1.unique_id())
# Output Window
RecursionError: maximum recursion depth exceeded while calling a Python object
This line return self.random_digits() and this line return self.unique_id() are infinite recursive loops.
I assume what you intended to return was last_two_digits and id.
When you return a function call, that function has to execute to return the result of its call. Since you are calling the function you are executing it continues calling itself forever.
Corrected code
# Increasing Recursion Limit
import sys
sys.setrecursionlimit(10000)
import random
# Parent Class
class Unique_id_creator:
def __init__(self, birthdate, birthmonth, birthyear):
self.date = birthdate
self.month = birthmonth
self.year = birthyear
def random_digits(self):
last_two_digits = random.randrange(10, 99)
# convert to a string since you are concatenating the result
return str(last_two_digits)
def unique_id(self):
id = int(self.date + self.month + self.year + self.random_digits())
return id
# Child Class
class Unique_id_distributer(Unique_id_creator):
def __init__(self, name, birthdate, birthmonth, birthyear):
Unique_id_creator.__init__(self, birthdate, birthmonth, birthyear)
self.name = name
def unique_id(self):
id = Unique_id_creator.unique_id(self)
return "Dear "+ str(self.name) + ", Your Unique Id is: "+ str(id)
citizen1 = Unique_id_distributer("hasan", "01", "11", "2000")
print(citizen1.unique_id())
Additionally, you can eliminate the full call of your parent class by using super().
Child class example
# Child Class
class Unique_id_distributer(Unique_id_creator):
def __init__(self, name, birthdate, birthmonth, birthyear):
super().__init__(birthdate, birthmonth, birthyear)
self.name = name
def unique_id(self):
id = super().unique_id()
return "Dear "+ str(self.name) + ", Your Unique Id is: "+ str(id)

How to decrypted [<__main__.Food object at 0x1097ba828>

I am a python newbie. I want display actual names,values and calories instead of [<__main__.Food object at 0x1097ba828>, <__main__.Food object at 0x1097ba860>, <__main__.Food object at 0x1097ba898>] I know this question is very simple,but it would be a great help if you could let me know the answer!
class Food(object):
def __init__(self,n,v,w):
self.name = n
self.value = v
self.calories = w
def getValue(self):
return self.value
def getCal(self):
return self.calories
def density(self):
return self.getValue()/self.getCal()
def __str__(self):
return '<__main__.Food: '+self.name +' '+ self.value+' ' + self.calories
def buildMenu(self):
menu = []
for i in range(len(values)):
menu.append(Food(self.name[i], self.value[i], self.calories[i]))
return menu
names=['burger','fries','coke']
values=[1,2,3]
calories=[100,200,300]
if __name__ == '__main__':
new = Food(names, values, calories)
print(new.buildMenu())
Thank you!
I made two code changes to get what I think you're looking for. The first is to convert values to strings in your str function. The second is to use that.
def __str__(self):
return '<__main__.Food: '+ str(self.name) +' '+ str(self.value)+' ' + str(self.calories)
and
print (str(new)) #instead of print(new.buildMenu())
Now the output is:
<main.Food: ['burger', 'fries', 'coke'] [1, 2, 3] [100, 200, 300]
This is how I would do it, noting that we've created two classes: a separate Food and Menu class. The Menu class has an add method that appends to its foodItems property, though I don't feel like that's really necessary since we can just do direct property assignment:
m.foodItems = < some list of Food objects >
I've removed the confusing buildMenu method from the Food class, and defined __str__ methods for both classes:
class Food(object):
def __init__(self,n,v,w):
self.name = n
self.value = v
self.calories = w
def getValue(self):
return self.value
def getCal(self):
return self.calories
def density(self):
return self.getValue()/self.getCal()
def __str__(self):
return '\t'.join([self.name, str(self.value), str(self.calories)])
class Menu(object):
def __init__(self):
self.foodItems = []
def add(self, foodItem):
self.foodItems.append(foodItem)
def __str__(self):
"""
prints the food items
"""
s = 'Item\tValue\tCalories\n'
s += '\n'.join(str(f) for f in self.foodItems)
return s
names=['burger','fries','coke']
values=[1,2,3]
calories=[100,200,300]
m = Menu()
items = list(Food(n,v,c) for n,v,c in zip(names,values,calories))
m.foodItems = items
print(m)
And outputs like:
The issue you have is that you're printing a list of Food instances, not a single instance at a time. The list type's __str__ operator calls repr on the items the list contains, not str, so your __str__ method does not get run.
A simple fix is to just rename your __str__ method to __repr__.
I'd note that it's a bit strange that you're building a Food instance with lists of values for name, value and calories, just so that you can call a method on it to make a list of Food instances with the individual values. A more Pythoic approach would be to pass the lists to a classmethod that returns the list of instances, without the intermediate instance needing to exist:
#classmethod
def buildMenu(cls, names, values, calories):
menu = []
for i in range(len(values)): # consider using zip instead of looping over indexes
menu.append(cls(names[i], values[i], calories[i]))
return menu
You'd call it on the class:
if __name__ == '__main__':
print(Food.buildMenu(names, values, calories))

Categories

Resources