Почему functools.update_wrapper обновляет __dict__ в объекте оболочки? - PullRequest
0 голосов
/ 04 декабря 2018

Я столкнулся со странным поведением functools.update_wrapper: оно перезаписывает __dict__ объекта-оболочки на объект обернутого объекта - что может затруднить его использование при вложении декораторов.

В качестве простого примера предположим, что мы пишем класс декоратора, который кэширует данные в памяти, и другой класс декоратора, который кэширует данные в файл.Следующий пример демонстрирует это (я сделал пример кратким и пропустил всю логику кэширования, но я надеюсь, что он демонстрирует вопрос):

import functools

class cached:
    cache_type = 'memory'
    def __init__(self, fcn):
        super().__init__()
        self.fcn = fcn
        functools.update_wrapper(self, fcn, updated=())

    def __call__(self, *args):
        print("Retrieving from", type(self).cache_type)
        return self.fcn(*args)

class diskcached(cached):
    cache_type = 'disk'

@cached
@diskcached
def expensive_function(what):
    print("expensive_function working on", what)

expensive_function("Expensive Calculation")

Этот пример работает как задумано - его вывод равен

Retrieving from memory
Retrieving from disk
expensive_function working on Expensive Calculation

Однако это заняло у меня много времени - сначала я не включил аргумент updated = () в вызов functools.update_wrapper.Но если это не учитывать, то вложение декораторов не работает - в этом случае вывод будет

Retrieving from memory
expensive_function working on Expensive Calculation

Т.е. внешний декоратор напрямую вызывает самую внутреннюю упакованную функцию.Причина (которая заняла у меня некоторое время, чтобы понять) заключается в том, что functools.update_wrapper обновляет атрибут __dict__ обертки до атрибута __dict__ обернутого аргумента, который закорачивает внутренний декоратор, если только один не добавляетupdated=() аргумент.

Мой вопрос: это поведение предназначено и почему?(Python 3.7.1)

1 Ответ

0 голосов
/ 04 декабря 2018

Создание функции-обертки, похожей на функцию, которую она оборачивает, - точка update_wrapper, которая включает в себя __dict__ записей.Он не заменяет __dict__;он вызывает update.

Если update_wrapper этого не сделал, то если один декоратор установил атрибуты для функции, а другой декоратор обернул модифицированную функцию:

@decorator_with_update_wrapper
@decorator_that_sets_attributes
def f(...):
    ...

оболочкойфункция не имеет установленных атрибутов, что делает ее несовместимой с кодом, который ищет эти атрибуты.

...