【问题标题】:How to maintain state in Python without classes?如何在没有类的情况下在 Python 中维护状态?
【发布时间】:2012-08-05 16:12:25
【问题描述】:

是否有pythonic 方法来维护状态(例如,出于优化目的)而不完全面向对象?

为了更好地说明我的问题,这里有一个我在 JavaScript 中经常使用的模式示例:

var someFunc = (function () {
    var foo = some_expensive_initialization_operation();
    return someFunc (bar) {
        // do something with foo and bar
    }
}());

在外部,这只是一个和其他函数一样的函数,不需要初始化对象或类似的东西,但是闭包允许计算一次值,然后我基本上将其用作常量。

Python 中的一个例子是优化正则表达式时 - 使用 re.compile 并存储 matchsearch 操作的编译版本很有用。

我知道在 Python 中执行此操作的唯一方法是在模块范围内设置一个变量:

compiled_regex = compile_my_regex()

def try_match(m): # In reality I wouldn't wrap it as pointlessly as this
    return compiled_regex.match(m)

或者通过创建一个类:

class MatcherContainer(object):
    def __init__(self):
        self.compiled_regex = compile_my_regex()
    def try_match(self, m):
        self.compiled_regex.match(m)

my_matcher = MatcherContainer()

前一种方法是临时的,函数和上面声明的变量之间的关联不是很清楚。它还稍微污染了模块的命名空间,我对此不太满意。

后一种方法看起来很冗长,而且在样板上有点沉重。

我能想到的解决这个问题的唯一另一种方法是将任何类似这样的函数分解到单独的文件(模块)中,然后只导入函数,这样一切都很干净。

更有经验的 Python 专家对如何处理这个问题有何建议?或者你只是不担心它并继续解决问题?

【问题讨论】:

  • 我个人认为您的课程创建没有问题。冗长是好的。 "Explicit is better than implicit."
  • 至少使该类的使用不那么冗长的一种方法是将try_match重命名为__call__,这样您就可以(在构造之后)完全像使用函数一样使用它。但是,正如@glglgl 的回答一样,您的 javascript 代码实际上直接转换为 Python。
  • 在您的第一个示例中调用 someFunc 是否正确?还是应该是函数定义?
  • 调用是正确的 - 它执行外部函数,返回内部函数,这就是在外部范围内分配给 someFunc 的内容。
  • @Cerales 抱歉,我仍在努力解决它。 return someFunc (bar) {} 中的括号不是虚假的吗?另外如果someFunc(bar) 是实际调用,会不会导致堆栈溢出?

标签: python closures state


【解决方案1】:

怎么样

def create_matcher(re):
    compiled_regex = compile_my_regex()
    def try_match(m):
        return compiled_regex.match(m)
    return try_match

matcher = create_matcher(r'(.*)-(.*)')
print matcher("1-2")

?

但在大多数情况下,类更好更干净。

【讨论】:

    【解决方案2】:

    您也可以使用默认参数来完成此操作:

    def try_match(m, re_match=re.compile(r'sldkjlsdjf').match):
        return re_match(m)
    

    因为默认参数只在模块导入时评估一次。

    甚至更简单:

    try_match = lambda m, re_match=re.compile(r'sldkjlsdjf').match: re_match(m)
    

    或者最简单的:

    try_match = re.compile(r'sldkjlsdjf').match
    

    这不仅节省了重新编译时间(实际上它实际上是在 re 模块内部缓存的),而且还节省了 '.match' 方法的查找。在繁忙的功能或紧密的循环中,那些'.'分辨率可以叠加。

    【讨论】:

    • 感谢您的回答。我知道模块缓存,但即使在脚本中我在一些紧密循环中使用单个正则表达式,通过使用 re.compile 而不是依赖内置缓存,我获得了显着的性能提升。
    【解决方案3】:

    您可以在 Python 中定义闭包,就像在 JavaScript 中定义闭包一样。

    def get_matcher():
        compiled_regex = compile_my_regex()
    
        def try_match(m)
            return compiled_regex.match(m)
    
        return try_match
    

    但是,在 Python 2.x 中,闭包是只读的(对于上面的示例,您不能在函数调用内部重新分配给 compiled_regex)。如果闭包变量是可变数据结构(例如listdictset),您可以在函数调用中对其进行修改。

    def get_matcher():
        compiled_regex = compile_my_regex()
        match_cache = {}
    
        def try_match(m):
            if m not in match_cache:
               match_cache[m] = compiled_regex.match(m)
    
            return match_cache[m]
    
        return try_match
    

    在 Python 3.x 中,您可以使用 nonlocal 关键字在函数调用中重新分配给闭包变量。 (PEP-3104)

    另请参阅以下有关 Python 闭包的问题:

    【讨论】:

    • 补充一点:Python 3 引入了nonlocal,它可用于提供对封闭变量的显式写访问权限。 (PEP 3104)
    • 我对这个有点困惑。为什么每次运行时都不会评估get_matcher() 的整个主体?或者是为了然后做类似try_match = get_matcher()的事情?
    • 是的。想法是只调用一次get_matcher(),然后使用get_matcher()返回的函数来做实际的工作。
    【解决方案4】:

    一个常用的约定是在私有模块级全局变量之前使用下划线表示它们不是模块导出 API 的一部分:

    # mymodule.py
    
    _MATCHER = compile_my_regex()
    
    def try_match(m):
        return _MATCHER.match(m)
    

    您不应该因此而气馁 - 它比函数闭包中的隐藏变量更可取。

    【讨论】:

      【解决方案5】:

      您可以在任何函数中存储属性。由于函数名称是全局的,您可以在其他函数中检索它。例如:

      def memorize(t):
          memorize.value = t
      
      def get():
          return memorize.value
      
      memorize(5)
      print get()
      

      输出:

      5
      

      您可以使用它在单个函数中存储状态:

      def memory(t = None):
          if t:
              memory.value = t
          return memory.value
      
      print memory(5)
      print memory()
      print memory()
      print memory(7)
      print memory()
      print memory()
      

      输出:

      5
      5
      5
      7
      7
      7
      

      当然它的用处是有限的。我只在 this question 的 SO 上使用过它。

      【讨论】:

        【解决方案6】:

        你可以使用 generator.send();它可能不适合这种特殊情况,但对于维护没有类的状态很有用。调用 '.send(x)' 在调用 yield 后设置值。如果调用 'next' 而不是 possible_vals 将是无。 Related Send Question.

        def try_match(regex = '', target = ''):
            cache = {}
            while True:
                if regex not in cache:
                    cache[regex] = re.compile(regex).match
                possible_vals = (yield cache[regex](target))
                if possible_vals is not None:
                    (regex, target) = possible_vals
                
        m = try_match(r'(.*)-(.*)', '1-2')
        print(next(m))
        m.send((r'(.*)-(.*)', '3-4'))
        print(next(m))
        
        #Note that you have to call yield before you can send it more values
        n = try_match()
        n.send((r'(.*)-(.*)', '5-6'))
        print(next(n))
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-06-25
          • 2010-11-16
          • 1970-01-01
          • 2013-01-25
          • 1970-01-01
          • 2013-01-12
          相关资源
          最近更新 更多