蟒蛇 |实例化之前的类方法装饰器

Posted

技术标签:

【中文标题】蟒蛇 |实例化之前的类方法装饰器【英文标题】:Python | class method decorator before instantiation 【发布时间】:2020-12-03 09:21:17 【问题描述】:

前言:这个问题可能比标题所暗示的要简单

我正在尝试实现一个简单的事件处理程序系统,使用装饰器“订阅”事件的各种方法和函数。

1.事件处理程序的简单方法

一种简单的方法是创建一个用于添加和运行事件的类:

# class for adding and running functions, aka an event handler
class Runner:
    def __init__(self):
        self.functions = []
    def add(self, function):
        self.functions.append(function)
    def run(self):
        for function in self.functions:
            function()

runner = Runner()

然后添加各种函数或方法:

# random example function
def myFunction():
    print('myFunction')

# random example class
class MyClass:
    def myMethod(self):
        print('myMethod')

# getting instance of class & method
myObject = MyClass()
myObjectMethod = myObject.myMethod

runner.add(myFunction)
runner.add(myObjectMethod)
runner.run()

这会导致:

> py main.py
myFunction
myMethod

不错!它按预期工作

2。装饰器方法

所以这没关系,但我最近了解了装饰器,并认为我可以通过替换有点丑陋的 runner.add() 方法来整理语法。

首先我将Runner.add() 方法更改为装饰器:

class Runner:
    def __init__(self):
        self.functions = []
    def add(self, function):
        self.functions.append(function)
        return function # <-- returns function
    def run(self):
        for function in self.functions:
            function()

runner = Runner()

然后我删除 runner.add() 调用并插入装饰器:

@runner.add
def myFunction():
    print('myFunction')

class MyClass:
    @runner.add
    def myMethod(self):
        print('myMethod')

# myObject = MyClass()
# myObjectMethod = myObject.myMethod

runner.run()

这显然会导致:

> py main.py
myFunction
Traceback (most recent call last):
...
TypeError: myMethod() missing 1 required positional argument: 'self'

很明显,这里的问题是我将“静态”(或者是 “未绑定”?myMethod 添加到事件中,它与实例无关,因此没有self 的任何实例。

this 之类的答案是指实现装饰器以包装方法as 它们被称为,但是我使用装饰器的原因是您在初始时获得的访问权限代码执行。

一个想法是必须在MyClass.__init__() 构造函数中运行某些东西,因为只有这样才能创建一个实例并且您可以访问self,但是您将如何运行装饰器仅在实例化时?更不用说达到我们在这里追求的结果了。

问题

由于runner.add() 有点冗长、丑陋,当然,我有哪些选项来实现示例 1 中显示的行为,但语法尽可能接近示例 2(或任何其他“看起来干净”的变体)不像装饰器那样精简。

干杯!

【问题讨论】:

是否应该将每个已创建的MyClass 实例添加到Runner?他们如何被删除?使用函数有什么问题? @SamMason 是的,每个实例(甚至是子类)都应该将其myMethod 添加到Runner。如前所述,我想要一个更简洁的事件订阅语法 - 带有装饰器的内联语法似乎更可取。 为什么不把runner.add(self.myMethod) 放在__init__ 中?它使代码的读者更清楚正在发生的事情,并且这将影响每个实例。我能想到让它工作的唯一方法是搞乱元类并涉及__new__ 方法。希望其他人会有更简单的想法! 【参考方案1】:

不要将函数和方法视为相同,而是稍微不同地处理它们:

功能:

[A] 正常添加函数到列表

方法:

调用装饰器时的未绑定方法,因此还没有self 的关联实例或值 [B] 我们通过设置_tagged 属性在方法未绑定时(在创建实例之前)“标记”该方法 [C] 一旦创建了一个实例(并且self 变量已被填充),我们在__init__() 中搜索并添加所有标记的方法 我们可以在之后添加方法,因为一旦它们绑定到实例,self 参数就会被填充(这意味着它在技术上没有参数,类似于 functools.partial()
import inspect

class Runner:
    def __init__(self):
        self.functions = []

    def add(self, function):
        if len(inspect.signature(function).parameters) == 0:
            # [A] adds if has no parameters
            self.functions.append(function)
        else:
            # [B] tags if has 1 parameter (unbound methods have "self" arg)
            function._tagged = True
        return function

    # [C] search through object and add all tagged methods
    def add_all_tagged_methods(self, object):
        for method_name in dir(object):
            method = getattr(object, method_name)
            if hasattr(method, '_tagged'):
                self.functions.append(method)

    def run(self):
        for function in self.functions:
            function()

runner = Runner()

然后使用与下面相同的初始代码

@runner.add
def myFunction():
    print('myFunction')

class MyClass:
    def __init__(self):
        runner.add_all_tagged_methods(self)

    @runner.add
    def myMethod(self):
        print('myMethod')

myObject = MyClass()
myObjectMethod = myObject.myMethod

runner.run()

【讨论】:

以上是关于蟒蛇 |实例化之前的类方法装饰器的主要内容,如果未能解决你的问题,请参考以下文章

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

使用类来写装饰器

知识点 - python 装饰器@staticmethod和@classmethod区别和使用

二装饰器

装饰器

装饰器