简体   繁体   English

如何将 python class 中的方法自动“注册”为列表 class 变量?

[英]How to automatically "register" methods in a python class as a list class variable?

When defining a Python class, I'd like to use decorators to register some of its methods into a class variable list.在定义 Python class 时,我想使用装饰器将其一些方法注册到 class 变量列表中。 Here's an example of incorrect python that outlines what I'm looking for:这是一个不正确的 python 示例,它概述了我正在寻找的内容:

class MyClass:

    dangerous_methods = []

    @classmethod
    def dangerous_method(cls, func):
        cls.dangerous_methods.append(func)
        return func

    @MyClass.dangerous_method
    def incinerate(self):
        pass

    def watch_tv(self):
        pass

    @MyClass.dangerous_method
    def stab(self):
        pass

    def print_dangerous_methods(self):
        print(self.dangerous_methods)


obj = MyClass()
obj.print_dangerous_methods()

with the expected output being预期的 output 是

[<function MyClass.incinerate at 0x000001A42A629280>, <function MyClass.stab at 0x000001A42A629281>]

Is it possible to do this without torturing Python too much?是否可以在不过度折磨 Python 的情况下做到这一点?

This is one way to implement that:这是实现它的一种方法:

class MyClass:
    def __init__(self):
        self.dangerous_methods = []

    def dangerous_method(func):
        def inner(self):
            self.dangerous_methods.append(func)
            return func(self)
        return inner

    @dangerous_method
    def incinerate(self):
        print('Incinerate called!')
        pass

    def watch_tv(self):
        print('Watch_tv called!')
        pass

    @dangerous_method
    def stab(self):
        print('Stab called!')
        pass

    def print_dangerous_methods(self):
        print(self.dangerous_methods)


obj = MyClass()
obj.incinerate()
# Incinerate called!
obj.watch_tv()
# Watch_tv called!
obj.stab()
# Stab called!
obj.incinerate()
# Incinerate called!
obj.print_dangerous_methods()
# [<function MyClass.incinerate at 0x0000029C11666EE8>, <function MyClass.stab at 0x0000029C11666B88>, <function MyClass.incinerate at 0x0000029C11666EE8>]

Just note that in this way, functions are being added to the list ONLY once they've called and there is a risk that a function being added to the list multiple times.请注意,通过这种方式,只有在调用函数后才会将函数添加到列表中,并且存在将 function 多次添加到列表中的风险。 However, if you know that there are some functions in mind that you want to add to the list and they're constants, you can simply add them while the object is being constructed:但是,如果您知道要添加到列表中的某些函数并且它们是常量,则可以在构建 object 时简单地添加它们:

class MyClass:
    def __init__(self):
        self.dangerous_methods = [self.incinerate, self.stab]

    def incinerate(self):
        print('Incinerate called!')
        pass

    def watch_tv(self):
        print('Watch_tv called!')
        pass

    def stab(self):
        print('Stab called!')
        pass

    def print_dangerous_methods(self):
        print(self.dangerous_methods)


obj = MyClass()
obj.print_dangerous_methods()
# [<bound method MyClass.incinerate of <__main__.MyClass object at 0x0000029C11388F08>>, <bound method MyClass.stab of <__main__.MyClass object at 0x0000029C11388F08>>]

All you really want to do is to set dangerous on the methods.您真正想要做的就是在方法上设置dangerous Remember that python functions and methods are first-class objects, you can set arbitrary attributes on them.请记住,python 函数和方法是一流的对象,您可以在它们上设置任意属性。

def print_dangerous_methods(cls):
    """ yes, you could also return a list """
    for name in dir(cls):
        f = getattr(cls, name)
        if callable(f) and getattr(f, "dangerous", False):
            print(name)


def dangerous(func):
    setattr(func, "dangerous", True)
    return func

class MyClass:

    @dangerous
    def incinerate(self):
        print("incinerate")

    def watch_tv(self):
        pass

    @dangerous
    def stab(self):
        return "you've been stabbed"

    class_born_dangerous = print_dangerous_methods

print("\non instance")
obj = MyClass()
print_dangerous_methods(obj)

print("\non class")
print_dangerous_methods(MyClass)

print("\nand yes, they work")
obj.incinerate()
print (obj.stab())

print("\nas a classmethod")
obj.class_born_dangerous()

output: output:


on instance
incinerate
stab

on class
incinerate
stab

and yes, they work
incinerate
you've been stabbed

as a classmethod
incinerate
stab

If you want to generalize this approach and set arbitrary attributes, you need to set up a parametrized decorator:如果你想推广这种方法并设置任意属性,你需要设置一个参数化的装饰器:

def annotate_func(**kwds):
    """set arbitrary attributes"""
    def actual_decorator(func):
        for k, v in kwds.items():
            setattr(func, k, v)
        return func
    return actual_decorator

which you would use as follows:您将使用如下:

    @annotate_func(dangerous=1,range=1000)
    def shoot(self, times):
        for i in range(0, times):
            print("bang!")

The following snippet does exactly what you described.以下代码段完全符合您的描述。

Note that print_dangerous_methods is declared as a class method, because that's what it really is (it applies to the class, not to a certain instance).请注意, print_dangerous_methods被声明为 class 方法,因为这就是它的真正含义(它适用于 class,而不适用于某个实例)。 That means you can call it even without creating an instance.这意味着即使不创建实例也可以调用它。

class MyClass:

    def dangerous_method(meth):
        meth.is_dangerous = True
        return meth

    @dangerous_method
    def incinerate(self):
        pass

    def watch_tv(self):
        pass

    @dangerous_method
    def stab(self):
        pass

    @classmethod
    def print_dangerous_methods(cls):
        print ([
            meth for meth in [
                getattr(cls, methname) for methname in dir(cls)
            ]
            if getattr(meth, "is_dangerous", False)
        ])

MyClass.print_dangerous_methods()

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM