Як працюють властивості Python?


78

Я успішно використовую властивості Python, але я не бачу, як вони можуть працювати. Якщо я розмежую властивість поза класом, я просто отримую об'єкт типу property:

@property
def hello(): return "Hello, world!"

hello  # <property object at 0x9870a8>

Але якщо я кладу властивість у клас, поведінка зовсім інша:

class Foo(object):
   @property
   def hello(self): return "Hello, world!"

Foo().hello # 'Hello, world!'

Я помітив, що unbound Foo.helloвсе ще є propertyоб'єктом, тому екземпляр класу повинен робити магію, але що це за магія?



гарно відобразити обидва питання @MartinThoma, дякую!
Кушан Гунасекера

3
@MartinThoma max recursion depth exceeded... hahaa
Zain Arshad

Відповіді:


54

Як зазначали інші, вони використовують мовну функцію, яка називається дескрипторами.

Причина повернення фактичного об'єкта властивості при доступі до нього через клас Foo.helloполягає у тому, як властивість реалізує __get__(self, instance, owner)спеціальний метод:

  • Якщо до екземпляра здійснюється доступ до дескриптора , тоді цей екземпляр передається як відповідний аргумент і ownerє класом цього екземпляра.
  • Коли доступ до нього здійснюється через клас, тоді instanceзначення None і ownerпередається лише . propertyОб'єкт розпізнає це і повертається self.

Окрім інструкцій дескрипторів , див. Також документацію щодо впровадження дескрипторів та виклику дескрипторів у Мовному посібнику.


25

Для того, щоб @properties працював належним чином, клас повинен бути підкласом об'єкта . коли клас не є підкласом об'єкта, тоді, коли ви вперше намагаєтесь отримати доступ до сеттера, він фактично робить новий атрибут із коротшим ім'ям, замість того, щоб здійснювати доступ через сеттер.

Наступне нічого НЕ працює правильно.

class C(): # <-- Notice that object is missing

    def __init__(self):
        self._x = None

    @property
    def x(self):
        print 'getting value of x'
        return self._x

    @x.setter
    def x(self, x):
        print 'setting value of x'
        self._x = x

>>> c = C()
>>> c.x = 1
>>> print c.x, c._x
1 0

Наступне буде працювати правильно

class C(object):

    def __init__(self):
        self._x = None

    @property
    def x(self):
        print 'getting value of x'
        return self._x

    @x.setter
    def x(self, x):
        print 'setting value of x'
        self._x = x

>>> c = C()
>>> c.x = 1
setting value of x
>>> print c.x, c._x
getting value of x
1 1

31
Слід пам’ятати, що станом на Python 3 успадкування від об’єкта більше не потрібно.
DeFazer

8
Або точніше, у Python 3 класи за замовчуванням успадковують від об’єкта , тому це не потрібно писати в коді явно.
Натаніель Верхаарен

12

Властивості є дескрипторами , і дескриптори поводяться спеціально, коли є членом екземпляра класу. Коротше кажучи, якщо aє екземпляром типу Aі A.fooє дескриптором, то a.fooце еквівалентно A.foo.__get__(a).


Насправді вони, схоже, також працюють із класом старого стилю (у Python 2.7). Але дякую за посилання, прочитаю.
Fred Foo

Зверніть увагу, що підпис методу для __get__()неправильний. Він має два аргументи (на додаток до власного). Інакше добре пояснили.
Тім Йейтс,

@larsmans: На пов'язаній сторінці: "Зверніть увагу, що дескриптори викликаються лише для нових стильових об'єктів або класів." Також я пам’ятаю, що колись пробував їх на уроках старого стилю.
Sven Marnach

@Tim: Другий аргумент необов’язковий.
Свен Марнах,

2
@larsmans: Я провів кілька швидких тестів. Тип самого дескриптора повинен бути класом нового стилю, інакше він не буде працювати. Клас, що містить дескриптор, не має значення. Наведену вище цитату можна прочитати будь-яким способом.
Sven Marnach

2

propertyОб'єкт просто реалізує протокол дескриптора: http://docs.python.org/howto/descriptor.html


15
Я, звичайно, розумію міркування, що лежать в основі dw у цьому випадку, і ця відповідь, і відповідна, позбавлені реального пояснення, і в основному є лише посиланнями. Хоча цього достатньо у деяких випадках вирішення помилок, схоже, ОП шукав людського пояснення з цього приводу.
Morgan Wilde
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.