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

Mit*_*ras 7 python contextmanager python-decorators

两者的主要区别是什么?我一直在研究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))
Run Code Online (Sandbox Code Playgroud)

但是学习完Context Manager之后,我不禁注意到它也具有进入退出的功能,您可以在其中执行大多数类似的操作。

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')
Run Code Online (Sandbox Code Playgroud)

收益率之前的所有内容都被视为“输入”的一部分,而收益率部分之后的所有内容均被视为“退出”的一部分。

尽管上下文管理器和装饰器在语法上都不同,但是可以将它们的行为视为相似。那么区别是什么呢?什么时候应该使用其中任何一种,会有哪些不同的方案?

Mar*_*ers 10

它们是完全独立的概念,不应一视同仁。

装饰器使您可以在定义函数或类时对其进行扩充或替换。这远不止是在函数调用之前或之后执行事情。当然,您可以使用特定的装饰器在函数调用之前和之后执行某些操作,前提是不会引发异常,也可以显式处理异常。但是,您也可以使用装饰器向功能对象添加属性,或更新某种注册表。或返回完全不同的内容并忽略原始功能。或产生一个包装器来处理传入的参数或原始函数的返回值。上下文管理器无法执行任何这些操作。

另一方面,上下文管理器使您可以抽象出try: ... finally:结构无论块如何退出,都可以在块末执行更多代码。即使该块引发异常或用于return退出函数__exit__无论如何,上下文管理器方法仍将被调用。上下文管理器甚至可以抑制块中引发的任何异常。

否则,这两个概念根本不相关。当需要在定义函数或类时对其进行处理时,请使用装饰器。当您要清除或在块结束后执行其他操作时,请使用上下文管理器。


aja*_*nss 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")
Run Code Online (Sandbox Code Playgroud)

with我们可以将其用作块中的上下文管理器,也可以将其用作装饰器:

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

@my_context()
def bar():
    print("bar ran")
Run Code Online (Sandbox Code Playgroud)
>>> foo()
setup
foo ran
teardown
>>> bar()
setup
bar ran
teardown
Run Code Online (Sandbox Code Playgroud)

你应该使用哪个?

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

with open("my_file.txt") as file:
    file.read()  # needs access to the file object
Run Code Online (Sandbox Code Playgroud)

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

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

@suppress_all_exceptions()
def div_by_zero():
    print("hi")
    x = 1 / 0  # exception suppressed
Run Code Online (Sandbox Code Playgroud)

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

class MyContext(contextlib.ContextDecorator):
    def __enter__(): ...
    def __exit__(*errs): ... 
Run Code Online (Sandbox Code Playgroud)