用参数装饰类方法

Posted

技术标签:

【中文标题】用参数装饰类方法【英文标题】:decorating class method with arguments 【发布时间】:2019-11-16 23:42:38 【问题描述】:

如何?当前代码是

def establish_con(func):

    con, meta = db.connect(config.user, config.password, config.db)
    meta.reflect(bind=con)

    def inner(self, query, con, *args, **kwargs):
        return func(self, query, con, *args, **kwargs)

    con.close()
    return inner

class DataReader:
    def __init__(self):
        self.data = 

    @establish_con
    def execQuery(self, query, con):
        # con, meta = db.connect(config.user, config.password, config.db)
        # meta.reflect(bind=con)

        result = pd.read_sql(query, con)

        # con.close()
        return result

test = DataReader()
df = test.execQuery("Select * from backtest limit 10")
print(df)

目前,第一个参数似乎是类实例。我尝试了代码的不同变体,但总是遇到太多/不够/未定义的参数问题。

我读过其他类似的帖子

How do I pass extra arguments to a Python decorator?

Decorators with parameters?

还有其他人,但还是想不通。

编辑:不是 Python decorators in classes 的副本,因为在这个答案中不需要将参数传递给函数。

【问题讨论】:

@fizzybear 不是重复的,因为该答案中没有传递任何参数。 我同意这不是那个问题的重复,但它不是minimal reproducible example。 【参考方案1】:

改编@madjardi 来自this post 的答案以使用参数。

import functools

class Example:

    con = "Connection"

    def wrapper(func):
        @functools.wraps(func)
        def wrap(self, *args, **kwargs):
            print("inside wrap")
            return func(self, Example.con, *args, **kwargs) # use *args to pass objects down
        return wrap

    @wrapper
    def method(self, con, arg): 
        print("METHOD 0 1".format(con, arg))

    wrapper = staticmethod(wrapper)


e = Example()
e.method(1) # METHOD Connection 1

【讨论】:

我仍然收到TypeError: execQuery() missing 1 required positional argument: 'con',因为当我在装饰器中调用方法+另一个参数(con)时,我正在传递参数 已编辑。基本上,您将连接作为类变量并在 *args 之前传递它。如果你在 __init__ 中初始化它,你也可以做 self.con。 我需要在课堂上使用这个装饰器吗?我可以把它移出来吗?另外,在这种情况下 functools.wraps 的目的是什么? 是的,否则,您无法将self 传递给func。您可以硬编码一个全局变量并传递它而不是self,但这是不可取的。 functools.wraps 用于使 wrap 看起来像 func,用于堆栈跟踪中的文档字符串和函数名称。没有它,调试变得更加混乱。但这并不是绝对必要的。【参考方案2】:

你应该替换:

def inner(self, query, con, *args, **kwargs):
        return func(self, query, con, *args, **kwargs)

与:

def inner(self, query, *args, **kwargs):   # no 'con' here
        return func(self, query, con, *args, **kwargs) 

使用您的策略的最小工作示例是(实现与您的目标不一样):

def add_arg(method):

    def decorated_method(self, *args):
        return method(self, 10, *args)

    return decorated_method

class Data:
    @add_arg
    def summation(self, *args):
        return sum(args)


d = Data()
print(d.summation(1, 2))  # prints 13, not 3

【讨论】:

我需要传递在装饰器内部创建的变量 con。 是的,这就是inner 没有con 作为参数的原因。这个值来自周围的上下文(即闭包上下文)。【参考方案3】:

我认为您需要这样做。我已将代码分成两个单独的块,以使其更清晰。

的第一部分只是建立了一个最小的脚手架,以便可以运行(并跟踪执行)第二个块中的代码,并使其尽可能接近您的问题。它本身并不是很重要。

带参数的装饰器实际上变成了装饰器工厂——从某种意义上说,它们必须创建一个返回装饰器函数,然后将其应用于目标函数或方法。

# Scaffolding
class Pandas:
    @staticmethod
    def read_sql(query, con):
        print(f'in Pandas read_sql(query!r, con)')
        return 'pandas_dataframe'


class Connection:
    def close(self):
        print('in Connection.close()')

    def __repr__(self):
        return '<Connection object>'

con = Connection()


class Meta:
    def reflect(self, bind=con):
        print(f'in Meta.reflect(bind=bind')


class Database:
    def connect(self, user, password, db):
        print(f'in Database.connect(user, password, db)')
        return Connection(), Meta()

    def __repr__(self):
        return '<Database object>'

class Config:
    def __init__(self, user, password, db):
        self.user = user
        self.password = password
        self.db = db

# Set up a framework for testing.
pd = Pandas()
meta = Meta()
db = Database()
config = Config('username', 'secret', db)

环境建立后,装饰器的编写方法如下。

# Decorator
def establish_con(config):

    def wrapper(method):

        def wrapped(*args, **kwargs):
            con, meta = db.connect(config.user, config.password, config.db)
            meta.reflect(bind=con)

            args = args + (con,)
            result = method(*args, **kwargs)

            con.close()

            return result

        return wrapped

    return wrapper


class DataReader:
    def __init__(self):
        self.data = 

    @establish_con(config)
    def execQuery(self, query, con):
        return pd.read_sql(query, con)


test = DataReader()
df = test.execQuery("Select * from backtest limit 10")
print(df)

输出:

in Database.connect(username, secret, <Database object>)
in Meta.reflect(bind=<Connection object>
in Pandas read_sql('Select * from backtest limit 10', <Connection object>)
in Connection.close()
pandas_dataframe

【讨论】:

出于好奇,当其他 2 个答案使用装饰器时,您为什么使用装饰器工厂?有什么优点/缺点?只是想了解有关装饰器的更多信息 因为这就是带参数的装饰器的工作方式——一个明显的优势;¬) 这在文档的Function definitions 部分中关于装饰器的部分中有所提及——注意尤其是@f1(arg) 大致相当于示例中的内容。 我的另一个answer的第一部分更详细地解释了“装饰工厂”的概念。

以上是关于用参数装饰类方法的主要内容,如果未能解决你的问题,请参考以下文章

classmethod和staticmethod区别

5.10 绑定方法和非绑定方法

将参数传递给要装饰的类方法的装饰器

装饰器、装饰器类与类装饰器(三)

TypeScript(21): 装饰器

带有自参数的类方法装饰器?