точечная нотация в определении функции Python

#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 что он недоступен извне. Некоторые параметры:

  1. Сохраните его как атрибут функции.
    • Однако это не очень хорошая идея, поскольку везде используется только один объект функции. Если бы вы хотели независимо подсчитывать вызовы одной и той же функции в двух разных местах, у вас возникли бы проблемы.
  2. 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 , это все равно будет плохо.

  3. Откажитесь от идеи использовать его в качестве декоратора с использованием @ нотации. Это дает вам еще несколько вариантов.
    • Вы можете сохранить свой текущий код и передать функцию вручную:
        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, и оба они сработали. Нелокальный с кортежем из двух функций в качестве возврата выглядит забавно, и это решило мою проблему. У меня нет никаких проблем с чтением вашего кода. Большое спасибо!