Hi @RafalD3molisher
TL;DR: The cleanest solution by far here is to declare a class attribute, modify that attribute in your __init__()
, and then let access be public to callers. Managing or “protecting” attributes (class or instance) is a bit of a Python anti-pattern in (almost all) situations.
If you are interested in the complicated and gory details, read below.
To expand a bit on what @IsaacG has said …
With the way the tests are calling Alien.total_aliens_created
, it must be defined as a class attribute, not a property object.
@property
returns a special descriptor object that uses a class instance to construct a PyProperty_Type
. To oversimplify — it cannot resolve/return attributes named cls.<attribute>
— it can only resolve/return attributes named self.<attribute>
.
For it to resolve cls.<attribute>
, it would need to wrap a function that took cls
(the class) as a parameter and not self
(the instance created from the class). It would also need to be a @classmethod
as well as a @property
. But as of Python 3.11, you cannot chain @classmethod
and @property
together.
So there is no easy way in Python 3.11/3.11+ to define a class property via the @property
decorator. But it can be done via a metaclass.
can != should
I wrote the code below after doing some digging and falling down a rabbit hole. I wanted to know if it was possible to define an @property
on a class. I managed to write code that passed the exercise tests, but it is verbose (it defines two property decorators AND a property.setter) and it is neither readable nor elegant:
class AlienMeta(type):
_total_aliens_created = 0
@property
def total_aliens_created(cls):
return cls._total_aliens_created
@total_aliens_created.setter
def total_aliens_created(cls, value):
cls._total_aliens_created = value
class Alien(metaclass=AlienMeta):
def __init__(self, x_coordinate, y_coordinate):
self.x_coordinate = x_coordinate
self.y_coordinate = y_coordinate
self.health = 3
Alien.total_aliens_created += 1
@property
def total_aliens_created(self):
return type(self).total_aliens_created
def hit(self):
self.health -= 1
def is_alive(self):
return self.health > 0
def teleport(self, x, y):
self.x_coordinate = x
self.y_coordinate = y
def collision_detection(self, other):
pass
def new_aliens_collection(positions):
new_list = [ ]
for x, y in positions:
new_list.append(Alien(x, y))
return new_list
I can’t really think of a scenario where I’d want to use it. But it does work to manage the “class property”.
Comment out the @total_aliens_created.setter
block and a caller cannot assign a value to Alien.total_aliens_created
. Instead, an AttributeError: property 'total_aliens_created' of 'AlienMeta' object has no setter
error is thrown.
But that seems like way too much work for way to little value IMHO.