Поиск по блогу

четверг, 16 января 2014 г.

Связанные и несвязанные методы (self.)

Методы – это разновидность объектов, напоминающая функции, – они могут присваиваться переменным, передаваться функциям, сохраняться в структурах данных и так далее. Доступ к методам класса осуществляется через экземпляр класса или через сам класс и, фактически, в языке Python имеется две разновидности методов:

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

Попытка обращения к функциональному атрибуту класса через имя класса возвращает объект несвязанного метода. Чтобы вызвать этот метод, необходимо явно передать ему объект экземпляра в виде первого аргумента.
В Python 3.0 несвязанные методы напоминают простые функции и могут вызываться через имя класса. В версии 2.6 несвязанные методы – это совершенно иной тип данных, и они не могут вызываться без передачи им ссылки на экземпляр.

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

Попытка обращения к функциональному атрибуту класса через имя экземпляра возвращает объект связанного метода. Интерпретатор автоматически упаковывает экземпляр с функцией в объект связанного метода, поэтому вам не требуется передавать экземпляр в вызов такого метода.
In [1]:
class Spam:
    def doit(self, message):
        print(message)
В обычной ситуации мы создаем экземпляр и сразу же вызываем его метод (object1.doit()) для вывода содержимого аргумента:
In [3]:
object1 = Spam()
object1.doit('hello world')
hello world

Однако в действительности попутно создается объект связанного метода – как раз перед круглыми скобками в вызове метода. Т.е. мы можем получить связанный метод и без его вызова.
Квалифицированное имя object.name – это выражение, которое возвращает объект.
В следующем примере это выражение возвращает объект связанного метода, в котором упакованы вместе экземпляр (object1) и метод (Spam.doit). Мы можем присвоить этот связанный метод другому имени и затем использовать это имя для вызова, как простую функцию:
In []:
object1 = Spam()
x = object1.doit # Объект связанного метода: экземпляр+функция
x(hello world) # То же, что и object1.doit(‘...’)
С другой стороны, если для получения метода doit использовать имя класса (Spam.doit), мы получим объект несвязанного метода, который просто ссылается на объект функции. Чтобы вызвать метод этого типа, необходимо явно передавать экземпляр класса в первом аргументе:
In []:
object1 = Spam()
t = Spam.doit # Объект несвязанного метода
t(object1, howdy) # Передать экземпляр
Те же самые правила действуют внутри методов класса, когда используются атрибуты аргумента self, которые ссылаются на функции в классе. Выражение self.method возвращает объект связанного метода, потому что self – это объект экземпляра:
In [4]:
class Eggs:
    def m1(self, n):
        print(n)
    def m2(self):
        x = self.m1 # Еще один объект связанного метода
        x(42) # Выглядит как обычная функция
Eggs().m2() # Выведет 42
42

В большинстве случаев вы будете вызывать методы немедленно, сразу же после указания квалифицированного имени, поэтому вы не всегда будете замечать, что попутно создается объект метода. Но как только вы начнете писать программный код, который вызывает объекты единообразным способом, вам потребуется проявить внимание к несвязанным методам, потому что обычно они требуют явной передачи экземпляра в первом аргументе.

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

Связанные методы могут интерпретироваться как обычные вызываемые объекты, то есть как обычные функции, – они могут произвольно передаваться между компонентами программы.
Кроме того, так как связанные методы объединяют в себе функцию и экземпляр, они могут использоваться как любые другие вызываемые объекты и не требуют применения специальных синтаксических конструкций для вызова.
Ниже демонстрируется возможность сохранения четырех объектов связанных методов в списке и их вызов как обычных функций:
In [8]:
class Number:
    def __init__(self, base):
        self.base = base
    def double(self):
        return self.base * 2
    def triple(self):
        return self.base * 3
In [9]:
xN = Number(2) # Объекты экземпляров класса
yN = Number(3) # Атрибуты + методы
zN = Number(4)
xN.double() # Обычный непосредственный вызов
Out[9]:
4
In [10]:
acts = [xN.double, yN.double, yN.triple, zN.double] # Список связанных методов
for act in acts: # Вызовы откладываются
    print(act()) # Вызов как функции
4
6
9
8

Как и простые функции, объекты связанных методов обладают информацией, позволяющей провести интроспекцию, включая атрибуты, обеспечивающие доступ к объекту экземпляра и к методу. Вызов связанного метода просто задействует эту пару:
In [13]:
bound = xN.double
bound.__self__, bound.__func__
Out[13]:
(<__main__.Number instance at 0x00000000062CDC08>, <function __main__.double>)
In [14]:
bound.__self__.base
Out[14]:
2
In [15]:
bound()
Out[15]:
4
Фактически связанные методы – это лишь одна из разновидностей вызываемых объектов в языке Python. Как демонстрирует следующий пример, простые функции, определенные с помощью инструкции def или lambda, экземпляры, наследующие метод call и связанные методы экземпляров могут обрабатываться и вызываться одинаковыми способами:
In [16]:
def square(arg):
    return arg ** 2 # Простые функции (def или lambda)
In [17]:
class Sum:
    def __init__(self, val): # Вызываемые экземпляры
        self.val = val
    def __call__(self, arg):
        return self.val + arg
In [18]:
class Product:
    def __init__(self, val): # Связанные методы
        self.val = val
    def method(self, arg):
        return self.val * arg
In [19]:
sobject = Sum(2)
pobject = Product(3)
actions = [square, sobject, pobject.method] # Функция, экземпляр, метод
for act in actions:                         # Все 3 вызываются одинаково
    print(act(5))      # Вызов любого вызываемого объекта с 1 аргументом
25
7
15

In [20]:
actions[-1](5) # Индексы, генераторы, отображения
Out[20]:
15
In [21]:
[act(5) for act in actions]
Out[21]:
[25, 7, 15]
In [22]:
list(map(lambda act:act(5), actions))
Out[22]:
[25, 7, 15]
Технически классы также принадлежат к категории вызываемых объектов, но обычно они вызываются для создания экземпляров, а не для выполнения какой-либо фактической работы, как показано ниже:
In [23]:
class Negate:
    def __init__(self, val): # Классы - тоже вызываемые объекты
        self.val = -val # Но вызываются для создания объектов
    def __repr__(self): # Реализует вывод экземпляра
        return str(self.val)
In [24]:
actions = [square, sobject, pobject.method, Negate] # Вызвать класс тоже
for act in actions:
    print(act(5))
25
7
15
-5

In [25]:
[act(5) for act in actions] # Вызовет __repr__, а не __str__!
Out[25]:
[25, 7, 15, -5]
In [27]:
table = {act(5): act for act in actions} # генератор словарей в 2.6/3.0
for (key, value) in table.items():
    print('{0:2} => {1}'.format(key, value)) # метод str.format в 2.6/3.0
25 => <function square at 0x00000000062DC438>
15 => <bound method Product.method of <__main__.Product instance at 0x00000000062DD5C8>>
-5 => __main__.Negate
 7 => <__main__.Sum instance at 0x00000000062DD8C8>

Как видите, связанные методы и модель вызываемых объектов вообще – это лишь некоторые из множества особенностей, обеспечивающие языку Python невероятную гибкость.

Комментариев нет:

Отправить комментарий