Override Property() in Child Class

Asked

Viewed 223 times

3

A few days ago I asked a similar question to this, however, the method employed in creating the property was via decorators (@property and @name.setter). Here I am creating the 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, name.fset class Person will have to point to a modified version of the function set_name, but I don’t know what that would be like in practice. I made a few attempts but it didn’t 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

1 answer

2


In this case, the important thing is to understand when each of the calls is made, and how everything is assembled together.

Don’t 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, cause a "name mangling" of the name, but this is not the same as a "private variable". Older Python documentation (things older than 10 years) tend to say that the use of two underscores is the same as a "private" variable as exists in Java or other languages - this is not true - in Python there is no concept of private variables, except that an attribute should not be modified by users of the class - and this is simply a convention. Two __ activate a variable name modification mechanism at compile time (yes, Python code is compiled despite this step being transparent to the developer). In that specific case, it will only cause your variable self.__name in the inherited class is different of the variable self.__name in the mother 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 among __get__, __set__ or __delete__ - and it is these methods on an object that is a class attribute that cause the manipulation of the same attribute in the instance follow rules other than those of normal attributes. In practice, Property is just a "tidy" 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’s not - it exists as a "read only" attribute in the object), if you did that in the daughter class, it would change the Property in the parent class - it’s the same object - and the behavior would be changed for all instances both of the parent class, how much of the son, how much of other subclasses of the father.

1st form - Modifying properties in inheritance - hard-coding the getters and setters

Perhaps the simplest way to change a legacy Property is simply to create a new, zeroed Property - it will remain a separate, independent class attribute in the daughter class.

The way you are doing it has 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’t just put a line like:

 name = property(fget=get_name, fset=set_name)

In the body of the daughter class if the get_name is not set in the daughter class either. And as you noticed, it is not possible to call super() in the class name.
This would work here - Python will find 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. "Property" on the other hand wants exactly this as parameters: normal functions - it will take charge of inserting the parameterself when these functions are called.

The only drawback of this method is that you are required to fix the name of the class where the original getter is - if you are using a multiple heritage architecture, you may have problems there.

2nd form - Modifying properties in inheritance - __init_subclass__

A more elegant way may be to use this functionality introduced in Python 3.6: a class when inherited will have the method __init_subclass__ 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.

So it’s possible Recreate the Property within the method __init_subclass__ - in this case, the normal class attribute access rules will find the methods desired for your getter and Setter - and you don’t have to worry about marking 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 you override get_name either of set_name will get a new property and override rules will work normally for access to Property name - he will be recreated with the visible method in that descending class.

  • In the case of the 2nd Way, to override the get_name or a set_name in the daughter class, would it be enough to recreate the function without the need to clarify the creation of the new property? That is, in the daughter class would no longer need the line name = property(fget=Person.get_name, fset=set_name), simply recreate the function set_nome.

  • 1

    Exactmistress - the function __init_subclass__ will be automatically called by the language, and will receive the child class as a parameter. When referring to cls.get_name, normal inheritance attribute access mechanisms will apply, and the most derived methods, even in a complicated hierarchy, will be used automatically.

  • Hello John. Sorry for the delay in feedback. I performed a test following the instructions you left on "2nd form". The first mistake I had was that super().__init_subclass() does not receive arguments. I confirmed this in doc. That’s why I’m making the mistake "Typeerror: init_subclass() takes in Arguments (1 Given)", I tried to follow the example of doc super().__init_subclass__(**kw), then I have no error but the function is not overwritten. Follow the code

  • Find the problem. Is that at the initialization of my object, ie in __init__ the assignment is not going through the proprerty. For this to happen just change the first line of the __init__ of Person of self._name = name for self.name = name. With respect to function super().__init_subclass__, really she gets no arguments.

  • Ah yes - indeed, there was a cls remaining in the call to super() of __init_subclass__. It is automatically added by super().

Browser other questions tagged

You are not signed in. Login or sign up in order to post.