Python - 装饰器

17

我正在尝试学习装饰器。 我理解了它的概念,现在正在尝试实现它。

这里是我编写的代码 代码很简单。它只是检查传递给int的参数是否为整数。

def wrapper(func):
    def inner():
        if issubclass(x,int): pass
        else: return 'invalid values'

    return inner()

@wrapper
def add(x,y):
    return x+y

print add('a',2)

出现错误,提示“全局名称 'x' 未定义”。我知道它在inner下未定义,但不知道如何纠正此代码。我错在哪里?

5个回答

23
你的装饰器应该长这样:

def wrapper(func):
    def inner(x, y): # inner function needs parameters
        if issubclass(type(x), int): # maybe you looked for isinstance?
            return func(x, y) # call the wrapped function
        else: 
            return 'invalid values'
    return inner # return the inner function (don't call it)

请注意以下几点:

  • issubclass 函数的第一个参数应该是一个类(你可以用一个简单的 try/except TypeError 来替换它)。
  • 装饰器应该返回一个函数,而不是被调用函数的返回值。
  • 内部函数中应该实际调用被包装的函数。
  • 内部函数没有参数。

你可以在这里找到一个关于装饰器的好解释。


如果不使用isinstance(),我会使用type(x)而不是x.__class__ - Martijn Pieters
感谢您纠正代码。我对包装函数的概念有疑问。是否真的需要编写嵌套函数来检查值是否为整数。我的意思是,不能不使用嵌套函数就完成吗?例如:def wrapper(x,y): if issubclass(x.__class__, int): return func(x, y) else: return 'invalid values' - python-coder
@MartijnPieters 谢谢你的评论;使用type(x)比使用x.__class__更好。已修复。 - sloth
@python-coder 不行,那样做不起作用。当您将wrapper用作装饰器时,它将使用包装函数作为第一个参数进行调用。 - sloth
2
为了更加向上兼容,您可以使用灵活的参数列表:def inner(x, *args, **kwargs):return func(x, *args, **kwargs) - Alfe

3

我看到你当前的代码存在三个问题。

首先,你调用了inner函数,而不是返回对它的引用。

其次,你的inner函数没有像你要装饰的函数一样接收相同的参数。在这种情况下,你至少需要显式地传递x参数(某些内部函数可以完全使用*args**kwargs,但显然你的不能)。

最后,你从未调用过包装函数。虽然这并非严格要求(在开发期间,将方法替换为装饰器可能很有用),但通常你希望在内部函数的代码中某个时刻调用该函数。

因此,为了将所有内容包装在一起,我认为你想让你的代码像这样:

def wrapper(func):
    def inner(x, y):
        if issubclass(x, int): # issue 2
            return func(x, y) # issue 3
        else:
            return "invalid values" # consider raising an exception here instead!

    return inner # issue 1

谢谢纠正代码。我对包装函数的概念有疑问。是否确实需要编写嵌套函数来检查值是否为整数。我的意思是,不能不使用嵌套函数来完成吗?例如:def wrapper(x,y): if issubclass(x.__class__, int): return func(x, y) else: return 'invalid values' - python-coder
1
嵌套函数是使其作为装饰器工作所必需的。如果您希望代码的用户直接调用wrapper而不是按其通常名称调用func,则您的备选版本可能有效(尽管您需要在wrapper中直接命名它)。请注意,您对函数的命名有些不寻常。通常,外部函数的名称是根据装饰器的目的命名的(例如arg_type_check或其他名称),而wrapper是装饰器返回的内部函数的名称,"包裹"在原始函数周围。 - Blckknght

0

这可能会起作用。

def wrapper(func):
    def inner(*args,**kwargs):
        if ((args[0] is int) and (args[1] is int)): pass
        else: return 'invalid values'
    return inner
@wrapper
def add(x,y):
    return x+y
print add('a',2)

0

如果类型检查失败,您也可以引发异常以正确终止添加方法。就像这样

def check_int_types(func):
    def type_checker(x, y):
        if issubclass(type(x), int) and issubclass(type(y), int):
            return func(x, y)
        raise Exception("Invalid types: {}, {}".format(x, y))
    return type_checker

@check_int_types
def add(a, b):
    return a + b

def main():
    x = y = 15.0
    print add(x, y)

if __name__ == '__main__':
    main()

结果:

Traceback (most recent call last):
  File "deco_test.py", line 17, in <module>
    main()
  File "deco_test.py", line 14, in main
    print add(x, y)
  File "deco_test.py", line 5, in type_checker
    raise Exception("Invalid types: {}, {}".format(x, y))
Exception: Invalid types: 15.0, 15.0

-2

这个怎么样?

def wrapper(func):
    def inner():
        if isinstance(func,int):
                return func(x, y)
        else: return 'invalid values'

    return inner()

很糟糕。那个“wrapper”应该返回一个函数,以便作为装饰器使用。你的解决方案返回了对“inner”的调用结果,即你将返回对“func”的调用结果或字符串(“invalue values”)。两者都不是函数。 - Alfe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接