Overwriting property () in the child class

3

A few days ago I asked a similar question, but the method used to create property was via decorators ( @property and @name.setter ). Here I am creating property via function property() :

from abc import ABCMeta, abstractmethod

class Person(metaclass=ABCMeta):

    @abstractmethod
    def __init__(self, name, telephone):
        self.name = name
        self.telephone = telephone

    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)

class Employee(Person):

    def __init__(self, name, telephone, email):
        super().__init__(name, telephone)
        self.email = email

In theory I know what needs to be done, the name.fset attribute of the Person class will have to point to a modified version of the set_name function, but I do not know how that would be in practice. I made a few attempts but it did not work:

class Employee(Person):

    def __init__(self, name, telephone, email):
        super().__init__(name, telephone)
        self.email = email

    def set_name(self, value):
        super().set_name('override')

    super(self.__class__, self.__class__).name.fset = set_name
    
asked by anonymous 15.12.2018 / 14:41

1 answer

2

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.

    
19.12.2018 / 21:27