Почему обертка и обернутая функция одинаковы для некоторых кодов Python. - PullRequest
0 голосов
/ 03 мая 2018

Я читал исходный код Яна Гудфеллоу в Github (ссылка https://github.com/goodfeli/adversarial/blob/master/deconv.py). В частности, в строке 40/41 код:

@functools.wraps(Model.get_lr_scalers)
def get_lr_scalers(self):

Это довольно незнакомый способ использования wraps, и, похоже, цель состоит в том, чтобы заменить get_lr_scalers пользовательской функцией. Но в этом случае нам не нужна обертка для этого, верно? Я не знаю цели wraps в этом случае.

Ответы [ 2 ]

0 голосов
/ 03 мая 2018

Цель @wraps - скопировать метаинформацию одной функции в другую функцию. Обычно это делается при замене исходной функции ее переносом, что часто делается декораторами.

Но в общем случае вот что он делает в примере:

def f1():
    """Function named f1. Prints 'f1'."""
    print('f1')

@functools.wraps(f1)
def f2():
    print('f2')

Теперь вы можете проверить, что произошло:

>>> f1
<function f1 at 0x006AD8E8>
>>> f2
<function f1 at 0x006AD978>
>>> f1()
f1
>>> f2()
f2
>>> f1.__doc__
"Function named f1. Prints 'f1'."
>>> f2.__doc__
"Function named f1. Prints 'f1'."

Когда вы вызываете f2, очевидно, что это на самом деле f2, но когда вы проверяете его, он ведет себя как f1 - у него та же строка документа и то же имя.

Для чего это хорошо? Для этого:

f1 = f2

Теперь оригинальный f1 заменен на новую функциональность, но он по-прежнему выглядит как f1 снаружи.

Обычно это делается в декораторе:

def replace(func):
    @functools.wraps(func)
    def replacement():
        print('replacement')
    return replacement

@replace
def f1():
    """Function named f1. Prints 'f1'."""
    print('f1')

И он ведет себя так:

>>> f1()
replacement
>>> f1
<function f1 at 0x006AD930>
>>> f1.__name__
'f1'
>>> f1.__doc__
"Function named f1. Prints 'f1'."
0 голосов
/ 03 мая 2018

wraps копирует ряд атрибутов из другой функции в эту функцию - по умолчанию __module__, __name__, __qualname__, __annotations__ и __doc__.

Наиболее очевидно полезный для копирования это __doc__. Рассмотрим этот более простой пример: 1

class Base:
    def spam(self, breakfast):
        """spam(self, breakfast) -> breakfast with added spam

        <29 lines of detailed information here>
        """

class Child:
    @functools.wraps(Base.spam)
    def spam(self, breakfast):
        newbreakfast = breakfast.copy()
        newbreakfast.meats['spam'] + 30
        return newbreakfast

Теперь, если кто-то захочет использовать help(mychild.spam), он получит 29 строк полезной информации. (Или, если они автоматически заполняют mychild.spam в PyCharm, всплывет оверлей с документацией и т. Д.) Все без того, чтобы мне пришлось вручную копировать и вставлять его. И, что еще лучше, если Base получен из какой-то среды, которую я не писал, и мой пользователь обновит версию 1.2.3 до 1.2.4 этой платформы, и появится лучшая строка документации, они увидят эту лучшую строку документации.


В наиболее распространенном случае Child будет подклассом Base, а spam будет переопределением. 2 Но это на самом деле не требуется - wraps не делает не волнует, используете ли вы подтип с помощью наследования, или типизацию утили, просто реализуя неявный протокол; это одинаково полезно для обоих случаев. Поскольку Child предназначен для реализации протокола spam из Base, имеет смысл для Child.spam иметь ту же строку документации (и, возможно, другие атрибуты метаданных).


Другие атрибуты, вероятно, не так полезны, как строки документов. Например, если вы используете аннотации типов, их полезность в чтении кода, вероятно, по меньшей мере столь же высока, как и возможность запускать Mypy для статической проверки типов, поэтому простое их динамическое копирование из другого метода часто не все это полезно. И __module__ и __qualname__ в основном используются для размышлений / осмотров, и в этом случае, скорее всего, будут вводить в заблуждение, чем полезны (хотя вы, вероятно, могли бы придумать пример структуры, где вы бы хотели, чтобы люди читали код в Base вместо кода в Child, что не так для очевидного примера по умолчанию). Но, если они не являются активно вредными, стоимость читабельности использования @functools.wraps(Base.spam, assigned=('__doc__',)) вместо просто значений по умолчанию может не стоить этого.


1. Если вы используете Python 2, измените эти классы для наследования от object; в противном случае это будут классы старого стиля, которые просто усложняют ситуацию. В Python 3 классы старого стиля отсутствуют, поэтому эта проблема даже не возникает.

2. Или, может быть, «виртуальный подкласс» ABC, объявленный с помощью вызова register или через ловушку подкласса.

...