Присвоение функции атрибуту объекта - PullRequest
22 голосов
/ 25 июня 2011

Основываясь на моем понимании модели данных Python и, в частности, подраздела «Методы экземпляра», всякий раз, когда вы читаете атрибут, значение которого имеет тип «пользовательская функция», возникает некоторая магия, и выполучить связанный экземплярный метод вместо действительной, исходной функции.Эта магия заключается в том, что вы не передаете явно параметр self при вызове метода.

Но тогда я бы ожидал, что смогу заменить метод объекта функцией с такой же сигнатурой:

class Scriptable:
    def __init__(self, script = None):
        if script is not None:
            self.script = script   # replace the method
    def script(self):
        print("greetings from the default script")

>>> scriptable = Scriptable()
>>> scriptable.script()
greetings from the default script

>>> def my_script(self):
...     print("greetings from my custom script")
...
>>> scriptable = Scriptable(my_script)
>>> scriptable.script()
Traceback (most recent call last):
  ...
TypeError: script() takes exactly 1 positional argument (0 given)

Я создаю экземпляр Scriptable и устанавливаю его атрибут script для пользовательской функции с одним параметром, точно так же, как это определено в классе.Поэтому, когда я читаю атрибут scriptable.script, я ожидаю, что магия включится и даст мне метод привязанного экземпляра, который не принимает параметров (так же, как я получаю, когда я не заменил script).Вместо этого он, похоже, возвращает ту же самую функцию, которую я передал, параметр self и все.Магия привязки метода не происходит.

Почему магия привязки метода работает, когда я определяю метод внутри объявления класса, а не когда я присваиваю атрибут?Что заставляет Python по-разному относиться к этим ситуациям?

Я использую Python3, если это имеет какое-то значение.

Ответы [ 4 ]

16 голосов
/ 25 июня 2011

Вот как вы это делаете:

import types
class Scriptable:
    def __init__(self, script = None):
        if script is not None:
            self.script = types.MethodType(script, self)   # replace the method
    def script(self):
        print("greetings from the default script")

Как отметил в комментариях ba__friend, методы хранятся в объекте class. Дескриптор объекта класса возвращает функции как связанные методы при доступе к атрибуту из экземпляра.

Когда вы назначаете функцию для instance, ничего особенного не происходит, поэтому вы должны обернуть функцию самостоятельно.

9 голосов
/ 25 июня 2011

Благодаря ответу Алекса Мартелли есть еще одна версия:

class Scriptable:
    def script(self):
        print(self)
        print("greetings from the default script")

def another_script(self):
    print(self)
    print("greetings from the another script")

s = Scriptable()
s.script()

# monkey patching:
s.script = another_script.__get__(s, Scriptable)
s.script()
6 голосов
/ 25 июня 2011

Посмотрите на это:

>>> scriptable = Scriptable()
>>> scriptable.script
<bound method Scriptable.script of <__main__.Scriptable instance at 0x01209DA0>>
>>> scriptable = Scriptable(my_script)
>>> scriptable.script
<function my_script at 0x00CF9730>

Оператор self.script = script создает только атрибут объекта класса без какой-либо «магии» с ним.

Оператор def script(self): внутри определения класса создает дескриптор - специальный объект, который фактически управляет всеми вещами с параметром self.

Подробнее о дескрипторах в Python можно прочитать в упомянутой ссылке на модель данных: реализационные дескрипторы .

Еще одна замечательная статья о дескрипторах в Python от Raymond Hettinger: Руководство по дескрипторам .

1 голос
/ 25 июня 2011

Я не могу ответить на ваш вопрос , почему это работает так, вам придется спросить Гвидо ван Россума, но я могу дать вам возможный обходной путь:

class Scriptable:
    def __init__(self, script = None):
        self._script = script # replace the method
    def script(self):
        if self._script: return self._script(self)
        return self._defaultscript()
    def _defaultscript(self):
        print("greetings from the default script")
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...