簡體   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