Проблема с методами обезьяны и ссылками - PullRequest
2 голосов
/ 26 августа 2011

Мне было интересно, может ли кто-нибудь объяснить и предложить решение этой проблемы:

$ cat object-override-methods.py 
class A:
    def foo(self):
        return 1

class B:
    def foo(self):
        return 1

for klass in A, B:
    orig_foo = klass.foo
    def foo(self):
        return orig_foo(self) * 2
    klass.foo = foo

A().foo()
B().foo()
$ python object-override-methods.py
Traceback (most recent call last):
  File "object-override-methods.py", line 15, in <module>
    A().foo()
  File "object-override-methods.py", line 12, in foo
    return orig_foo(self) * 2
TypeError: unbound method foo() must be called with B instance as first argument (got A instance instead)

Заранее спасибо.

Ответы [ 2 ]

2 голосов
/ 26 августа 2011

orig_foo - это глобальная переменная, которая меняет значение при каждом прохождении цикла. После завершения цикла orig_foo относится к B.foo.

Внутренние функции foo (один или каждый проход по циклу) обе используют глобальное значение для orig_foo при их вызове. Таким образом, они оба называют B.foo(self).

При вызове «несвязанного метода», такого как orig_foo, Python2 проверяет, является ли первый аргумент экземпляром соответствующего класса. A().foo() не проходит эту проверку. (Интересно, что эта проверка была удалена в Python3, поэтому не возникнет TypeError, и эту ошибку может стать труднее найти).

Чтобы это исправить, вы должны привязать значение orig_foo к соответствующему klass. Вы можете сделать это, сделав orig_foo локальной переменной foo. Один из способов сделать это - сделать orig_foo аргументом foo со значением по умолчанию. Python связывает значения по умолчанию во время определения функции. Так orig_foo=orig_foo связывает локальную переменную orig_foo с текущим значением klass.foo:

for klass in A, B:
    orig_foo = klass.foo
    def foo(self, orig_foo=orig_foo):
        return orig_foo(self) * 2
    klass.foo = foo
2 голосов
/ 26 августа 2011

Поскольку orig_foo определено в глобальной области видимости, вы попираете его значение каждый раз вокруг цикла.Это вытесненное значение затем используется всеми вашими новыми foo методами.

Простое исправление состоит в том, чтобы переместить код в функцию, например:

def rebind_foo(klass):
    orig_foo = klass.foo
    def foo(self):
        return orig_foo(self) * 2
    klass.foo = foo

for klass in A, B:
   rebind_foo(klass)

, которая гарантирует, что каждыйНовый метод foo получает свое значение orig_foo.

...