0

我知道 Python 支持面向对象的结构,它使用点表示法。但是,我对下面的代码感到困惑,其中点符号出现在没有定义类的函数定义中。那是在 Python 中定义为函数属性 [我猜] 的某些特性吗?

def count(f):
    def counted(*args):
        counted.call_count += 1
            return f(*args)
        counted.call_count = 0
        return counted

第二个问题:是否有替代方法可以使用 nonlocal 语句而不是点表示法来重写上面的代码来记录 call_count?

4

2 回答 2

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

于 2020-11-21T18:24:26.327 回答
0

如果您作为调用者不需要直接访问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他们想要阅读的时间,它不给他们修改它的能力。


对不起,如果我在某个地方做了一些缩进。尝试在嵌套点列表中格式化代码非常令人沮丧。

于 2020-11-21T20:41:33.637 回答