我正在尝试创建一个装饰器,它在一个函数和它将响应的执行的相关文本之间创建一个关联。以下是一些说明我的意思的工作代码:
# WORKING CODE
mapping = {}
def saying(text):
def decorator(function):
mapping[text] = function
return function
return decorator
@saying("hi")
def hi():
print "hello there"
@saying("thanks")
@saying("gracias")
def thanks():
print "you're welcome"
mapping["hi"]() #running this line will print "hello there"
mapping["thanks"]() #running this line will print "you're welcome"
当我尝试将这些方法添加到类时会出现问题。像这样:
#NON-WORKING CODE:
class politeModule(object):
def __init__(self):
self.mapping = {}
@saying("hi")
def hi(self):
print "hello there"
@saying("thanks")
@saying("gracias")
def thanks(self):
print "you're welcome"
module = politeModule()
module.mapping["hi"]()
module.mapping["thanks"]()
问题是,我不知道装饰器的位置,以便它可以访问mapping
并且也可以工作。我知道有很多StackOverflow问题和文章。我试图实现this博客文章中描述的一些解决方案,但一再陷入范围问题并从装饰器内部访问映射字典
答案 0 :(得分:0)
这里的问题是您需要访问self
才能追加self.mapping
,并且装饰器(如前所述)无法访问self
对象,因为它是甚至在创建实例之前在类定义期间运行。
您可以在类级别而不是实例级别存储变量mapping
。然后你可以让你的装饰器为它装饰的每个函数添加一个属性,然后使用类装饰器来搜索具有该属性的函数并将它们添加到cls.mapping:
方法装饰
def saying(text):
def decorator(function):
if hasattr(function,"mapping_name"):
function.mapping_name.append(text)
else:
function.mapping_name=[text]
return function
return decorator
(我在这里使用了一个列表,否则,当你两次调用装饰器时(如'thanks','gracias'示例),如果它只是一个字符串,则会覆盖mapping_name。)
类装饰器
def map_methods(cls):
cls.mapping = {}
for item in cls.__dict__.values():
if hasattr(item, "mapping_name"):
for x in item.mapping_name:
cls.mapping[x] = item
return cls
然后你必须用map_methods
装饰器装饰整个类,如下所示:
@map_methods
class politeModule(object):
@saying("hi")
def hi(self):
print ("hello there")
@saying("thanks")
@saying("gracias")
def thanks(self):
print ("you're welcome")
(另请注意,我们不再想写self.mapping=[]
,因此我删除了您的初始版。
替代方法
或者,您可以使用元类来处理此类事情,但我认为更重要的问题是您为什么要这样做。尽管可能有理由这样做,但无论原始问题是什么,都可能有更好的方法。
重要提示
您将无法使用原始帖子中使用的方法调用该函数,例如module.mapping["hi"]()
。请注意module.mapping["hi"]
返回一个函数,然后调用该函数,因此没有对象将被传递给第一个参数self
,因此您必须改为编写module.mapping["hi"](module)
。解决此问题的一种方法是,您可以按如下方式编写 init :
def __init__(self):
self.mapping = { k: functools.partial(m, self) for k,m in self.mapping.items() }
这意味着映射现在是实例变量而不是类变量。您现在也可以使用module.mapping["hi"]()
调用您的函数,因为functools.partial
将self
绑定到第一个参数。不要忘记将import functools
添加到脚本的顶部。
答案 1 :(得分:0)
您需要进行2次初始化。
在类初始化时,应使用装饰器将名称附加到指定的方法。可选地,在完全定义类之后,它可以接收新的mapping
属性映射名称到方法名称。
成员初始化,每个成员都应该接收mapping
属性映射名称到绑定方法。
我会使用基类和装饰器:
class PoliteBase(object):
def __init__(self):
"""Initializes "polite" classes, meaning subclasses of PoliteBase
This initializer will be called by subclasse with no explicit __init__ method,
but any class with a __init__ method will have to call this one explicitely
for proper initialization"""
cls = self.__class__ # process this and all subclasses
if not hasattr(cls, "mappings"): # add a mapping attribute TO THE CLASS if not
cls.mappings = {} # present
for m in cls.__dict__: # and feed it with "annotated" methods and names
o = getattr(cls, m)
if callable(o) and hasattr(o, "_names"): # only process methods
for name in o._names: # with a _name attribute
cls.mappings[name] = m # map the name to the method
# name
self.mappings = { k: getattr(self, m) # now set the mapping for instances
for k,m in cls.mappings.iteritems() } # mapping name to a bound method
如果子类没有定义__init__
方法,则将使用基类1,但如果子类定义了一个,则必须明确地调用此方法。
def saying(name):
"""Decorator used in combination with "PoliteBase" subclasses
It just adds a _name attribute to the decorated method containing the associated
names. This attribute will later be processed at instance initialization time."""
@functools.wraps(f)
def wrapper(f):
try:
f._names.append(name)
except Exception as e:
f._names = [name]
return f
return wrapper
完成此操作后,您可以定义礼让类:
class politeClass(PoliteBase):
def __init__(self):
self.mapping = {}
@saying("hi")
def hi(self):
print "hello there"
@saying("thanks")
@saying("gracias")
def thanks(self):
print "you're welcome"
obj = politeClass()
obj.mapping["hi"]()
obj.mapping["thanks"]()
我重命名了你的模块对象,因为在Python意义上模块是不同的东西(它是脚本文件本身)
答案 2 :(得分:0)
首先,当使用装饰器作为函数的寄存器时,一个好的选择是为你的装饰器编写一个类,这样它就可以用来注册和访问已注册的函数。
class RegisterDecorator(object):
def __init__(self):
self._register = {}
def __getitem__(self, item):
return self._register[item]
def register(self, text):
def wrapper(f):
self._register[text] = f
return f
return wrapper
saying = RegisterDecorator()
@saying.register('hello')
def f():
print('Hello World')
saying['hello']() # prints 'Hello World'
以上内容适用于注册方法。虽然,它只会注册未绑定的方法。这意味着您必须手动传递self
参数。
saying = Saying()
class PoliteModule(object):
@saying.register("hi")
def hi(self):
print("hello there")
saying['hi'](PoliteModule()) # prints: 'hello there'
saying['hi']() # TypeError: hi() missing 1 required positional argument: 'self'
在类实例化时无法注册绑定方法,因为还没有实例存在。您必须创建一个实例并注册其绑定方法。
saying = Saying()
class PoliteModule(object):
def hi(self):
print("hello there")
politeInstance = PoliteModule()
saying.register("hi")(politeInstance.hi)
saying["hi"]() # prints: hello there