简体   繁体   English

如何实现装饰器功能

[英]How to implement a decorator function

I'm brand-new to decorators and closures, I'm trying to practice with a simple example. 我对装饰器和闭合器是全新的,我尝试通过一个简单的示例进行练习。 When executed it raises an error of: 执行时会引发以下错误:

NameError: name 'congratulate' is not defined

What do I need to change? 我需要更改什么?

"""
A recursive function to check if a string is a palindrome.
"""

@congratulate
def palindrome(phrase):
    characters = [char.lower() for char in phrase if char.isalpha()]
    chars_len = len(characters)

    out1 = characters[0]
    out2 = characters[-1]

    if chars_len <= 2:
        return out1 == out2
    else:
        if out1 == out2:
            return palindrome(characters[1:-1])
        else:
            return False


def congratulate(func):
    if func:
        print('Congratulations, it\'s a palindrome!')


if __name__ == '__main__':
    print(palindrome('Rats live on no evil star'))
"""
A recursive function to check if a string is a palindrome.
"""

def congratulate(func):
    def wrapper(*argv, **kargs):
        result = func(*argv, **kargs)
        if result:
            print('Congratulations, it\'s a palindrome!')
        return result

    return wrapper

@congratulate
def palindrome(phrase):
    characters = [char.lower() for char in phrase if char.isalpha()]
    chars_len = len(characters)

    out1 = characters[0]
    out2 = characters[-1]

    if chars_len <= 2:
        return out1 == out2
    else:
        if out1 == out2:
            return palindrome(characters[1:-1])
        else:
            return False



if __name__ == '__main__':
    print(palindrome('Rats live on no evil star'))

the essence of understanding decorator is 了解装饰器的本质是

@f
def g(args)

=> =>

f(g)(args)

将congratulate()函数移至正在装饰的函数上方(回文)。

I know I'm late to the party, but I want to expand. 我知道我参加晚会很晚,但我想扩大规模。

As noted, the NameError in this case is caused by the fact that you use a name before you actually create one. 如前所述,在这种情况下, NameError是由您在实际创建名称之前使用名称引起的。 Moving congratulate() to the top remedies this. congratulate()移到顶部可解决此问题。


Appart from the NameError you have two implicit Logic Errors relating to Decorator/Function Functionality: NameError Appart中,您有两个与装饰器/功能功能有关的隐式逻辑错误


First Issue: 首要问题:

  • Your if clause in congratulate always evaluates to True ; congratulate您的if子句始终求值为True ; you aren't exactly congratulating when a string is a palindrome. 当字符串是回文符时,您并不会完全祝贺您。

This is caused by the fact that function objects always evaluate to True , so a condition of the form if func: will always execute: 这是由于以下事实造成的:函数对象始终求值为True ,因此条件形式为if func:的条件将始终执行:

def f(): 
    pass
if f: 
    print("I'm true!")  
# Prints: I'm true!

This is thankfully trivial and can easily be fixed by actually calling the function if func("test string"): 值得庆幸的是,这是微不足道的,并且可以通过实际调用if func("test string"):函数来轻松解决if func("test string"):


Second Issue: 第二期:

  • The second issue here is less trivial and probably caused by the fact that decorators can be comfusing. 这里的第二个问题不那么琐碎,可能是由于装饰者可能会感到困惑。 You aren't actually using congratulate() the way decorators are supposed to be used. 您实际上并没有按照应该使用装饰器的方式使用congratulate()

A decorator is a callable that returns a callable (callables are things like functions, classes overloaded on __call__ ). 装饰器是一个可调用函数, 返回调用函数(可调用函数是诸如函数,在__call__重载的类之类的东西)。 What your 'decorator' is doing here is simply accepting a function object, evaluating if the object is True and then printing congratulations. 您的“装饰器”在这里所做的只是简单地接受一个函数对象,评估该对象是否为True ,然后打印出祝贺。

Worst part? 最糟糕的部分? It is also implicitly rebinding the name palindrome to None . 它还隐式地将palindrome名称重新绑定为None

Again, you can see this indirect effect (+1 for rhyming) in this next snippet: 同样,您可以在下一个代码段中看到这种间接效果(押韵为+1):

def decor(f):
     if f: print("Decorating can be tricky")        

@decor
def f(): 
    print("Do I even Exist afterwards?")

# When executed, this prints:
Decorating can be tricky

Cool, our function f has been decorated, but, look what happens when we try calling our function f : 很酷,我们的函数f已经过修饰,但是,看看我们尝试调用函数f会发生什么:

f()
TypeError                                 Traceback (most recent call last)
<ipython-input-31-0ec059b9bfe1> in <module>()
----> 1 f()

TypeError: 'NoneType' object is not callable

Yes, our function object f has now been assigned to None , the return value of our decor function. 是的,我们的函数对象f现在已分配给了decor函数的返回值None

This happens because as pointed out, the @syntax is directly equivalent to the following: 发生这种情况是因为@syntax@syntax直接等效于以下内容:

@decor
def f(): pass

# similar to 
f = decor(f)  # we re-assign the name f!

Because of this we must make sure the return value of a decorator is an object that can afterwards be called again, ergo, a callable object. 因此,我们必须确保装饰器的返回值是一个可以随后再次调用的对象ergo,这是一个可调用的对象。


So what do you do? 所以你会怎么做? One option you might consider would be simply returning the function you passed: 您可能会考虑的一种选择就是简单地返回您传递的函数:

def congratulate(func):
    if func("A test Phrase!"):
        print('Congratulations, it\'s a palindrome!')
    return func

This will guarantee that after the decorator runs on your palindrome() function, the name palindrome is still going to map to a callable object. 这将确保装饰器在您的palindrome()函数上运行之后,名称palindrome仍将映射到可调用对象。

The problem? 问题? This turns out to be a one-time ride . 事实证明这是一次性的 When Python encounters your decorator and your function, it's going to execute congratulate once and as a result only going to execute your if clause once . 当Python遇到您的装饰器和函数时,它将执行一次 congratulate ,因此仅执行一次 if子句。

But you need it to run this if every time your function is called ! 但是, if 每次调用函数,都需要它来运行它! What can you do in order to accomplish this? 为了做到这一点,您能做什么? Return a function that executes the decorated function (so called nested function decorators). 返回执行装饰功能 (所谓的嵌套函数装饰器)的功能

By doing this you create a new function for the name palindrome and this function contains your original function which you make sure is executed each time palindrome() is called. 这样,您将为名称palindrome创建一个新函数,并且该函数包含您的原始函数,该函数确保每次调用palindrome()时都会执行该函数。

def congratulate(func):  # grabs your decorated function
    # a new function that uses the original decorated function
    def newFunc():
        # Use the function
        if func("Test string"):
            print('Congratulations, it\'s a palindrome!')
    # Return the function that uses the original function
    return newFunc

newFunc is now a function that issues calls to your original function. newFunc现在是一个函数,它发出对原始函数的调用。

The decoration process now assigns the palindrome name to the newFunc object (notice how we returned it with return newFunc . 装饰过程现在将palindrome名称分配给newFunc对象(注意我们如何通过return newFunc返回它。

As a result, each time you execute a call of the form palindrome() this is tranlated to newFunc() which in turn calls func() in its body. 结果,每次执行palindrome()形式的调用时,该调用都会转换为newFunc() ,后者又在其主体中调用func() (If you're still with me I commend you). (如果你仍然和我在一起,我赞扬你)。


What's the final issue here? 最后的问题是什么? We've hard-coded the parameters for func . 我们已经对func的参数进行了硬编码。 As is, everytime you call palindrome() function newFunc() will call your original function func with a call signature of func("Test String") , which is not what we want, we need to be able to pass parameters. newFunc() ,每次调用palindrome()函数newFunc()都会使用func("Test String")的调用签名调用原始函数func ,这不是我们想要的,我们需要能够传递参数。

What's the solution? 有什么解决方案? Thankfully, this is simple: Pass an argument to newFunc() which will then pass the argument to func() : 幸运的是,这很简单:将参数传递给newFunc() ,然后将其传递给func()

def congratulate(func):  # grabs your decorated function
    # a new function that uses the original decorated function
    # we pass the required argument <phrase>
    def newFunc(phrase):
        # Use the function
        # we use the argument <phrase>
        if func(phrase):
            print('Congratulations, it\'s a palindrome!')
    # Return the function that uses the original function
    return newFunc

Now, everytime you call palindrome('Rats live on no evil star') this will translate to a call of newFunc('Rats live on no evil star') which will then transfer that call to your func as func('Rats live on no evil star') in the if clause. 现在,每当您调用palindrome('Rats live on no evil star')这都会转换为对newFunc('Rats live on no evil star')调用,该调用将以func('Rats live on no evil star') if子句中func('Rats live on no evil star')

After execution, this works wonderfully and get's you the result you wanted: 执行后,这将非常有效,并为您带来所需的结果:

palindrome('Rats live on no evil star')
Congratulations, it's a palindrome!

I hope you enjoy reading, I believe I'm done (for now)! 希望您喜欢阅读,我相信我已经完成了!

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

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