Python - 函数属性或可变默认值
假设你有一个函数,它需要保持某种状态,并根据这个状态表现得不同。我知道有两种方法可以实现这个功能,状态完全由函数自己来存储:
- 使用函数属性
- 使用可变的默认值
下面是一个稍微修改过的例子,基于Felix Kling对另一个问题的回答,这个例子展示了如何在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)
在我看来,第一种方法看起来更简洁,但我发现第二种方法更常见。你觉得哪种更好,为什么呢?
4 个回答
1
怎么样:
- 使用一个类
- 使用一个全局变量
没错,这些东西并不是完全存储在函数内部。我可能会选择使用一个类:
class Replacer(object):
c = 0
@staticmethod # if you like
def replace(match):
replace.c += 1
...
要回答你真正的问题,可以使用 getattr
。这是一种非常清晰易懂的方式来存储数据,以便以后使用。读的人应该很容易明白你想做什么。
可变默认参数的版本是一个常见的编程错误(假设你每次都会得到一个新的列表)。仅仅因为这个原因,我会避免使用它。之后再看代码的人可能会觉得这是个好主意,但并没有完全理解后果。而且在这种情况下,你的函数似乎只会工作一次(你的 c
值从来没有重置为零)。
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)
然后你可以在全局空间或者你的模块初始化中写:
replace = StatefulReplace(0)
5
我用闭包来解决这个问题,这样就没有副作用了。
下面是一个例子(我只是修改了Felix Kling的原始例子):
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在类中没有实现这个功能,原因各不相同...)