Python装饰器继承问题

Posted

技术标签:

【中文标题】Python装饰器继承问题【英文标题】:Python Decorator Inheritance issue 【发布时间】:2022-01-18 05:03:41 【问题描述】:
class Foo():
    help_message = ""

    def Help_Decorator(func):
        def wrapper(data, context, caller):
            try:
                if data[0] == "help":
                    return(help_message) #<--- cannot access this locally
                else:
                    return func(data,context,caller)
            except:
                return func(data,context,caller)
    return wrapper

class Bar(Foo):
    help_message = "A real help message!"
    @foo.Help_Decorator
    def Run(data,context,caller):
        pass

如何在我的 Help_Decorator 中访问 Bar 的 help_message 而不将其作为参数传递给 Run

【问题讨论】:

你不能。 foo.Help_Decorator 仅获取一个函数作为其参数,并且该函数没有任何对 Bar 的引用,您可以使用它来访问 Bar.help_message 为什么Rundata的第一个参数?应该是self。无论如何,现在该实例将被传递给 第一个参数,所以data.help_message 应该可以工作,但您似乎根本不了解类定义 这样永远行不通。尝试在class Foo 中执行def foo(self): print(help_message) 根据您的描述,听起来您只需要模块中的常规功能。这里的课似乎没有用 【参考方案1】:

由于Run 是一个实例方法,data 是一个Bar 实例。通常这会按照约定称为self,但给它一个不同的名称不会改变函数调用的语义。

因此,您应该能够在您的wrapper 函数中访问data.help_message。 (我也希望data[0] 给你一个TypeError,除非Bar 已经实现了__getitem__。)

如果您的意图是在类而不是实例上调用Run,则不应将其定义为实例方法。使其成为类方法:

class Bar(): 
    help_message = "A real help message!" 

    @classmethod
    @foo.Help_Decorator
    def Run(cls, data, context, caller): 
        pass

现在你可以在你的包装器中做:

    def Help_Decorator(func):
        def wrapper(cls, data, context, caller):
            try:
                if data[0] == "help":
                    return(cls.help_message)
                else:
                    return func(cls, data, context, caller)
            except:
                return func(cls, data, context, caller)
    return wrapper

请注意,Help_Decorator 不需要成为FooBar 的方法。

大家一起:

class Foo():
    help_message = ""

    def Help_Decorator(func):
        def wrapper(cls, data, context, caller):
            try:
                if data[0] == "help":
                    return(cls.help_message)
                else:
                    return func(cls, data, context, caller)
            except:
                return func(cls, data, context, caller)
        return wrapper

class Bar(Foo):
    help_message = "A real help message!"

    @classmethod
    @Foo.Help_Decorator
    def Run(cls, data, context, caller):
        return "derp"

print(Bar.Run(["help"], 1, 0))

打印:

A real help message!

完全从代码中提取Foo 会产生完全相同的结果;实现Help_Decorator的地方和它装饰的方法的类之间不需要任何继承关系:

def Help_Decorator(func):
    def wrapper(cls, data, context, caller):
        try:
            if data[0] == "help":
                return(cls.help_message)
            else:
                return func(cls, data, context, caller)
        except:
            return func(cls, data, context, caller)
    return wrapper

class Bar:
    help_message = "A real help message!"

    @classmethod
    @Help_Decorator
    def Run(cls, data, context, caller):
        return "derp"

print(Bar.Run(["help"], 1, 0))

【讨论】:

data[0] 是我的初始函数参数的一部分,不会返回 TypeError。我没有实例化这些类的任何实例。 @Situc 那么为什么它是一个类,你希望函数如何访问类变量? @Sitluc 您可能希望将其设为@classmethod,以便它接收cls 作为第一个参数。将其设为实例方法,然后将不是实例的内容作为第一个参数传递(a)对试图理解您的代码的其他人不友好(b)与您在这里尝试做的事情适得其反。 你必须得到cls.help_message。如果函数是在Bar 上定义的类方法,则cls 应该是Bar 测试过了,需要先来。装饰器排序总是令人困惑。 @Sitluc 我用实际的工作代码和示例输出更新了它。运行它并告诉我它不起作用。 :P【参考方案2】:

传递给包装器的第一个参数是 Run 类的实例,您应该添加 self 作为第一个参数。然后你可以参考self.help_message,它会返回Run类中定义的help_message。

(编辑) 如果您想将装饰器用作类方法,那么只需对其进行装饰即可。你的例子会变成。

class foo(): 
    help_message = "bad" 
 
    @classmethod     
    def Help_Decorator(cls, func): 
        @classmethod 
        def wrapper(cls, data, context, caller): 
            print(data, context, caller) 
            try: 
                if data[0] == "help": 
                    print('helping', cls) 
                    return(cls.help_message) #<--- cannot access this locally 
                else: 
                    return func(data,context,caller) 
            except: 
                return func(data,context,caller) 
        return wrapper 
 
class Bar(): 
    help_message = "A real help message!" 
 
    @foo.Help_Decorator 
    @classmethod 
    def Run(cls, data,context,caller): 
        pass

【讨论】:

我应该更清楚。我没有实例化任何一个类的实例。 @Sitluc 那么它不应该是一个类。类的唯一目的是创建实例。如果这不是你使用它们的目的,那么你不应该期望类对你有用,就像你在这里遇到的那样。我不确定您希望如何访问类状态。 我相信你是不正确的。我从 Foo 继承来节省自己重写许多函数的时间。我如何错误地使用类? 为什么要重写任何函数未实例化的类没有任何用途,除了命名空间之外,但这可以通过types.SimpleNamespace 轻松完成,或者只是模块中的函数。如果你没有实例化任何东西,继承的目的是什么? 如果您想使用类方法而不是实例化,已编辑以包含答案

以上是关于Python装饰器继承问题的主要内容,如果未能解决你的问题,请参考以下文章

Python装饰器继承问题

重写的方法是不是继承python中的装饰器?

作为基类一部分的 Python 装饰器不能用于装饰继承类中的成员函数

Python 装饰器和继承

[Python]理解一下装饰器

Python子类方法从超类方法继承装饰器