python partial




如何製作一系列功能裝飾器? (11)

如何在Python中創建兩個裝飾器來執行以下操作?

調用時,您需要以下函數:

@makebold
@makeitalic
def say():
    return "Hello"

回來:

<b><i>Hello</i></b>

簡單解決方案

為了最簡單地做到這一點,讓make decorators返回關閉函數(閉包)並調用它的lambdas(匿名函數)並調用它:

def makeitalic(fn):
    return lambda: '<i>' + fn() + '</i>'

def makebold(fn):
    return lambda: '<b>' + fn() + '</b>'

現在根據需要使用它們:

@makebold
@makeitalic
def say():
    return 'Hello'

現在:

>>> say()
'<b><i>Hello</i></b>'

簡單解決方案的問題

但我們似乎幾乎失去了原有的功能。

>>> say
<function <lambda> at 0x4ACFA070>

為了找到它,我們需要深入研究每個lambda的閉包,其中一個被埋在另一個中:

>>> say.__closure__[0].cell_contents
<function <lambda> at 0x4ACFA030>
>>> say.__closure__[0].cell_contents.__closure__[0].cell_contents
<function say at 0x4ACFA730>

因此,如果我們將文檔放在這個函數上,或者希望能夠裝飾帶有多個參數的函數,或者我們只是想知道我們在調試會話中看到了什麼函數,我們需要對我們的函數做更多的事情。包裝。

全功能解決方案 - 克服大多數這些問題

我們有裝飾wrapsfunctools模塊中的標準庫!

from functools import wraps

def makeitalic(fn):
    # must assign/update attributes from wrapped function to wrapper
    # __module__, __name__, __doc__, and __dict__ by default
    @wraps(fn) # explicitly give function whose attributes it is applying
    def wrapped(*args, **kwargs):
        return '<i>' + fn(*args, **kwargs) + '</i>'
    return wrapped

def makebold(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return '<b>' + fn(*args, **kwargs) + '</b>'
    return wrapped

不幸的是,仍然有一些樣板,但這很簡單,我們可以做到。

在Python 3中,默認情況下也會獲取__qualname____annotations__分配。

所以現在:

@makebold
@makeitalic
def say():
    """This function returns a bolded, italicized 'hello'"""
    return 'Hello'

現在:

>>> say
<function say at 0x14BB8F70>
>>> help(say)
Help on function say in module __main__:

say(*args, **kwargs)
    This function returns a bolded, italicized 'hello'

結論

所以我們看到,wraps除了告訴我們函數作為參數的確切內容之外,幾乎所有事情都會使包裝函數完成。

還有其他模塊可能嘗試解決該問題,但該解決方案尚未出現在標準庫中。

如何在Python中創建兩個裝飾器來執行以下操作?

@makebold
@makeitalic
def say():
   return "Hello"

...應該返回:

"<b><i>Hello</i></b>"

我不是試圖在實際應用程序中以這種方式製作HTML - 只是試圖了解裝飾器和裝飾器鍊是如何工作的。


用不同數量的參數裝飾函數:

def frame_tests(fn):
    def wrapper(*args):
        print "\nStart: %s" %(fn.__name__)
        fn(*args)
        print "End: %s\n" %(fn.__name__)
    return wrapper

@frame_tests
def test_fn1():
    print "This is only a test!"

@frame_tests
def test_fn2(s1):
    print "This is only a test! %s" %(s1)

@frame_tests
def test_fn3(s1, s2):
    print "This is only a test! %s %s" %(s1, s2)

if __name__ == "__main__":
    test_fn1()
    test_fn2('OK!')
    test_fn3('OK!', 'Just a test!')

結果:

Start: test_fn1  
This is only a test!  
End: test_fn1  


Start: test_fn2  
This is only a test! OK!  
End: test_fn2  


Start: test_fn3  
This is only a test! OK! Just a test!  
End: test_fn3  

以更簡單的方式解釋裝飾器:

附:

@decor1
@decor2
def func(*args, **kwargs):
    pass

什麼時候:

func(*args, **kwargs)

你真的這樣做:

decor1(decor2(func))(*args, **kwargs)

另一種做同樣事情的方法:

class bol(object):
  def __init__(self, f):
    self.f = f
  def __call__(self):
    return "<b>{}</b>".format(self.f())

class ita(object):
  def __init__(self, f):
    self.f = f
  def __call__(self):
    return "<i>{}</i>".format(self.f())

@bol
@ita
def sayhi():
  return 'hi'

或者,更靈活:

class sty(object):
  def __init__(self, tag):
    self.tag = tag
  def __call__(self, f):
    def newf():
      return "<{tag}>{res}</{tag}>".format(res=f(), tag=self.tag)
    return newf

@sty('b')
@sty('i')
def sayhi():
  return 'hi'

或者,您可以編寫一個返回裝飾器的工廠函數,該裝飾器將裝飾函數的返回值包裝在傳遞給工廠函數的標記中。 例如:

from functools import wraps

def wrap_in_tag(tag):
    def factory(func):
        @wraps(func)
        def decorator():
            return '<%(tag)s>%(rv)s</%(tag)s>' % (
                {'tag': tag, 'rv': func()})
        return decorator
    return factory

這使您可以寫:

@wrap_in_tag('b')
@wrap_in_tag('i')
def say():
    return 'hello'

要么

makebold = wrap_in_tag('b')
makeitalic = wrap_in_tag('i')

@makebold
@makeitalic
def say():
    return 'hello'

就個人而言,我會以不同的方式編寫裝飾器:

from functools import wraps

def wrap_in_tag(tag):
    def factory(func):
        @wraps(func)
        def decorator(val):
            return func('<%(tag)s>%(val)s</%(tag)s>' %
                        {'tag': tag, 'val': val})
        return decorator
    return factory

會產生:

@wrap_in_tag('b')
@wrap_in_tag('i')
def say(val):
    return val
say('hello')

不要忘記裝飾器語法是簡寫的結構:

say = wrap_in_tag('b')(wrap_in_tag('i')(say)))

查看文檔以了解裝飾器的工作原理。 這是你要求的:

def makebold(fn):
    def wrapped():
        return "<b>" + fn() + "</b>"
    return wrapped

def makeitalic(fn):
    def wrapped():
        return "<i>" + fn() + "</i>"
    return wrapped

@makebold
@makeitalic
def hello():
    return "hello world"

print hello() ## returns "<b><i>hello world</i></b>"

看起來其他人已經告訴過你如何解決這個問題。 我希望這能幫助你理解裝飾器是什麼。

裝飾者只是語法糖。

這個

@decorator
def func():
    ...

擴展到

def func():
    ...
func = decorator(func)

裝飾器接受函數定義並創建一個執行此函數並轉換結果的新函數。

@deco
def do():
    ...

相當於:

do = deco(do)

例:

def deco(func):
    def inner(letter):
        return func(letter).upper()  #upper
    return inner

這個

@deco
def do(number):
    return chr(number)  # number to letter

與此def do2(數字)等效:return chr(number)

do2 = deco(do2)

65 <=>'a'

print(do(65))
print(do2(65))
>>> B
>>> B

要理解裝飾器,重要的是要注意,裝飾器創建了一個新的函數do,它執行func並轉換結果。


這個答案早已得到回答,但我想我會分享我的Decorator類,這使得編寫新的裝飾器變得簡單而緊湊。

from abc import ABCMeta, abstractclassmethod

class Decorator(metaclass=ABCMeta):
    """ Acts as a base class for all decorators """

    def __init__(self):
        self.method = None

    def __call__(self, method):
        self.method = method
        return self.call

    @abstractclassmethod
    def call(self, *args, **kwargs):
        return self.method(*args, **kwargs)

我認為這使得裝飾器的行為非常清晰,但它也使得簡單地定義新的裝飾器變得容易。對於上面列出的示例,您可以將其解決為:

class MakeBold(Decorator):
    def call():
        return "<b>" + self.method() + "</b>"

class MakeItalic(Decorator):
    def call():
        return "<i>" + self.method() + "</i>"

@MakeBold()
@MakeItalic()
def say():
   return "Hello"

您還可以使用它來執行更複雜的任務,例如裝飾器自動使函數以遞歸方式應用於迭代器中的所有參數:

class ApplyRecursive(Decorator):
    def __init__(self, *types):
        super().__init__()
        if not len(types):
            types = (dict, list, tuple, set)
        self._types = types

    def call(self, arg):
        if dict in self._types and isinstance(arg, dict):
            return {key: self.call(value) for key, value in arg.items()}

        if set in self._types and isinstance(arg, set):
            return set(self.call(value) for value in arg)

        if tuple in self._types and isinstance(arg, tuple):
            return tuple(self.call(value) for value in arg)

        if list in self._types and isinstance(arg, list):
            return list(self.call(value) for value in arg)

        return self.method(arg)


@ApplyRecursive(tuple, set, dict)
def double(arg):
    return 2*arg

print(double(1))
print(double({'a': 1, 'b': 2}))
print(double({1, 2, 3}))
print(double((1, 2, 3, 4)))
print(double([1, 2, 3, 4, 5]))

哪個印刷品:

2
{'a': 2, 'b': 4}
{2, 4, 6}
(2, 4, 6, 8)
[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]

請注意,此示例未list在裝飾器的實例化中包含類型,因此在最終的print語句中,該方法將應用於列表本身,而不是列表的元素。


可以製作兩個單獨的裝飾器,它們可以執行您想要的操作,如下圖所示。注意在函數*args, **kwargs聲明中的使用,該wrapped()函數支持具有多個參數的修飾函數(這對於示例say()函數來說並不是必需的,但是為了通用性而包括在內)。

出於類似的原因,functools.wraps裝飾器用於將包裝函數的元屬性更改為正在裝飾的元素的元屬性。這使得錯誤消息和嵌入式函數文檔(func.__doc__)成為裝飾函數的函數而不是wrapped()'s。

from functools import wraps

def makebold(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return "<b>" + fn(*args, **kwargs) + "</b>"
    return wrapped

def makeitalic(fn):
    @wraps(fn)
    def wrapped(*args, **kwargs):
        return "<i>" + fn(*args, **kwargs) + "</i>"
    return wrapped

@makebold
@makeitalic
def say():
    return 'Hello'

print(say())  # -> <b><i>Hello</i></b>

改進

正如您所看到的,這兩個裝飾器中存在大量重複代碼。鑑於這種相似性,你最好製作一個實際上是裝飾工廠的通用工具- 換句話說,就是製作其他裝飾工的裝飾工。這樣可以減少代碼重複次數 - 並允許遵循DRY原則。

def html_deco(tag):
    def decorator(fn):
        @wraps(fn)
        def wrapped(*args, **kwargs):
            return '<%s>' % tag + fn(*args, **kwargs) + '</%s>' % tag
        return wrapped
    return decorator

@html_deco('b')
@html_deco('i')
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

為了使代碼更具可讀性,您可以為工廠生成的裝飾器分配更具描述性的名稱:

makebold = html_deco('b')
makeitalic = html_deco('i')

@makebold
@makeitalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

或者甚至將它們組合成這樣:

makebolditalic = lambda fn: makebold(makeitalic(fn))

@makebolditalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

效率

雖然上面的示例都可以正常工作,但是當一次應用多個裝飾器時,生成的代碼會以無關函數調用的形式涉及相當大的開銷。這可能無關緊要,具體取決於具體用法(例如,可能是I / O綁定)。

如果修飾函數的速度很重要,可以通過編寫稍微不同的裝飾器工廠函數來保持開銷到一個額外的函數調用,該函數實現一次添加所有標記,因此它可以生成代碼以避免發生的附加函數調用通過為每個標籤使用單獨的裝飾器。

這需要裝飾器本身有更多的代碼,但這只在它被應用於函數定義時運行,而不是在它們自己被調用時運行。當使用lambda前面所示的函數創建更可讀的名稱時,這也適用。樣品:

def multi_html_deco(*tags):
    start_tags, end_tags = [], []
    for tag in tags:
        start_tags.append('<%s>' % tag)
        end_tags.append('</%s>' % tag)
    start_tags = ''.join(start_tags)
    end_tags = ''.join(reversed(end_tags))

    def decorator(fn):
        @wraps(fn)
        def wrapped(*args, **kwargs):
            return start_tags + fn(*args, **kwargs) + end_tags
        return wrapped
    return decorator

makebolditalic = multi_html_deco('b', 'i')

@makebolditalic
def greet(whom=''):
    return 'Hello' + (' ' + whom) if whom else ''

print(greet('world'))  # -> <b><i>Hello world</i></b>

#decorator.py
def makeHtmlTag(tag, *args, **kwds):
    def real_decorator(fn):
        css_class = " class='{0}'".format(kwds["css_class"]) \
                                 if "css_class" in kwds else ""
        def wrapped(*args, **kwds):
            return "<"+tag+css_class+">" + fn(*args, **kwds) + "</"+tag+">"
        return wrapped
    # return decorator dont call it
    return real_decorator

@makeHtmlTag(tag="b", css_class="bold_css")
@makeHtmlTag(tag="i", css_class="italic_css")
def hello():
    return "hello world"

print hello()

你也可以在Class中編寫裝飾器

#class.py
class makeHtmlTagClass(object):
    def __init__(self, tag, css_class=""):
        self._tag = tag
        self._css_class = " class='{0}'".format(css_class) \
                                       if css_class != "" else ""

    def __call__(self, fn):
        def wrapped(*args, **kwargs):
            return "<" + self._tag + self._css_class+">"  \
                       + fn(*args, **kwargs) + "</" + self._tag + ">"
        return wrapped

@makeHtmlTagClass(tag="b", css_class="bold_css")
@makeHtmlTagClass(tag="i", css_class="italic_css")
def hello(name):
    return "Hello, {}".format(name)

print hello("Your name")




python-decorators