繁体   English   中英

难以让装饰器在python中的类内部工作

[英]Difficulty getting decorator to work inside class in python

我正在尝试创建一个装饰器,它创建一个函数和它将响应的相关文本之间的关联。 这是一些工作代码,说明我的意思:

# 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问题和文章。 我尝试实现博文中描述的一些解决方案,但反复陷入范围问题并从装饰器内部访问映射字典

这里的问题是你需要访问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=[]所以我删除了你的init。

替代方法

或者,您可以使用元类来处理此类事情,但我认为更重要的问题是您为什么要这样做。 尽管可能有理由这样做,但无论原始问题是什么,都可能有更好的方法。

一个重要的说明

您将无法使用原始帖子中使用的方法调用该函数,例如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.partialself绑定到第一个参数。 不要忘记将import functools添加到脚本的顶部。

您需要进行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意义上模块是一个不同的东西(它是脚本文件本身)

注册装饰

首先,当使用装饰器作为函数的寄存器时,一个好的选择是为你的装饰器编写一个类,这样它就可以用来注册和访问已注册的函数。

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

暂无
暂无

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

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