Как метод python автоматически получает self в качестве первого аргумента? - PullRequest
11 голосов
/ 11 июля 2011

Рассмотрим этот пример шаблона стратегии в Python (адаптирован из примера здесь ).В этом случае альтернативной стратегией является функция.

class StrategyExample(object):
    def __init__(self, strategy=None) :
        if strategy:
             self.execute = strategy

    def execute(*args):
        # I know that the first argument for a method
        # must be 'self'. This is just for the sake of
        # demonstration 
        print locals()

#alternate strategy is a function
def alt_strategy(*args):
    print locals()

Вот результаты для стратегии по умолчанию.

>>> s0 = StrategyExample()
>>> print s0
<__main__.StrategyExample object at 0x100460d90>
>>> s0.execute()
{'args': (<__main__.StrategyExample object at 0x100460d90>,)}

В приведенном выше примере s0.execute является методом (непростая ванильная функция) и, следовательно, первый аргумент в args, как и ожидалось, равен self.

Вот результаты для альтернативной стратегии.

>>> s1 = StrategyExample(alt_strategy)
>>> s1.execute()
{'args': ()}

В этом случае s1.execute является простой ванильной функцией и, как и ожидалось, не получает self.Следовательно args пусто.Подождите минуту!Как это случилось?

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

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

>>> print dir(s0.execute)
['__cmp__', '__func__', '__self__', ...]
>>> print dir(s1.execute)
# does not have __self__ attribute

Имеет ли атрибут __self__ значение s0.execute (метод), но отсутствие его на s1.execute (функция) как-то объяснить эту разницу в поведении?Как это все работает внутри?

Ответы [ 4 ]

8 голосов
/ 11 июля 2011

Вам необходимо назначить несвязанный метод (т. Е. С параметром self) для класса или связанный метод для объекта.

С помощью механизма дескриптора вы можете создавать свои собственные связанные методы, также это работает, когда вы назначаете (несвязанную) функцию классу:

my_instance = MyClass()    
MyClass.my_method = my_method

При вызове my_instance.my_method() поиск не найдет запись в my_instance, поэтому в более поздний момент он выполнит следующее: MyClass.my_method.__get__(my_instance, MyClass) - это протокол дескриптора. Это вернет новый метод, связанный с my_instance, который вы затем выполните с помощью оператора () после свойства.

Это поделит метод между всеми экземплярами MyClass, независимо от того, когда они были созданы. Однако они могли «скрыть» метод до того, как вы присвоили это свойство.

Если вы хотите, чтобы этот метод был только у определенных объектов, просто создайте связанный метод вручную:

my_instance.my_method = my_method.__get__(my_instance, MyClass)

Подробнее о дескрипторах (руководство) см. здесь .

6 голосов
/ 11 июля 2011

Вы можете прочитать полное объяснение здесь в ссылке на python, в разделе «Пользовательские методы».Более короткое и простое объяснение можно найти в описании учебника по питону объекты методов :

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

По сути, чтов вашем примере происходит следующее:

  • функция, назначенная классу (как это происходит, когда вы объявляете метод внутри тела класса), является ... методом.
    • При доступе к этому методу через класс, например.StrategyExample.execute вы получаете «несвязанный метод»: он не «знает», к какому экземпляру он «принадлежит», поэтому, если вы хотите использовать его в экземпляре, вам нужно будет предоставить экземпляр в качестве первого аргумента самостоятельно,например.StrategyExample.execute(s0)
    • При доступе к методу через экземпляр, например.self.execute или s0.execute, вы получаете «связанный метод»: он «знает», какому объекту «принадлежит», и будет вызван с экземпляром в качестве первого аргумента.
  • функция, которую вы непосредственно назначаете атрибуту экземпляра, как, например, в self.execute = strategy или даже s0.execute = strategy - это ... простая функция (в отличие от метода, она не проходит через класс)

Чтобы ваш пример работал одинаково в обоих случаях:

  • либо вы превращаете функцию в «настоящий» метод: вы можете сделать это с помощью types.MethodType:

    self.execute = types.MethodType(strategy, self, StrategyExample)
    

    (вы более или менее говорите классу, что когда execute запрашивается для этого конкретного экземпляра, он должен превратить strategy в связанный метод)

  • или - если вашей стратегии действительно не нужен доступ к экземпляру - вы идете наоборот и превращаете оригинальный метод execute в статический метод (снова превращая его в нормальную функцию: он не будет вызыватьсяс экземпляром в качестве первого аргумента, поэтому s0.execute() будет делать то же самое, что и StrategyExample.execute()):

    @staticmethod
    def execute(*args):
        print locals()
    
6 голосов
/ 11 июля 2011

Метод является оберткой для функции и вызывает функцию с экземпляром в качестве первого аргумента. Да, он содержит атрибут __self__ (также im_self в Python до 3.x), который отслеживает, к какому экземпляру он прикреплен. Однако добавление этого атрибута в обычную функцию не сделает его методом; вам нужно добавить обертку. Вот как (хотя вы можете использовать MethodType из модуля types для получения конструктора, а не type(some_obj.some_method).

Кстати, обернутая функция доступна через атрибут __func__ (или im_func) метода.

1 голос
/ 11 июля 2011

Когда вы делаете self.execute = strategy, вы устанавливаете атрибут в простой метод:

>>> s = StrategyExample()
>>> s.execute
<bound method StrategyExample.execute of <__main__.StrategyExample object at 0x1dbbb50>>
>>> s2 = StrategyExample(alt_strategy)
>>> s2.execute
<function alt_strategy at 0x1dc1848>

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

См .: Python: привязать несвязанный метод?

...