2016-07-23 8 views
5

Мне кажется, что я не могу использовать обезьянный патч класса __call__ (и да, я хочу исправлять только отдельные экземпляры, но не все).Как обезглавить патч `__call__`?

Следующий код:

class A(object): 
    def test(self): 
     return "TEST" 

    def __call__(self): 
     return "EXAMPLE" 

a = A() 
print("call method: {0}".format(a.__call__)) 
print("test method: {0}".format(a.test)) 
a.__call__ = lambda : "example" 
a.test = lambda : "test" 
print("call method: {0}".format(a.__call__)) 
print("test method: {0}".format(a.test)) 

print(a()) 
print("Explicit call: {0}".format(a.__call__())) 
print(a.test()) 

Выходы это:

call method: <bound method A.__call__ of <__main__.A object at 0x7f3f2d60b6a0>> 
test method: <bound method A.test of <__main__.A object at 0x7f3f2d60b6a0>> 
call method: <function <lambda> at 0x7f3f2ef4ef28> 
test method: <function <lambda> at 0x7f3f2d5f8f28> 
EXAMPLE 
Explicit call: example 
test 

В то время как я хотел бы его к выходу:

... 
example 
Explicit call: example 
test 

Как monkeypatch __call__()? Почему я не могу исправить его так же, как я исправляю другие методы?

Хотя this answer рассказывает, как это сделать (предположительно, я не проверял еще), это не объясняет, почему часть вопроса.

+0

Не могли бы вы сделать разницу в 2-символьный между выходами немного больше очевидно? Я смотрел в течение трех минут, пытаясь разглядеть разницу, но мозг большинства людей делает автокоррекцию небольшими ошибками. – cat

+1

@cat: конечно, сделано –

+0

[Специальный поиск метода] (https://docs.python.org/3/reference/datamodel.html#special-lookup) – GingerPlusPlus

ответ

4

Так что, как J.J. Hakala прокомментировал то, что Python действительно, является вызов:

type(a).__call__(a) 

как таковой, если я хочу переопределить метод __call__, I должен переопределить класс __call__, но если я не хочу чтобы повлиять на поведение других экземпляров того же класса, мне нужно создать новый класс с помощью метода override __call__.

Так пример того, как переопределить __call__ будет выглядеть следующим образом:

class A(object): 
    def test(self): 
     return "TEST" 

    def __call__(self): 
     return "EXAMPLE" 

def patch_call(instance, func): 
    class _(type(instance)): 
     def __call__(self, *arg, **kwarg): 
      return func(*arg, **kwarg) 
    instance.__class__ = _ 

a = A() 
print("call method: {0}".format(a.__call__)) 
print("test method: {0}".format(a.test)) 
patch_call(a, lambda : "example") 
a.test = lambda : "test" 
print("call method: {0}".format(a.__call__)) 
print("test method: {0}".format(a.test)) 

print("{0}".format(a())) 
print("Explicit a.__call__: {0}".format(a.__call__())) 
print("{0}".format(a.test())) 

print("Check instance of a: {0}".format(isinstance(a, A))) 

Запуск производит следующий вывод:

call method: <bound method A.__call__ of <__main__.A object at 0x7f404217a5f8>> 
test method: <bound method A.test of <__main__.A object at 0x7f404217a5f8>> 
call method: <bound method patch_call.<locals>._.__call__ of <__main__.patch_call.<locals>._ object at 0x7f404217a5f8>> 
test method: <function <lambda> at 0x7f404216d048> 
example 
Explicit a.__call__: example 
test 
Check instance of a: True 
3

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

>>> class C: 
...  pass 
... 
>>> c = C() 
>>> c.__len__ = lambda: 5 
>>> len(c) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: object of type 'C' has no len() 

Источник: https://docs.python.org/3/reference/datamodel.html#special-lookup

+0

, поэтому для переопределения '__call__' мне нужно изменить * тип * экземпляра ? Есть ли способ сделать 'isinstance (a, A) == True' даже после того, как' __call__' переопределен? –

+0

Да, вы можете подклассифицировать 'A', например' class B (A): def __call __ (self): return example'. Затем вы можете просто создать экземпляр 'B' вместо' A', или, если вы достаточно храбры, 'a .__ class__ = B'. – GingerPlusPlus

+0

Хм, возможно, работает 'a .__ class__ = B' (мне нужно переопределить __call__ только на * some * не все экземпляры базового класса), позвольте мне проверить. –

 Смежные вопросы

  • Нет связанных вопросов^_^