如何在没有类的情况下在Python中维护状态?

问题描述 投票:0回答:7

是否有

pythonic
方法可以在不完全面向对象的情况下维护状态(例如,出于优化目的)?

为了更好地说明我的问题,下面是我在 JavaScript 中经常使用的模式的示例:

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

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

Python 中的一个例子是优化正则表达式时 - 使用

re.compile
并存储
match
search
操作的编译版本非常有用。

我知道在 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 开发者对于如何处理这个问题有什么建议吗?或者你只是不担心并继续解决问题?

python closures state
7个回答
16
投票

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

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
)。如果闭包变量是可变数据结构(例如
list
dict
set
),您可以在函数调用中修改它。

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 中的闭包的问题:


15
投票

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

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”方法的查找。在繁忙的函数或紧密的循环中,那些“.”分辨率可以累加。


8
投票

那又如何

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")

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


6
投票

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

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

当然它的用处是有限的。我只在这个问题中使用过它。


2
投票

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

# mymodule.py

_MATCHER = compile_my_regex()

def try_match(m):
    return _MATCHER.match(m)

您不应该灰心这样做 - 它比函数闭包中的隐藏变量更可取。


0
投票

您可以使用generator.send();它可能不适合这种特殊情况,但对于在没有类的情况下维护状态很有用。调用“.send(x)”会在调用yield 后设置值。如果调用“next”,possible_vals 将为 none。 相关发送问题.

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))

0
投票

我可以想到具有状态的函数的两种替代方案。以下是玩具示例:

  1. 使用“非本地”
def func_creator():
    state = 0

    def func():
        nonlocal state
        state += 1
        return state
    
    return func

func_with_state = func_creator()
  1. 利用列表可变性
def func_with_state(state=[0]):
    state[0] += 1
    return state[0]
© www.soinside.com 2019 - 2024. All rights reserved.