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

问题描述 投票:0回答:3

两者之间的主要区别是什么?我一直在研究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))

但是在研究了上下文管理器之后,我不禁注意到它也有一个 enterexit ,你可以在其中执行大多数类似的操作。

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之前的所有内容都被视为“进入”的一部分,而之后的所有内容都被视为“退出”的一部分。

尽管上下文管理器和装饰器在语法上不同,但它们的行为可以被视为相似。那么区别是什么呢?当人们应该使用其中任何一个时,有哪些不同的场景?

python python-decorators contextmanager
3个回答
29
投票

它们是完全独立的概念,不应以同样的眼光看待。

装饰器可让您在定义时增强或替换函数或类。这比在函数调用之前或之后执行事情要广泛得多。当然,您的特定装饰器允许您在函数调用之前和之后执行某些操作,假设没有引发异常,或者您显式处理异常。但是您也可以使用装饰器向函数对象添加属性,或者更新某种注册表。或者返回完全不同的东西并忽略原始函数。或者生成一个包装器来操作传入的参数或原始函数的返回值。上下文管理器不能做任何这些事情。

另一方面,上下文管理器可以让您抽象出

try: ... finally:

构造
,因为无论块如何退出,您都可以在块的末尾执行更多代码。即使块引发异常,或者使用 return
 退出函数,上下文管理器 
__exit__
 方法仍然会被调用,
无论如何。上下文管理器甚至可以抑制块中引发的任何异常。

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


4
投票
使用

contextlib.contextmanager

 
is 创建的任何上下文管理器也是一个装饰器,如下所述:https://docs.python.org/3/library/contextlib.html#using-a-context-manager-as-a -函数装饰器

上下文管理器可用于通过设置和拆卸步骤来包装代码。装饰器是一种更通用的构造,允许我们以多种方式修改函数,包括用设置/拆卸逻辑包装它们。因此,很自然地会问:为什么我们不能使用上下文管理器作为装饰器?

我们可以,事实上 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)
    
© www.soinside.com 2019 - 2024. All rights reserved.