Python - 函数属性或可变默认值

4 投票
4 回答
1657 浏览
提问于 2025-04-17 00:18

假设你有一个函数,它需要保持某种状态,并根据这个状态表现得不同。我知道有两种方法可以实现这个功能,状态完全由函数自己来存储:

  1. 使用函数属性
  2. 使用可变的默认值

下面是一个稍微修改过的例子,基于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

怎么样:

  1. 使用一个类
  2. 使用一个全局变量

没错,这些东西并不是完全存储在函数内部。我可能会选择使用一个类:

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在类中没有实现这个功能,原因各不相同...)

撰写回答