假设您有一个需要维护某种状态的函数,并且根据该状态的不同行为。我知道实现这两种方法,其中状态完全由函数存储:
使用稍微修改过的Felix Klings answer to another question版本,这是一个可以在re.sub()
中使用的示例函数,以便只替换正则表达式的第三个匹配项:
功能属性:
def replace(match):
replace.c = getattr(replace, "c", 0) + 1
return repl if replace.c == 3 else match.group(0)
可变默认值:
def replace(match, c=[0]):
c[0] += 1
return repl if c[0] == 3 else match.group(0)
对我来说,第一个似乎更清洁,但我更常见的是第二个。哪个更好,为什么?
答案 0 :(得分:4)
我使用闭合,没有副作用。
以下是示例(我刚刚修改了Felix Klings answer的原始示例):
def replaceNthWith(n, replacement):
c = [0]
def replace(match):
c[0] += 1
return replacement if c[0] == n else match.group(0)
return replace
用法:
# reset state (in our case count, c=0) for each string manipulation
re.sub(pattern, replaceNthWith(n, replacement), str1)
re.sub(pattern, replaceNthWith(n, replacement), str2)
#or persist state between calls
replace = replaceNthWith(n, replacement)
re.sub(pattern, replace, str1)
re.sub(pattern, replace, str2)
如果有人调用replace(match,c = [])会发生什么?
对于属性你破坏了封装(是的,我知道python没有在类中实现差异原因......)
答案 1 :(得分:2)
这两种方式对我来说都很奇怪。第一个虽然好多了。但是当你以这种方式思考它时:“具有可以对该状态进行操作的状态和额外输入的东西”,它听起来真的像普通对象。当某些东西听起来像一个物体时,它应该是一个物体......
所以,我的解决方案是使用一个带有__call__
方法的简单对象:
class StatefulReplace(object):
def __init__(self, initial_c=0):
self.c = initial_c
def __call__(self, match):
self.c += 1
return repl if self.c == 3 else match.group(0)
然后你可以在全局空间或模块init中编写:
replace = StatefulReplace(0)
答案 2 :(得分:1)
怎么样:
是的,这些并不完全存储在函数中。我可能会使用一个类:
class Replacer(object):
c = 0
@staticmethod # if you like
def replace(match):
replace.c += 1
...
要回答您的实际问题,请使用getattr
。这是一种非常清晰易读的方式来存储数据以供日后使用。对于那些阅读它的人来说,你应该做的事情应该是非常明显的。
可变默认参数版本是一个常见编程错误的示例(假设您每次都会获得一个新列表)。仅仅因为这个原因,我会避免它。稍后阅读它的人可能会在没有完全理解后果的情况下决定这是一个好主意。即使在这种情况下,似乎您的函数只能工作一次(您的c
值永远不会重置为零)。
答案 3 :(得分:1)
对我而言,这两种方法看起来都很狡猾。问题是为一个类实例而哭泣。我们通常不认为函数是在调用之间保持状态;这就是的。。
那就是说,之前我曾经使用过函数属性来处理这类事情。特别是如果它是在其他代码中定义的一次性函数(即,不可能从其他任何地方使用它),只需在其上添加属性比定义一个完整的新类并创建它的实例更简洁。 p>
我永远不会滥用此默认值。理解存在很大障碍,因为默认值的自然目的是提供参数的默认值,而不是维持调用之间的状态。另外,默认参数会邀请您提供非默认值,如果您使用滥用默认值来维护状态的函数,则通常会出现非常奇怪的行为。