Прив’язка методів Чим відрізняється функція від методу якогось об’єкта? Давайте подивимось. Спершу створимо об’єкт з методом:

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)

Посилання

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