如何实现装饰器功能

时间:2015-11-13 23:05:38

标签: python python-3.x decorator

我对装饰器和闭包是全新的,我正在尝试用一个简单的例子练习。执行时会引发错误:

NameError: name 'congratulate' is not defined

我需要改变什么?

"""
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'))

3 个答案:

答案 0 :(得分:3)

"""
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'))
理解装饰者的本质是

@f
def g(args)

=&GT;

f(g)(args)

答案 1 :(得分:1)

将congratulate()函数移到它正在装饰的函数(回文)之上。

答案 2 :(得分:1)

我知道我迟到了,但我想扩大。

如上所述,在这种情况下,NameError是由您在实际创建名称之前使用名称引起的。将congratulate()移至顶部可以解决此问题。

来自NameError的公寓,你有两个与装饰/功能相关的隐含逻辑错误

第一期:

  • if 中的congratulate条款始终评估为True;当字符串是回文时,你并不完全是在祝贺。

这是因为函数对象总是求值为True,因此if func:形式的条件将始终执行:

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

幸运的是,这很简单,可以通过实际调用函数if func("test string"):

轻松修复

第二期:

  • 这里的第二个问题不那么简单,可能是因为装饰器可以令人着迷。你实际上并没有使用 congratulate()应该使用装饰器的方式。

装饰器是一个可调用的返回一个可调用的(callable是像函数一样的东西,类在__call__上重载)。你的“装饰者”在这里做的只是接受一个函数对象,评估对象是否为True,然后打印祝贺。

最差的部分? 它还隐含地将名称palindrome重新绑定到None

同样,你可以在下一个片段中看到这种间接效应(押韵+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

很酷,我们的函数f已被修饰,但是,看看当我们尝试调用函数f时会发生什么:

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

TypeError: 'NoneType' object is not callable

是的,我们的函数对象f现已分配给None,即decor函数的返回值。

这是因为正如所指出的,@syntax直接等同于以下内容:

@decor
def f(): pass

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

因此我们必须确保装饰器的返回值是一个后来可以再次调用的对象,ergo,一个可调用的对象。

那你做什么?您可能会考虑的一个选项就是返回您传递的函数:

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

这将保证在装饰器在palindrome()函数上运行后,名称palindrome仍将映射到可调用对象。

问题?结果是一次性。当Python遇到你的装饰者和你的函数时,它会执行congratulate 一次,结果只会执行你的if子句一次

但是每次调用函数时都需要它来运行if !你能做些什么才能做到这一点?返回执行装饰函数函数(所谓的嵌套函数装饰器)。

通过执行此操作,您可以为名称palindrome创建一个新功能,此功能包含您的原始功能,您可以确保每次调用palindrome()时执行此功能。< / p>

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现在是一个发出对原始函数调用的函数。

装饰过程现在将palindrome名称分配给newFunc对象(请注意我们如何使用return newFunc将其返回。

因此,每次您执行palindrome()表单的调用时,都会转换为newFunc(),而func()会在其正文中调用func。 (如果你还和我在一起,我推荐你。)

这里的最终问题是什么?我们对palindrome()的参数进行了硬编码。按原样,每次拨打newFunc()功能func时,系统都会调用原始功能func("Test String"),其呼叫签名为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

palindrome('Rats live on no evil star')

现在,每次拨打newFunc('Rats live on no evil star')时,这都会转换为func('Rats live on no evil star')的来电,然后将if条款中的palindrome('Rats live on no evil star') Congratulations, it's a palindrome! 转移到您的func。< / p>

执行后,这非常有效,让你得到你想要的结果:

{{1}}

我希望你喜欢阅读,我相信我已经完成了(现在)!