The error I get is TypeError: must be real number, not Field
from the rendering code which tries to rotate an image.
It only does this when I try to decorate live
with activated_range
.
@dataclass
class EntityZombie(entity_manager.Clickable, entity_manager.Rotatable, entity_manager.BaseEntity):
max_distance = 125
max_health = 20.0
# more class variables go here
# there's also a field here, which gets initialized to 0 without any issues
last_attack: int = field(init=False, default=0)
@utils.activated_range(250)
def live(self, game) -> None:
...
The decorator tries to check the distance from the entity to the player, and only run things if it's close enough to the player:
def activated_range(activate_distance: float):
def decorator(method):
def new_func(instance, game): # self.live(game)
# if the distance is too large, do not execute the method (live)
if instance.player_distance(game.player) > activate_distance:
return
method(instance, game)
return new_func
return decorator
Rotatable is an ABC that specifies the field rotation
:
class Rotatable(BaseEntity, abc.ABC):
rotation: float = field(init=False, default=0.0)
# some more methods that are not relevant here
Normally, the value of rotation
correctly gets set to 0 (as it's the default), but whenever I have the live
decorated with activated_range
, it will always set the value of rotation
to a dataclass Field
instead of the number it's supposed to be defaulted to.
I thought this might be an issue with the decorator, but live = utils.activated_range(250)(live)
also gives the same error.
Even weirder, on some tries when I run this, one or two of the EntityZombie
s actually do have their rotations set correctly, but then eventually one of them doesn't and the program crashes. The debugger shows that rotation
is set to the actual field, but the dataclass should be making it into the float (as that's what the default
is set to). It did that when I didn't decorate that function, so why not now?
Why does adding the decorator to one method break the dataclass and have the fields not default correctly? Any help would be greatly appreciated!
Full error:
Traceback (most recent call last):
File "/home/greateric/Documents/PYTHON/immatureidiotsimulator/main.py", line 131, in <module>
Game()()
File "/home/greateric/Documents/PYTHON/immatureidiotsimulator/main.py", line 43, in __call__
self.mainloop()
File "/home/greateric/Documents/PYTHON/immatureidiotsimulator/main.py", line 126, in mainloop
self.redraw_queue()
File "/home/greateric/Documents/PYTHON/immatureidiotsimulator/main.py", line 112, in redraw_queue
entity.blit_me(self.asset_manager)
File "/home/greateric/Documents/PYTHON/immatureidiotsimulator/entity/entityzombie.py", line 56, in blit_me
super().blit_me(asset_manager)
File "/home/greateric/Documents/PYTHON/immatureidiotsimulator/entity/entity_manager.py", line 197, in blit_me
surface = pygame.transform.rotate(surface, self.rotation)
TypeError: must be real number, not Field
I tried to reproduce it here, with an even more abstract and smaller minimal code, and invariably, I got the field
value instead of a number whenever the mixin class was not declared a dataclass.
So, I am confident there is some code in there that is setting the rotation of your EntityZombie instance to 0 prior to .blit_me()
being called - that would explain why some of the instances work. Also, it makes sense that this behavior is prevented to run whenever activate_range
is in effect: the decorator effect will prevent that other code to set the instance value to 0.
The workaround, as you had found out, is just to declare whatever mixins you have with declared field
attributes as dataclasses as well - this will "activate" the field descriptor for the most derived classes.