Понимание декоратора @property и наследования

Python 3 здесь, на всякий случай, если это важно.

Я пытаюсь правильно понять, как реализовать наследование, когда используется @property, и я уже искал StackOverflow и прочитал около 20 похожих вопросов, но безрезультатно, потому что проблемы, которые они пытаются решить, немного отличаются. Это код, который я использую для тестирования:

class Example:
    def __init__(self):
        self.__data = None

    @property
    def data(self):
        return self.__data

    @data.setter
    def data(self, data):
        self.__data = data


class Example2(Example):
    def __init__(self):
        super().__init__()

    @property
    def data(self):
        return super().data  # Works!

    @data.setter
    def data(self, data):
        data = '2' + data
        #Example.data = data   # Works, but I want to avoid using the parent name explicitly
        #super().data = data  # Raises AttributeError: 'super' object has no attribute 'data'
        #super().data.fset(self, data) # Raises AttributeError: 'NoneType' object has no attribute 'fset'
        #super(self.__class__, self.__class__).data = data  # Raises AttributeError: 'super' object has no attribute 'data'
        super(self.__class__, self.__class__).data.fset(self, data)  # Works!


a = Example2()
a.data = 'element a'
print(a.data)

Чего я не могу понять, так это почему super().data работает в Example2 геттере, а не в сеттере. В смысле, почему в сеттере нужен метод с привязкой к классу, а в геттере работает метод с привязкой к экземпляру?

Может ли кто-нибудь указать мне объяснение или объяснить, почему я получаю AttributeError в трех из пяти различных вызовов, которые я тестирую?

Да, я знаю, я мог бы использовать Example.data в сеттере, но это не нужно в геттере, и а) я бы предпочел не использовать явно имя родительского класса, если это возможно, и б) я не понимаю асимметрии между геттером и сеттером .


person Raúl Núñez de Arenas Coronado    schedule 09.02.2016    source источник
comment
Я думаю, что это было проблемой некоторое время... stackoverflow.com/a/13599342/1345165. По-видимому, единственный способ - явно использовать имя родительского класса.   -  person dnaranjo    schedule 09.02.2016
comment
Вы действительно не должны использовать self.__class__ в вызовах super(), см. при вызове super() в производном классе, могу ли я передать self.__class__?   -  person Martijn Pieters    schedule 09.02.2016
comment
И ваш последний вызов работает, потому что передача объекта класса в super() создает прокси для класса, а доступ к имени свойства в классе возвращает исходный объект свойства. Таким образом, super(Example2, Example2).data возвращает исходное свойство, поэтому вы можете получить доступ к .fset. Или вы можете связать свойство с super(Example2, Example2).__set__(self, new_value).   -  person Martijn Pieters    schedule 09.02.2016
comment
@dnaranjo, этот вопрос был одним из тех, которые я читал, и я думал, что он решен. Похоже, я был неправ. Спасибо :)   -  person Raúl Núñez de Arenas Coronado    schedule 09.02.2016
comment
@MartijnPieters, большое спасибо за всю информацию. Моя проблема не в том, чтобы обновлять только сеттер или геттер, я знаю, что могу сделать это более чем одним способом, используя @CLASS_NAME.var.setter в качестве декоратора, используя property() и т. д. Я сомневался, почему один синтаксис работал на сеттере, а другой синтаксис не работал. 'т. Благодаря вашему объяснению, теперь я знаю, почему (более или менее), и оказывается, что мне нужно больше изучать свойства.   -  person Raúl Núñez de Arenas Coronado    schedule 09.02.2016
comment
@MartijnPieters, кстати, указанный вами дубликат - ЕДИНСТВЕННАЯ ветка, которую я не нашел, когда исследовал проблему !!! Большое спасибо за указание на этот вопрос!   -  person Raúl Núñez de Arenas Coronado    schedule 09.02.2016


Ответы (1)


вы должны сделать что-то вроде этого:

class Example:
    def __init__(self):
        self._data = None

    @property
    def data(self):
        return self._data

    @data.setter
    def data(self, data):
        self._data = data


class Example2(Example):
    def __init__(self):
        super().__init__()

    @Example.data.setter
    def data(self, data):
        data = '2' + data
        self._data = data


    a = Example2()
    a.data = 'element a'
    print(a.data)

вы получаете ошибку атрибута, потому что у класса нет атрибута данных, он есть у экземпляра.

Если вы хотите переопределить @property, просто сделайте это:

class Example:
def __init__(self):
    self._data = None

    @property
    def data(self):
        return self._data

    @data.setter
    def data(self, data):
        self._data = data


class Example2(Example):
    def __init__(self):
    super().__init__()

    @property
    def data(self):
        return self._data

    @data.setter
    def data(self, data):
        data = '2' + data
        self._data = data
person Serbitar    schedule 09.02.2016
comment
Но тогда вы пропускаете @property, в чем и была суть проблемы. - person dnaranjo; 09.02.2016
comment
Я отредактировал некоторые ошибки в реализации. Если вы переопределяете @property, вы ничего не наследуете, вы переопределяете все. Какой там смысл? Пример также работает с переопределением свойства. - person Serbitar; 09.02.2016
comment
Спасибо, Serbitar, я знал об этом решении, но я пытался полностью изолировать дочерний элемент от родителя, то есть расширить интерфейс и наследовать только интерфейс. Я не объяснил это в своем OP, потому что думал, что это очевидно из кода, теперь я вижу, что это не так. Вот почему я использовал super() вместо жесткого кодирования имени родительского класса. Что делать, если я не знаю внутренностей сеттера в родительском? Я не знаю, как это решить, и в любом случае я сомневался, почему один вариант super() работает, а другой нет. - person Raúl Núñez de Arenas Coronado; 09.02.2016