Python/Протокол дескрипторів
Прив’язка методів
[ред.]Чим відрізняється функція від методу якогось об’єкта? Давайте подивимось. Спершу створимо об’єкт з методом:
class X:
def __init__(self, name):
self.name = name
def hello(self):
print('hello, my name is', self.name)
x = X('petro')
x.hello()
# OUT: hello, my name is petro
Все працює, метод викликається. Тепер давайте створимо функцію, запишемо її в один з атрибутів об’єкта і звіди викликатимемо:
def getname(self):
return self.name
x.getname = getname
x.getname()
# OUT: Traceback (most recent call last):
# OUT: File "<input>", line 1, in <module>
# OUT: TypeError: getname() takes exactly 1 argument (0 given)
Чому коли ми викликаємо функцію з об’єкта їй не передається цей об’єкт в перший параметр (self
)?
А тому, що функція - це ще не метод:
x.getname # Функція
# OUT: <function getname at 0xa6c636c>
x.hello # метод
# OUT: <bound method X.hello of <__main__.X object at 0xa7a7f6c>>
Це в об’єкта. А в класі?
X.hello
# OUT: <function hello at 0xa6c626c>
А в класі - просто функція. Відмінність метода в тому - що він обгортка навколо звичайної функції, яка повертається з атрибутів екземпляра, і коли ми пишемо x.hello()
, тобто викликаємо цю обгортку, всередині неї виконується звичайний X.hello(x).
А давайте покладемо в клас і нашу функцію:
X.getname = getname
x.getname
# OUT: <function getname at 0xa6c636c>
Бачимо що в об’єкті нічого не змінилось. А все тому, що ми переписали атрибут екземпляра, а при пошуку атрибутів спершу дивляться чи це не атрибут екземпляра (в словнику __dict__
), і тільки після того лізуть в клас.
x.__dict__
# OUT: {'getname': <function getname at 0xa6c636c>, 'name': 'petro'}
del x.__dict__['getname']
x.getname
# OUT: <bound method X.getname of <__main__.X object at 0xa7a7f6c>>
x.getname()
# OUT: 'petro'
І справді, наша функція тепер стала методом, хоча єдине що ми зробили - присвоїли її атрибуту класу, а не екземпляру.
Як це все працює всередині? Хто загортає функції в метод? Дескриптори!
TODO: розібрати метод __get__
самих функцій.
Приклади
[ред.]Статичний метод
[ред.]Виклик процедури в стилі мови Паскаль
[ред.]В мові Паскаль, якщо процедура не має аргументів, щоб її викликати не обов’язково писати в кінці її імені пару порожніх дужок. Можна просто написати
clrscr;
writeln;
І процедури виконаються.
Давайте зробимо таке саме в Python, щоправда для методів звісно. Виглядатиме все так:
class X:
@PascalMethod
def hello():
print('hello')
@PascalMethod
def world():
print('world')
def say():
X.hello # Виклики методів які зовсім не виглядають як виклики методів
X.world # А все тому що це паскалівські методи.
say() # Та все ж це виклики, тому що нам роздруковується фраза:
# OUT: hello
# OUT: world
А ось сам декоратор PascalMethod
:
class PascalMethod:
def __init__(self, m):
self.m = m # Запам’ятовуємо наш метод в конструкторі
def __get__(self, obj, cls):
return self.m() # При будь-якому читанні атрибута - викликаємо
Перевірка значення атрибута на відповідність умові
[ред.]class ConstrainedField:
def __init__(self, initial, check):
self.check = check
self.value = initial
def __set__(self, obj, value):
if self.check(value):
self.value = value
else:
raise TypeError("Value {!r} don't pass check".format(value))
def __get__(self, obj, cls):
if obj:
return self.value
else:
return self
def is_adult(age):
return isinstance(age, int) and age >= 18
class Adult:
age = ConstrainedField(20, is_adult)
Посилання
[ред.]- Raimond Hettinger. Descriptor HowTo Guide (англ.) . python.org. Процитовано 26 травня 2013.
- Peter Inglesby (6 липня 2012). Discovering Descriptors (англ.) . YouTube. Процитовано 26 травня 2013.
- Гвідо ван Россум (21 червня, 2010). The Inside Story on New-Style Classes (англ.) . python-history.blogspot.com. Процитовано 26 травня 2013.
- Understanding __get__ and __set__ and Python descriptors StackOverflow
- Introduction to Python descriptors Alex Starostin, IBM DeweloperWorks, 26 Червня 2012
- The Python Language Reference > Special method names > Data model > Customizing attribute access
- Python instance descriptors: when class descriptors aren’t dynamic enough 29 січня 2009 Brian Beck