*args 是传递参数 to/from 修饰函数的正确方法吗?

is *args the proper way to pass parameters to/from decorated functions?

我有以下(简单的家庭级)问题:我将程序的状态保存在 JSON 文件中,并有几个使用该“数据库”的函数。有些只需要加载数据库,有些需要加载它,然后写回文件。

我想在这些函数上使用装饰器来集中读取和写入数据库。下面是我的代码的简化版本,有两个功能:一个只使用数据库,另一个也修改它。此代码有效并且 returns 预期值(*)

请注意数据库(db)是如何在装饰器和函数之间传递的

def load_db(func):
    def wrapper():
        print("loading DB")
        db = 5
        # the db was loaded and is now passed to the function actually making use of it
        func(db)
    return wrapper

def load_and_write_db(func):
    def wrapper():
        print("loading DB")
        db = 5
        # the db was loaded and is now passed to the function actually making use of it
        # we will get back the changed database
        db = func(db)
        # now we write the DB to the disk
        print(f"writing DB: {db}")
    return wrapper

@load_db
def do_stuff_load_only(*db):
    # a function that just consumes the DB, without changing it
    print(f"initial DB is {db}")

@load_and_write_db
def do_stuff_load_and_write(*db):
    # a function that consumes and chnages the DB (which then needs to be updated on disk)
    print(f"initial DB is {db}")
    db = 10
    print(f"changed DB to {db}")
    # returning the new DB
    return db


do_stuff_load_only()
do_stuff_load_and_write()

# Output:
# 
# loading DB
# initial DB is (5,)
# loading DB
# initial DB is (5,)
# changed DB to 10
# writing DB: 10

这是在装饰器和函数之间传递信息的正确方法吗?具体来说,我是否应该依靠*db来指示装饰器只将参数传递给函数,而当它实际从代码中调用时什么也没有传递(最后两行) ?

This answer 很好地解释了应该如何传递参数,只是未能解决我关于装饰函数有时接收参数的问题,有时不接收参数的问题。


(*) 几乎db 传递给函数的是一个元组,我可以接受

装饰器的编写方式,do_stuff_load_only 可以使用常规参数定义,即使您在调用它时实际上不会传递参数。那是因为名称 do_stuff_load_only 不会绑定到单参数函数;它将绑定到 load_db 中定义的 zero 参数函数 wrapperwrapper 本身会负责将参数传递给被修饰的实际单参数函数。

@load_db
def do_stuff_load_only(db):
    # a function that just consumes the DB, without changing it
    print(f"initial DB is {db}")


do_stuff_load_only()

定义 do_stuff_load_only(*db) 工作 ,但会更改实际绑定到 db 的内容;在这种情况下,它将是单例元组 (5,) 而不是整数 5.

如果您认为这看起来很尴尬,那是因为它确实如此。 load_db 有一个您不必担心的“隐藏”副作用。上下文管理器可能更适合这里:

from contextlib import contextmanager


@contextmanager
def load_db():
    print("Initializing the database")
    yield 5  # provide it
    print("Tearing down the database")


def do_stuff_load_only(*db):
    # a function that just consumes the DB, without changing it
    print(f"initial DB is {db}")


with load_db() as db:
    do_stuff_load_only(db)

现在绑定到 do_stuff_load_only 的函数的定义和使用一致,上下文管理器隐藏了数据库如何创建和销毁的详细信息。上面代码的输出是

Initializing the database
initial DB is 5
Tearing down the database