#python #decorator #python-nonlocal
#python #декоратор #python-нелокальный
Вопрос:
Я знаю, что Python поддерживает объектно-ориентированную структуру, в которой используется точечная нотация. Однако меня смущает приведенный ниже код, где точечная нотация появляется в определении функции без определенного класса. Это какая-то функция, определенная как атрибуты функции [я думаю] в Python?
def count(f):
def counted(*args):
counted.call_count = 1
return f(*args)
counted.call_count = 0
return counted
Второй вопрос: есть ли альтернатива, что приведенный выше код может быть переписан с использованием нелокального оператора вместо точечной нотации для записи call_count ?
Комментарии:
1. ДА. Функции также являются объектами, которые могут иметь атрибуты
2. Атрибуты функции были добавлены с помощью PEP 232 и функционируют так же, как и любой класс. Чтобы использовать нелокальную, просто создайте
call_count
переменную и поместитеnonlocal call_count
ее в началоcounted
функции.3. Круто, это полезно, спасибо вам обоим!
Ответ №1:
Замыкание было бы более надежным, чем атрибут функции. Вы могли бы, предположительно, привязаться counted
к чему-то другому, и тогда counted.call_count
это больше не будет тем атрибутом, который вы хотите.
def count(f):
call_count = 0
def counted(*args):
nonlocal call_count
call_count = 1
return f(*args)
return counted
При каждом count
вызове создается новая переменная call_count
. После count
возврата единственная ссылка на эту переменную находится внутри тела counted
, а не (легко) видна для всего, на что есть ссылка counted
.
Комментарии:
1. У меня возникли проблемы с получением информации call_count после вызова count(f) . Могу ли я знать, как получить call_count после выполнения?
Ответ №2:
Если вам, как вызывающему, не нужен прямой доступ к call_count
(потому что, скажем, он использовался только внутри декоратора), то, что показывает @chepner, было бы правильным решением.
Проблема в том, call_count
что он недоступен извне. Некоторые параметры:
- Сохраните его как атрибут функции.
- Однако это не очень хорошая идея, поскольку везде используется только один объект функции. Если бы вы хотели независимо подсчитывать вызовы одной и той же функции в двух разных местах, у вас возникли бы проблемы.
- Pass — это изменяемый контейнер, который видоизменяется декоратором:
def count(count_container): count_container[0] = 0 def inner(f): def counted(*args): count_container[0] = 1 return f(*args) return counted return inner l = [0] @count(l) def f(): print("Test") >>> l [0] >>> f() Test >>> f() Test >>> l [2]
Конечно, недостатком этого является, eww. Даже если вы замените список специализированным объектом, таким как пользовательский
dataclass
, это все равно будет плохо. - Откажитесь от идеи использовать его в качестве декоратора с использованием
@
нотации. Это дает вам еще несколько вариантов.- Вы можете сохранить свой текущий код и передать функцию вручную:
def count(f): def counted(*args): counted.call_count = 1 return f(*args) counted.call_count = 0 return counted >>> counted = count(f) >>> counted() Test >>> counted() Test >>> counted.call_count 2
Это намного лучше, потому что каждый явный вызов
count
возвращает новую функцию вместо того, чтобы одной глобальной функции присваивался атрибут, как раньше. Если вы хотите отслеживать два отдельных экземпляра вызовов одновременно, вам просто нужно вызватьcount
дважды, а затем удерживать каждую возвращенную функцию. - Вы также можете вернуть средство получения. Использование модифицированной версии кода @chepner:
def count(f): call_count = 0 def counted(*args): nonlocal call_count call_count = 1 return f(*args) return counted, lambda: call_count >>> counted, counter_getter = count(f) >>> counted() Test >>> counted() Test >>> counter_getter() 2
Основным преимуществом здесь является то, что он предоставляет вызывающему доступ к
call_count
тому, когда они хотят его прочитать, но не дает им возможности его изменять.
- Вы можете сохранить свой текущий код и передать функцию вручную:
Извините, если я где-то сделал какой-то отступ. Невероятно сложно пытаться форматировать код внутри вложенного списка точек.
Комментарии:
1. Я попробовал два разных способа в вашем пункте 3, и оба они сработали. Нелокальный с кортежем из двух функций в качестве возврата выглядит забавно, и это решило мою проблему. У меня нет никаких проблем с чтением вашего кода. Большое спасибо!