5

有人可以彻底解释以下代码的最后一行:

def myMethod(self):
    # do something

myMethod = transformMethod(myMethod)

为什么要通过另一个方法传递一个方法的定义?那将如何运作?提前致谢!

4

5 回答 5

2

这是一个函数包装的例子,当你有一个函数接受一个函数作为参数,并返回一个修改原始函数行为的新函数时。

这是一个如何使用它的示例,这是一个简单的包装器,它只在每次调用时打印“Enter”和“Exit”:

def wrapper(func):
    def wrapped():
        print 'Enter'
        result = func()
        print 'Exit'
        return result
    return wrapped

这是一个如何使用它的示例:

>>> def say_hello():
...     print 'Hello'
... 
>>> say_hello()  # behavior before wrapping
Hello
>>> say_hello = wrapper(say_hello)
>>> say_hello()  # behavior after wrapping
Enter
Hello
Exit

为方便起见,Python 提供了装饰器语法,它只是函数包装的简写版本,在函数定义时做同样的事情,下面是如何使用它:

>>> @wrapper
... def say_hello():
...     print 'Hello'
... 
>>> say_hello()
Enter
Hello
Exit
于 2012-07-02T22:53:08.303 回答
2

为什么要通过另一个方法传递一个方法的定义?

因为你想修改它的行为。

那将如何运作?

完美,因为函数在 Python 中是一流的。

def decorator(f):
  def wrapper(*args, **kwargs):
    print 'Before!'
    res = f(*args, **kwargs)
    print 'After!'
    return res
  return wrapper

def somemethod():
  print 'During...'

somemethod = decorator(somemethod)

somemethod()
于 2012-07-02T22:43:15.150 回答
1

您描述的是装饰器,一种方法/功能修改的形式,可以使用装饰器的特殊语法更容易地完成。

你描述的相当于

@transformMethod
def myMethod(self):
    # do something

装饰器的使用非常广泛,例如以@staticmethod@classmethod@functools.wraps()等的形式@contextlib.contextmanager等等。

由于某个 P​​ython 版本(我认为是 2.6),类也可以被装饰。

两种装饰器都允许返回甚至不是函数或类的对象。例如,您可以装饰生成器函数,将其变成字典、集合或其他任何东西。

apply = lambda tobeapplied: lambda f: tobeapplied(f())

@apply(dict)
def mydict():
    yield 'key1', 'value1'
    yield 'key2', 'value2'
print mydict

@apply(set)
def myset():
    yield 1
    yield 2
    yield 1
    yield 4
    yield 2
    yield 7
print myset

我在这里做什么?

我创建了一个函数,它接受一个“要应用的东西”,然后返回另一个函数。

这个“内部”函数接受要修饰的函数,调用它并将其结果放入外部函数并返回此结果。

f()返回一个生成器对象,然后将其放入dict()or set()

于 2012-07-02T22:48:07.723 回答
0

在您最初的问题中,您问“为什么要通过另一种方法传递方法的定义?” 然后,在评论中,您问“为什么不直接修改方法的实际源代码?” 我实际上认为这是一个非常好的问题,而且如果不挥手就很难回答,因为装饰器只有在您的代码达到一定程度的复杂性时才会变得真正有用​​。但是,如果您考虑以下两个功能,我认为装饰器的意义会变得更加清晰:

def add_x_to_sequence(x, seq):
    result = []
    for i in seq:
        result.append(i + x)
    return result

def subtract_x_from_sequence(x, seq):
    result = []
    for i in seq:
        result.append(i - x)
    return result

现在,这两个示例函数有一些缺陷——例如,在现实生活中,你可能只是将它们重写为列表推导式——但让我们暂时忽略明显的缺陷,假设我们必须这样写,作为for循环遍历序列。我们现在面临的问题是,我们的两个函数做几乎相同的事情,只是在一个关键时刻有所不同。这意味着我们在这里重复自己!这是一个问题。现在我们必须维护更多的代码行,为 bug 的出现留出更多的空间,并为 bug 出现后隐藏的空间留出更多的空间。

解决这个问题的一种经典方法可能是创建一个函数,该函数接受一个函数,并将其应用于一个序列,如下所示:

def my_map(func, x, seq):
    result = []
    for i in seq:
        result.append(func(i, x))
    return result

现在我们所要做的就是定义要传递给的特定函数my_map(这实际上只是内置map函数的一个特殊版本)。

def sub(a, b):
    return a - b

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

我们可以像这样使用它们:

added = my_map(sub, x, seq)

但是这种方法有它的问题。例如,它比我们原来的独立函数更难阅读;每次我们想要x从项目列表中添加或减去时,我们都必须将函数和值指定为参数。如果我们经常这样做,我们宁愿有一个始终引用相同操作的单个函数名称——这将提高可读性,并使我们更容易理解代码中发生的事情。我们可以将上面的内容包装在另一个函数中......

def add_x_to_sequence(x, seq):
    return my_map(add, x, seq)

但现在我们又在重复自己了!而且我们还创建了大量的函数,使我们的命名空间变得混乱。

装饰器提供了解决这些问题的方法。我们可以传递一次,而不是每次都将一个函数传递给另一个函数。首先我们定义一个包装函数:

def vectorize(func):
    def wrapper(x, seq):
        result = []
        for i in seq:
            result.append(func(i, x))
        return result
    return wrapper

现在我们所要做的就是定义一个函数并将其传递给上面,包装它:

def add_x_to_sequence(a, b):
    return a + b
add_x_to_sequence = vectorize(add_x_to_sequence)

或者,使用装饰器语法:

@vectorize
def add_x_to_sequence(a, b):
    return a + b

现在我们可以编写许多不同的vectorized 函数,所有这些函数的for逻辑发生在一个地方。现在我们不必单独修复或优化许多不同的功能;我们所有与循环相关的错误和与循环相关的优化都发生在同一个地方;而且我们仍然获得了特殊定义函数的所有可读性优势。

于 2012-07-03T01:10:10.793 回答
0

你需要明白,在 Python 中,一切都是对象。函数是一个对象。您可以对函数对象执行与其他类型对象相同的操作:存储在列表中、存储在字典中、从函数调用返回,等等。

像您展示的代码的通常原因是“包装”另一个函数对象。例如,这是一个打印函数返回值的包装器。

def print_wrapper(fn):
    def new_wrapped_fn(*args):
        x = fn(*args)
        print("return value of %s is: %s" % (fn.__name__, repr(x)))
        return x
    return new_wrapped_fn

def test(a, b):
    return a * b

test = print_wrapper(test)

test(2, 3)  # prints:  return value of test is: 6

这是一项非常有用的任务,也是一项非常常见的任务,Python 对它有特殊的支持。谷歌搜索“Python 装饰器”。

于 2012-07-02T22:51:10.217 回答