关于python:有没有办法在函数中添加一个属性作为函数定义的一部分?

Is there a way to add an attribute to a function as part of the function definition?

每次调用函数时,函数属性do_something.n都会递增。

我在函数之外声明了属性do_something.n=0,这让我很困扰。

我使用queue.priorityqueue回答了这个问题,不关心使用"函数属性"进行比较,为priorityqueue的使用提供了一个独特的计数器-MartijnPieers提供了更好的解决方案)

MCVE:

1
2
3
4
5
6
7
8
9
10
11
def do_something():
    do_something.n += 1
    return do_something.n

# need to declare do_something.n before usign it, else
#     AttributeError: 'function' object has no attribute 'n'
# on first call of do_something() occures
do_something.n = 0

for _ in range(10):
    print(do_something())  # prints 1 to 10

还有什么其他方法可以定义函数"内部"的属性,以便在忘记它时避免使用AttributeError: 'function' object has no attribute 'n'

从评论中编辑了很多其他方式:

  • 函数中静态变量的python等价物是什么?@熊熊
  • 不使用martineau的"global"访问函数外部的函数变量


不完全在内部,但装饰器使函数属性更明显:

1
2
3
4
5
6
7
8
9
10
def func_attr(**attrs):
    def wrap(f):
        f.__dict__.update(attrs)
        return f
    return wrap

@func_attr(n=0)
def do_something():
    do_something.n += 1
    return do_something.n

这可能比将属性初始化放在函数中的任何内容都要干净。


使用内置的hasattr功能怎么样?

1
2
3
4
5
def do_something():
    if not hasattr(do_something, 'n'):
        do_something.n = 1
    do_something.n += 1
    return do_something.n

以下是关于hasattrtry-except的讨论,供参考:

hasattr()vs try except块处理不存在的属性


还有两种方法。第一个类使用一些人称之为"functor"的类来创建一个具有所需属性的可调用类,这些属性都来自于类内。

这种方法不需要处理异常,因此唯一的运行时开销来自一次性创建它的单个实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
class do_something:
    def __init__(self):
        self.n = 0

    def __call__(self, *args, **kwargs):
        do_something.n += 1
        return do_something.n

do_something = do_something()  # Allow only one instance to be created.


for _ in range(10):
    print(do_something())  # Prints 1 to 10.

第二种方法——非常"Python式的"——是把功能放在一个模块中(实际上是单子模块)。这就是我的意思:

文件do_something.py

1
2
3
4
5
6
n = 0

def do_something():
    global n
    n += 1
    return n

示例用法(在某些其他脚本中):

1
2
3
4
from do_something import do_something

for _ in range(10):
    print(do_something())  # Prints 1 to 10.


当我把我对另一个问题的回答提给你时,我的脑海里就有这样的想法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def with_this_arg(func):
    def wrapped(*args, **kwargs):
        return func(wrapped, *args, **kwargs)
    return wrapped

@with_this_arg
def do_something(this):
    if not getattr(this, 'n', None):
        this.n = 0
    this.n += 1
    return this.n

for _ in range(10):
    print(do_something())  # prints 1 to 10

如果您更喜欢更"Python式"的EAFP编码方式,它的速度会稍微快一点,那么可以编写成thusly:

1
2
3
4
5
6
7
@with_this_arg
def do_something(this):
    try:
        this.n += 1
    except AttributeError:  # First call.
        this.n = 1
    return this.n

当然。。。

这可以与@user2357112的回答(如果按正确的顺序进行)结合成这样的内容,不需要检查或异常处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def func_attr(**attrs):
    def wrap(f):
        f.__dict__.update(attrs)
        return f
    return wrap

def with_this_arg(func):
    def wrapped(*args, **kwargs):
        return func(wrapped, *args, **kwargs)
    return wrapped

@func_attr(n=0)
@with_this_arg
def do_something(this):
    this.n += 1
    return this.n

for _ in range(10):
    print(do_something())  # prints 1 to 10


我唯一能想到的方法是,遵循Python请求宽恕而不是许可的方法是:

1
2
3
4
5
6
7
def do_something():
    try:
        do_something.n += 1
    except AttributeError:
        do_something.n = 1

    return do_something.n

这将在第一次调用时自动生成属性,之后,try:代码块将工作。

有一些开销是由于try:catch:,但这是我能想到的在函数内部解决这个问题的唯一方法。