在定义Python类时,我希望使用decorator将其一些方法注册到类变量列表中。下面是一个不正确的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()
预期输出为
[<function MyClass.incinerate at 0x000001A42A629280>, <function MyClass.stab at 0x000001A42A629281>]
有可能做到这一点而不过分折磨Python吗?
您真正想要做的就是在方法上设置dangerous
。请记住,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()
输出:
on instance
incinerate
stab
on class
incinerate
stab
and yes, they work
incinerate
you've been stabbed
as a classmethod
incinerate
stab
如果你想推广这种方法并设置任意属性,你需要设置一个参数化的装饰器:
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
您将按如下方式使用:
@annotate_func(dangerous=1,range=1000)
def shoot(self, times):
for i in range(0, times):
print("bang!")
这是实现的一种方法
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>]
只需注意,通过这种方式,函数只有在调用后才能添加到列表中,并且存在函数多次添加到列表的风险。然而,如果你知道你想添加到列表中的一些函数是常量,你可以在构建对象时简单地添加它们:
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>>]
下面的代码片段正是您所描述的。
注意,print_dangerous_methods
被声明为一个类方法,因为它实际上就是这样(它适用于类,而不是某个实例(。这意味着您甚至可以在不创建实例的情况下调用它。
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()