В чем разница между вызовом декоратора класса для экземпляра класса и для определения класса?
class Deco:
def __init__(self, name):
self.name = name
def __call__(self, test_class):
def inner_func(whatisit):
return whatisit
test_class.method = inner_func
return test_class
class TestClass:
def __init__(self, name):
self.name = name
@Deco('deco')
class TestClassWrapped:
def __init__(self, name):
self.name = name
test = TestClass('test')
test = Deco('deco')(test)
test_wrapped = TestClassWrapped('test')
print(test.method('whatisit')) >> whatisist
print(test_wrapped == test_wrapped.method()) >> True
Почему делать и возвращать разные результаты?
Кажется, что первый аргумент в
test_wrapped.method
является
self
, пока это не для
test.method
. Почему он отличается от одного к другому?
2 ответа
Разница не в том, как работает украшение, а в том, как работает вызов. Когда экземпляр вызывает метод, определенный в его классе, как
test_wrapped
делает, он всегда проходит как первый аргумент. Между тем, когда объект вызывает атрибут самого себя, который оказывается функцией, но не существует в его классе, он вызывает его без передачи . Рассмотрим этот простой класс:
class LoudTalker:
def __init__(self, name):
self.shout_hello = lambda: print("HELLO, I'M {}".format(name.upper()))
>>> john = LoudTalker("John")
>>> LoudTalker.shout_hello()
HELLO, I'M JOHN
Обратите внимание, что
john
не прошел
self
to (что вызвало бы ошибку
<lambda>() takes 0 positional arguments but 1 was given
) потому что
shout_hello
был определен непосредственно в экземпляре, а не в классе.
Пошаговое изучение вашего кода:
Вы создаете обычный файл с именем .
Вы вручную звоните
Deco
и снабдить его строкойtest = Deco('deco')(test)
.Это заставляет ваш код проходить через функцию, которая изменяет переданный класс, чтобы установить его атрибут для вложенной функции. Затем он возвращает его, и теперь он содержит успешно измененный : вызов будет успешно возвращен. Важно отметить, что здесь вы НЕ получаете доступ к методу: вы получаете доступ к ФУНКЦИИ через АТРИБУТ. передается каждому методу классов в Python, но поскольку это не метод, здесь он не играет роли. Попробуйте распечатать
type(test.method)
, вот увидишь<class 'function'>
и не<class 'method'>
. Важно отметить, что вы прошли ЭКЗЕМПЛЯРTestClass
, а не само определение класса: и только этот экземпляр с именемtest
был установлен его атрибут.Затем вы создаете именованный
test_wrapped
. При его создании он входит в__call__
еще раз, передав его какtest_class
параметр. Важно отметить, что вы передали ОПРЕДЕЛЕНИЕ , а не его экземпляр. Параметрmethod
здесь изменит его для каждого экземпляраTestWrappedClass
вы позже создадите, и к ним можно будет получить доступ даже без создания экземпляра чего-либо. Попробуйте позвонить: он будет печатать без создания экземпляраTestClassWrapped
вообще. Интересно, что когда он установлен таким образом, он устанавливается не как атрибут, а как метод! Попробуйте распечататьtype(test_wrapped.method)
. Это то, что я считаю источником путаницы.В случае
print(test_wrapped.method())
, вы должны помнить, что каждый метод созданных классов принимает в качестве своего первого параметра. Это означает, что вернется: поэтому, почемуtest_wrapped == test_wrapped.method()
. Обратите внимание, что это не относится к методам, вызываемым из определения класса, как я показал ранее.TestClassWrapped.method("abc")
ДОЛЖЕН принимать какой-либо параметр (например,abc
), иначе он будет жаловаться на отсутствие аргумента.
Так вот почему
test.method('whatisit')
возвращается
'whatisit'
и не принимает в качестве параметра, и почему
test_wrapped.method()
возвращается
self
.