In this case, the important thing is to understand when each call is made, and how everything is put together.
Do not try to use __
for private variables
Before addressing the central point, however, I draw attention to one thing - the use of __
, two underscores, as a prefix of a name in a class, causes a name mangling of the name, but this is not the same as a "private variable". Older Python documentation (things over 10 years old) tend to say that the use of two underscores is the same as a "private" variable as it exists in Java or other languages - that's not true - in Python there is no concept of private variables except the indication that an attribute should not be modified by class users - and this is simply a convention. Two __
activates a compile-time variable name modification mechanism (yes, Python code is compiled although this step is transparent to the developer). In this particular case, this will only cause your self.__name
variable in the inherited class to be different from the self.__name
variable in the parent class - if you kept your code and changed only the setter, the getter would try to read a variable that does not exist.
What is an object returned by property
In Python, the built-in function "property" returns an object that is a descriptor . This is an object that implements a method between __get__
, __set__
or __delete__
- and these methods are in an object that is a class attribute that causes the same attribute handling in the instance follow rules different from those of normal attributes. In practice, the property is just a "neat" way of dynamically creating a descriptor - what matters is that it exists as a class attribute.
So even if it were possible to change the
fget
of a property object (and it is not - it exists as a "read only" attribute on the object), if you did this in the child class, you would change the property in the class parent - is the same object - and the behavior would be changed for all instances of both the parent class and the child, as well as other subclasses of the parent.
1st form - Modifying properties in inheritance - hard-coding getters and setters
Perhaps the simplest way to change a property with inheritance is simply to create a new property, zeroed - it will remain a separate and independent class attribute in the child class.
As you are doing, you have the advantage that your getter and setter methods are "standard" methods. (If you use the property in decorator form, these methods are "hidden" and you could not use them.)
The problem is that you can not simply put a line of type:
name = property(fget=get_name, fset=set_name)
In the body of the child class if get_name
is not defined in the child class as well. And as you've noticed, it's not possible to call super()
in the class name.
This would work here - Python will find itself and create the property:
class Employee(Person):
...
def set_name(self, value):
...
name = property(fget=Person.get_name, fset=set_name)
In Python 3, normal methods (which are used in the instance) are functions without anything special when retrieved as class attributes. The "property" on the other hand wants exactly this as parameters: normal functions - it will be in charge of inserting the self
parameter when these functions are called.
The only disadvantage of this method is that you are required to place the name of the class where the original getter is - if you are using a multi-inheritance architecture, you may have problems there.
2nd form - Modifying properties in inheritance - __init_subclass__
A more elegant way is to use this functionality introduced in Python 3.6: a class to be inherited will have the __init_subclass__
method called with the newly created class as parameter. This special method is a class method that is only called once for each new inherited class.
Then you can Rebuild the property within the __init_subclass__
method - in this case, the normal class attribute access rules will find the desired methods for your getter and setter - and you do not need worrying about tagging them explicitly as part of a property in each new class created.
Your root class would look like this:
class Person (...):
@abstractmethod
def __init__(self, name, telephone):
self.name = name
self.telephone = telephone
def __init_subclass__(cls, **kw):
cls.name = property(cls.get_name, cls.set_name)
super().__init_subclass__(**kw)
def get_name(self) -> str:
return self._name
def set_name(self, value: str):
self._name = value
name = property(fget=get_name, fset=set_name)
And then, any daughter class that overrides either get_name
or set_name
will have a new property and the override rules will work normally for access to the property name
- it will be recreated with the method visible in that descending class.