Связанные и несвязанные методы

Несвязанные методы класса - это методы без аргумента self.

При обращении к функциональному атрибуту класса через имя класса получаем объект несвязанного метода. Для вызова метода нужно передать экземпляр класса в первый аргумент (self).

Связанные методы экземпляра - пара self + функция.

Попытка обращения к функциональному атрибуту класса через имя экземпляра возвращает объект связанного метода. Интерпретатор автоматически упаковывает экземпляр с функцией в объект связанного метода, поэтому вам не требуется передавать экземпляр в вызов такого метода.

И несвязанные методы класса, и связанные методы экземпляра - объекты (так же, как числа и строки) и могут передаваться в виде аргументов (так же как числа или строки). При запуске оба требуют экземпляр в первом аргументе (self).

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

class Spam:
    def doit(self, message):
        print(message)

object1 = Spam()
object1.doit('hello world')

На самом деле создается объект связанного метода object1.doit, в который упакованы вместе экземпляр object1 и метод Spam.doit.

Можно присвоить этот объект переменной и использовать переменную для вызова, как простую функцию:

x = object1.doit    # Объект связанного метода: экземпляр+функция
x('hello world')    # То же, что и object1.doit('...')

Вызовем метод через имя класса:

object1 = Spam()
t = Spam.doit       # Объект несвязанного метода
t(object1, 'howdy') # Передать экземпляр в первый аргумент

self.method - это объект связанного метода экземпляра, так как self - объект экземпляра.

class Eggs:
    def m1(self, n):
        print(n)
    def m2(self):
        x = self.m1     # Еще один объект связанного метода
        x(42)           # Выглядит как обычная функция

Eggs().m2()             # Выведет 42

В Python 3 несвязанные методы - это функции

В Python 3 можно в классе создавать методы без аргумента self и не писать декоратор @staticmethod.

class A(object):
    def __init__(self, x):
        self.x = x

    def __str__(self):
        return str(self.x)

    @staticmethod
    def new_A(s):
        t = A(int(s))
        return t

    @staticmethod
    def common_foo(x, k):
        return x * k

    def a_foo(self, k):
        self.x = __class__.common_foo(self.x, k)

    def func_foo(x, k):
        return x * k

    def a_func_foo(self, k):
        self.x = __class__.func_foo(self.x, k)

a1 = A(1)
print('a1 =', a1)

a2 = A.new_A("2")
print('a2 =', a2)

z = A.common_foo(3, 4)
print('z =', z)

a1.a_foo(5)
print('a1 =', a1)

z = A.func_foo(3, 4)
print('z =', z)

a1.a_func_foo(5)
print('a1 =', a1)

Использование декоратора @staticmethod повышает читаемость кода.

Связанные методы и другие вызываемые объекты

Связанные методы экземпляра класса - это объекты, которые хранят и экземпляр, и метод. Их можно использовать как обычные функции:

>>> class Number:
... def __init__(self, base):
...     self.base = base
... def double(self):
...     return self.base * 2
... def triple(self):
...     return self.base * 3
...
>>> x = Number(2)   # Объекты экземпляров класса
>>> y = Number(3)   # Атрибуты + методы
>>> z = Number(4)
>>> x.double()      # Обычный непосредственный вызов
4
>>> acts = [x.double, y.double, y.triple, z.double] # Список связанных методов
>>> for act in acts:                                # Вызовы откладываются
...     print(act())                                # Вызов как функции
...
4
6
9
8

Можно посмотреть на атрибуты, которые дают доступ к объекту экземпляра и к методу:

>>> bound = x.double
>>> bound.__self__, bound.__func__
(<__main__.Number object at 0x0278F610>, <function double at 0x027A4ED0>)
>>> bound.__self__.base
2
>>> bound() # Вызовет bound.__func__(bound.__self__, ...)
4

Можно обрабатывать одинаково:

  • функции, определенные через def или lambda;
  • экземпляры, наследующие метод __call__;
  • связанные методы экземпляров.
>>> def square(arg):
...     return arg ** 2 # Простые функции (def или lambda)
...
>>> class Sum:
...     def __init__(self, val): # Вызываемые экземпляры
...         self.val = val
...     def __call__(self, arg):
...         return self.val + arg
...
>>> class Product:
...     def __init__(self, val): # Связанные методы
...         self.val = val
...     def method(self, arg):
...         return self.val * arg
...
>>> sobject = Sum(2)
>>> pobject = Product(3)
>>> actions = [square, sobject, pobject.method] # Функция, экземпляр, метод
>>> for act in actions:                         # Все 3 вызываются одинаково
...     print(act(5))                           # Вызов любого вызываемого
...                                             # объекта с 1 аргументом
25
7
15
>>> actions[-1](5)                              # Индексы, генераторы, отображения
15
>>> [act(5) for act in actions]
[25, 7, 15]
>>> list(map(lambda act: act(5), actions))
[25, 7, 15]

Класс - тоже вызываемый объект. Но он вызывается для создания экземпляра:

>>> class Negate:
...     def __init__(self, val):    # Классы - тоже вызываемые объекты
...         self.val = -val         # Но вызываются для создания объектов
...     def __repr__(self):         # Реализует вывод экземпляра
...         return str(self.val)
...
>>> actions = [square, sobject, pobject.method, Negate] # Вызвать класс тоже можно
>>> for act in actions:
...     print(act(5))
...
25
7
15
-5
>>> [act(5) for act in actions]     # Вызовет __repr__, а не __str__!
[25, 7, 15, -5]

Посмотрим, какие это объекты:

>>> table = {act(5): act for act in actions}    # генератор словарей
>>> for (key, value) in table.items():
...     print('{0:2} => {1}'.format(key, value))
...
-5 => <class '__main__.Negate'>
25 => <function square at 0x025D4978>
15 => <bound method Product.method of <__main__.Product object at 0x025D0F90>>
7 => <__main__.Sum object at 0x025D0F70>

Связанные методы и callback

Пример использования связанных методов - GUI на tkinter.

Везде, где можно использовать функцию, можно использовать связанный метод.

Можно написать через функцию (или лямбда-выражение):

def handler():
...     сохраняет информацию о состоянии в глобальных переменных...
...
widget = Button(text='spam', command=handler)

Можно использовать связанный метод:

class MyWidget:
    def handler(self):
...     сохраняет информацию о состоянии в self.attr...
    def makewidgets(self):
        b = Button(text='spam', command=self.handler)

self.handler - объект связанного метода. В нем хранятся self и MyWidget.handler. Так как self ссылается на оригинальный экземпляр, то потом, когда метод handler будет вызван для обработки событий, у него будет доступ к экземпляру и его атрибутам (где можно хранить информацию о состоянии объекта между событиями).

Еще один вариант хранения информации между событиями - переопределение метода __call__ (см. перегрузку операторов).

results matching ""

    No results matching ""