этот омниметод очень умный. Он использует некоторые очень тонкие уловки, чтобы сделать свою работу. Давайте начнем с самого начала.
Вы, наверное, уже знаете, что синтаксис декоратора просто сахар для применения функции, то есть:
@somedecorator
def somefunc(...):
pass
# is the same thing as
def somefunc(...):
pass
somefunc = somedecorator(somefunc)
так что somefunc
на самом деле является omnimethod
экземпляром, а не функцией, которая была определена. Интересно, что omnimethod
также реализует интерфейс descriptor
. Если атрибут класса определяет метод __get__
, то всякий раз, когда упоминается этот атрибут, интерпретатор вместо этого вызывает __get__
для этого объект, и возвращает это вместо возврата самого атрибута.
метод __get__
всегда вызывается с экземпляром в качестве первого аргумента и классом этого экземпляра в качестве второго аргумента. Если атрибут действительно был найден в самом классе, то экземпляр будет None
.
Последний бит хитрости - functools.partial
, который является питонским способом функции curry . когда вы используете partial
, вы передаете ему функцию и некоторые аргументы, и она возвращает новую функцию, которая при вызове будет вызывать оригинальную функцию с оригинальными аргументами в дополнение к любым аргументам, которые вы передали позже. omnimethod
использует эту технику для заполнения параметра self
для функции, которую он переносит.
Вот как это выглядит. обычный метод может быть вызван, когда вы читаете его из экземпляра, но вы не можете использовать его из самого класса. вы получаете несвязанную ошибку TypeError
>>> class Foo(object):
... def bar(self, baz):
... print self, baz
...
>>> f = Foo()
>>> f.bar('apples')
<__main__.Foo object at 0x7fe81ab52f90> apples
>>> Foo.bar('quux')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method bar() must be called with
Foo instance as first argument (got str instance instead)
>>> Foo.bar(None, 'quux')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unbound method bar() must be called with
Foo instance as first argument (got NoneType instance instead)
>>>
Python предоставляет декоратор bultin classmethod
(а также staticmethod
, но это не важно), который позволит вам использовать его на уровне класса, но он никогда не увидит экземпляр. всегда получает класс в качестве первого аргумента.
>>> class Foo(object):
... @classmethod
... def bar(cls, baz):
... print cls, baz
...
>>> f = Foo()
>>> Foo.bar('abc')
<class '__main__.Foo'> abc
>>> f.bar('def')
<class '__main__.Foo'> def
>>>
В силу своей хитрости omnimethod
дает вам немного того и другого.
>>> class Foo(object):
... @omnimethod
... def bar(self, baz):
... print self, baz
...
>>> f = Foo()
>>> Foo.bar('bananas')
None bananas
>>> f.bar('apples')
<__main__.Foo object at 0x7fe81ab52f90> apples
>>>