Python中上下文管理器和装饰器的区别

20

这两者的主要区别是什么?我一直在学习Python并遇到了它们。装饰器本质上是一个将另一个函数包装起来的函数,您可以在特定函数执行之前和之后执行任何操作。

def my_decorator(some_function):
    def wrapper(*args, **kwargs):
        print("Do something before the function is called")
        some_function(*args, **kwargs)
        print("Do something after the function is called")

    return wrapper

@my_decorator
def addition(a, b):
    result = a+b
    print("Addition of {} and {} is {}".format(a,b,result))

但是在学习上下文管理器后,我不禁注意到它也具有 enter exit ,您可以在其中执行大多数相似操作。

from contextlib import contextmanager

@contextmanager
def open_file(path, mode):
    the_file = open(path, mode)
    yield the_file
    the_file.close()

files = []

for x in range(100000):
    with open_file('foo.txt', 'w') as infile:
        files.append(infile)

for f in files:
    if not f.closed:
        print('not closed')

在yield之前的所有部分都被视为"进入(enter)"的一部分,而yield之后的所有部分则被视为"退出(exit)"的一部分。

尽管上下文管理器和装饰器在语法上有所不同,但它们的行为可以看作是相似的。那么它们的区别是什么?在何种情况下应该使用其中之一?

4个回答

30
它们是完全独立的概念,不应该被视为同一光源下的东西。
装饰器让你在定义函数或类时"增强或替换函数或类"。这远比只在函数调用前或后执行某些操作要广泛得多。当然,你特定的装饰器可以让你在函数调用之前和之后做一些事情,前提是没有引发异常,或者你明确处理异常。但你也可以使用装饰器向函数对象添加属性,或更新某种注册表。或者返回完全不同的内容并忽略原始函数。或生成一个包装器,以操作传递的参数或原始函数的返回值。上下文管理器无法执行任何这些操作。
另一方面,上下文管理器让你抽象try: ... finally:构造,因为无论块如何结束,你都可以在块结束时执行更多的代码。即使块引发异常或使用return退出函数,上下文管理器__exit__方法仍将被调用,而且无论如何都会被调用。上下文管理器甚至可以压制在块中引发的任何异常。
这两个概念在其他方面完全没有关联。当你需要对定义的函数或类进行某些操作时,请使用装饰器。当你想在块结束后进行清理或采取其他操作时,请使用上下文管理器。

5
任何使用contextlib.contextmanager创建的上下文管理器都是装饰器,如此处所述:https://docs.python.org/3/library/contextlib.html#using-a-context-manager-as-a-function-decorator 上下文管理器可用于包装具有设置和拆卸步骤的代码。装饰器是一种更通用的构造方式,可以通过包装设置/拆卸逻辑等多种方式修改函数。因此,很自然地会问:为什么不能将上下文管理器用作装饰器?
实际上我们可以这样做,事实上,contextlib已经为您完成了这项工作。如果我们编写一个上下文管理器,就像这样:
from contextlib import contextmanager

@contextmanager
def my_context():
    print("setup")
    yield
    print("teardown")

我们可以在with块中将其作为上下文管理器使用,或者我们可以将其作为装饰器使用:

def foo():
    with my_context():
        print("foo ran")

@my_context()
def bar():
    print("bar ran")

>>> foo()
setup
foo ran
teardown
>>> bar()
setup
bar ran
teardown

你应该使用什么?

当你的封闭代码需要访问上下文管理器返回的对象时,例如文件处理,使用with块:

with open("my_file.txt") as file:
    file.read()  # needs access to the file object

当整个函数需要被包装在一个上下文中且不需要任何上下文变量时,请使用作为装饰器:

@contextmanager
def suppress_all_exceptions():  
    try: yield
    except: pass

@suppress_all_exceptions()
def div_by_zero():
    print("hi")
    x = 1 / 0  # exception suppressed

注意:也可以通过子类化contextlib.ContextDecorator来实现相同的功能:

class MyContext(contextlib.ContextDecorator):
    def __enter__(): ...
    def __exit__(*errs): ... 

1
他们是完全不同的概念。
上下文管理器是与Python中的with关键字一起使用的对象。它在进入块和退出块时运行代码。
装饰器是对函数或类定义的修改。它运行替换函数正在被定义的代码。
@D
def Y(...):
    ...

是另一种编写的方式。

def Y(...):
    ....
Y = D(Y)

1

很好的想法,确实这些概念有很多相似之处,但也存在重要的区别,所以把它们看作完全不同的概念更为稳妥。


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