【问题标题】:Is this Python "static variable" hack ok to use? [closed]这个 Python “静态变量” hack 可以使用吗? [关闭]
【发布时间】:2016-04-01 04:00:41
【问题描述】:

一个经常被问到的问题是 Python 中的函数内部是否存在与静态变量等价的变量。答案有很多,比如创建包装类、使用嵌套函数、装饰器等。

我发现的最优雅的解决方案之一是this,我对其稍作修改:

def foo():
    # see if foo.counter already exists
    try: test = foo.counter
    # if not, initialize it to whatever
    except AttributeError: foo.counter = 0

    # do stuff with foo.counter
    .....
    .....

例子:

static.py

def foo(x):
    # see if foo.counter already exists
    try: test = foo.counter
    # if not, initialize it to whatever
    except AttributeError: foo.counter = 0

    foo.counter += x

    print(foo.counter)

for i in range(10):
    foo(i)

输出

$ python static.py
0
1
3
6
10
15
21
28
36
45

有什么理由我应该避免这种方法吗?无论如何,它到底是如何工作的?

【问题讨论】:

  • 只是好奇,你为什么要这样做?这不就是为什么 python 有生成器吗?
  • 我看到的大多数生成器示例都是用于简单的递增过程。此外,它们引入了另一个级别的缩进。我不是想yeild 一个数字,我只是希望能够返回该函数并存储我的最后一个值。就我而言,它是传入的一些东西的最大值。随之而来的是各种操作。
  • 您链接到的页面上的装饰器解决方案有什么问题?无论如何,您链接到的页面上的所有解决方案看起来都很好。
  • 我看不出任何客观邪恶的东西。
  • IDK。上课似乎有点矫枉过正。创建一个类,以便我实例化一个对象以执行其他语言中需要一行的事情?为什么它会在多线程环境中失败?

标签: python static


【解决方案1】:

这是如何工作的?

有效,因为函数的名称只是本地范围内的另一个条目,并且该函数与 Python 中的其他所有内容一样是一个对象,并且可以在其上设置任意属性:

def foo():
    # The foo function object has already been fully constructed
    # by the time we get into our `try`
    try: test = foo.counter  # Only run when foo is invoked
    except AttributeError: foo.counter = 0

    foo.counter += 1

if hasattr(foo, 'counter'):
    print('Foo has a counter attribute')
else:
    # This prints out - we've parsed `foo` but not executed it yet
    print('Foo.counter does not exist yet')

# Now, we invoke foo
foo()

if hasattr(foo, 'counter'):
    # And from now on (until we remove the attribute)
    # this test will always succeed because we've added the counter
    # attribute to the function foo.
    print('Foo has a counter attribute')
else:
    print('Foo.counter does not exist yet')  # No longer true

【讨论】:

  • 感谢您的解释。而且,我想你已经回答了我的问题。函数只是一个对象,我所做的只是添加一个属性。令我感到奇怪的是,我正在将属性 inside 添加到函数本身。我不会猜到foofooinside 范围内。
  • @abalter:它必须在范围内。否则递归将不起作用。
  • def foo 在模块范围内定义名称foo。模块中的所有内容都在函数 foo 的范围内,包括名称 foo
【解决方案2】:

为什么不这样:

def foo(x):
    foo.counter += x
    print(foo.counter)

foo.counter = 0 # init on module import

然后:

for i in range(10):
    foo(i)

我得到与 py2.7、py3.4 相同的输出。

【讨论】:

  • 该解决方案也在其他地方给出,并且大致等效。我只是认为将初始化保留在函数内部更安全、更整洁。更安全的可重复使用。只是个人意见。问题是:有什么理由不应该使用这种类型的解决方案?问题是在foo 内向foo 添加一个属性。这让我觉得很奇怪,我什至不明白到底发生了什么。
  • 应该对其进行分析,try 块可能会降低性能。
【解决方案3】:

您拥有的解决方案运行良好,但如果您追求最优雅的解决方案,您可能更喜欢这个(改编自您链接到的答案之一):

def foo(x):
    foo.counter = getattr(foo, 'counter', 0) + x
    print(foo.counter)

for i in range(10):
    foo(i)

它的工作原理基本相同,但getattr 返回一个默认值(0),仅当foo 没有counter 属性时才适用。

【讨论】:

  • 有趣。视觉上更干净。通过try/catch 调用getattr 方法是否会增加开销?有人指出这是不使用同样简单的if/then 的原因。
  • @abalter:差别很小,不是很值得注意。属性存在时使用try/catch 稍快,不存在时使用getattr() 稍快。
  • 我猜在实践中,try/catch 会获胜,因为如果您实际上多次使用该函数,该属性就会存在。但对于大多数应用程序来说,“稍微”可能不是问题。
  • 是的,但我认为你有点想多了 ;)
【解决方案4】:

在 python 中,使用生成器函数可能会更好。

  1. 它支持多个同时作用域(每个生成器实例都可以拥有自己的 foo.counter 实例)。
  2. “静态”变量被正确封装在函数的范围内(foo.counter 实际上是在外部范围(文件级范围)中)。

这是一个使用两个同时生成器的示例,每个生成器都有自己的 counter 变量版本(不能使用“静态”变量)。

def foo():
    counter = 0
    while True:
        # You can yield None here if you don't want any value.
        yield counter
        counter += 1

gen1 = foo()
gen2 = foo()
gen1.next()
# 0
gen1.next()
# 1
gen2.next()
# 0

您可以为生成器提供一些初始值,也可以将数据发送回生成器。

def foo(x=0)
    counter = x
    val = 1
    while True:
        sent = (yield counter)
        if sent is None:
            counter += val
            val = 1
        else:
            val = sent

gen1 = foo(3)
gen1.next()
# 3
gen1.send(3)
gen1.next()
# 6
gen1.next()
# 7

您可以做的不仅仅是简单地迭代一个计数器。生成器是 Python 中的一个强大工具,比简单的“静态”变量灵活得多。

【讨论】:

    【解决方案5】:

    我觉得一个对象正是您在这里寻找的。它是一些附加到使用和操纵该状态的操作(在本例中为一个操作)的状态。那为什么不呢:

    class Foo(object):
        def __init__(self, start=0):
            self.counter = start
        def foo(self, x):
            self.counter += x
            print(self.counter)
    foo = Foo()
    for i in range(10):
        foo.foo(i)
    

    正如其他人所说,如果你真的想避免上课,你可以。函数已经是一个对象,并且可以添加任何属性,就像任何普通对象一样。但你为什么真的想要那个?我知道为单个函数编写一个类感觉有点矫枉过正,但您已经说过您的实际代码具有随之而来的各种操作。在没有看到各种操作等的情况下,您似乎有合理的理由在这里使用类。

    【讨论】:

      【解决方案6】:

      您可能会遇到一些问题,如果在函数中找不到 foo.counter,测试将在函数范围之外查找。例如以下返回 101 而不是 1

      class Bar():
          counter = 100
      
      
      class Hoo():
          def foo(x):
          # see if foo.counter already exists
              try: test = foo.counter
          # if not, initialize it to whatever
              except AttributeError: foo.counter = 0
              foo.counter += x
              print(foo.counter)
      # make an object called foo that has an attribute counter    
      foo = Bar()
      # call the static foo function    
      Hoo.foo(1)
      

      【讨论】:

      • 很奇怪。当我不那么累的时候,我需要考虑一下。在可以破坏属性和方法的后期绑定语言中,这种事情不是总是有可能吗?
      • 有趣的想法,但你真的运行过它吗(如果是的话,是哪个python)当我按照python2中的方式运行时,我得到TypeError:unbound method foo() must be called with Hoo instance as first论点(改为获得 int 实例)。改变它,所以我们有一个外部函数(隐藏 foo 这样做 foo = Bar() 不会覆盖) wrap_foo 它返回内部函数并调用它,我得到 1。python 会尝试对我来说没有意义反复查找 foo.counter
      猜你喜欢
      • 2010-09-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-19
      • 2021-05-02
      • 2011-01-21
      • 1970-01-01
      相关资源
      最近更新 更多