python装饰器、with和contextmanager

本文介绍了Python中的装饰器概念及其应用,详细解释了如何利用装饰器增强函数的功能,并探讨了with语句及contextmanager装饰器在上下文管理中的作用。

1,装饰器: 

装饰器是一种简单的记号而已,有如下等价的转换规则(带不带参数的装饰器都一样,只要是一个可调用对象即可)。

@foo

def bar:

    pass

等价于:

x = foo

bar = x(bar)

'----------------'

@foo('abc')

def bar:

    pass

等价于:

x = foo('abc')

bar = x(bar)

'----------------'

@foo.X('abc').Y

def bar:

    pass

等价于:

x = foo.X('abc').Y

bar = x(bar)

真正起作用的是"函数式编程"方面的知识,包括闭包,LEGB访问规则,自由变量等。

装饰器的用处:返回一个增强版的函数,主要是对函数的上文和下文,都可以进行处理。

比如:参数检查,缓存数据(建立输入与输出的字典),代理,上下文提供者

from threading import RLock
lock = RLock()
def locker(function):
    def _locker(*args,**kw):
        lock.acquire()
        try:
            return function(*args,**kw)
        finally:
            lock.release()
    return _locker

@locker
def thread_safe():
    pass
以上装饰器的定义都没有问题,但还差最后一步!!

因为我们讲了函数也是对象,它有__name__等属性,但经过decorator装饰之后的函数,

它们的__name__已经从原来的'thread_safe'变成了'_locker'。所以不完美。

不需要直接编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的。

import functools
def locker(function):
    @functools.wraps(function)
    def _locker(*args,**kw):
       lock.acquire()
       try:
           return function(*args,**kw)
       finally:
           lock.release()
    return _locker

2,with语句:

任何实现了 __enter__和__exit__的类都可以和 with使用。
class Context(object):
    def __enter__(self):
        print 'entering the zone'
    def __exit__(self, exception_type, exception_value, 
                exception_traceback):
        print 'leaving the zone'
        if exception_type is None:
            print 'with no error'
        else:
            print 'with an error (%s)' % exception_value
        '异常是会被新的异常覆盖的
        raise TypeError('i am the second bug')  
try:
    with Context():
        print 'i am the buggy zone'
        raise TypeError('i am the bug')
except Exception,s:
    print 'I got :',Exception,s

3,contextmanager装饰器:

一种和yield结合使用的方便的上下文管理方式,切记yield是协程技术。

Typical usage: 
    # @contextmanager 
    def some_generator(<arguments>): 
        <setup> 
        try: 
            yield <value> 
        finally: 
            <cleanup> 

This makes this: 
    with some_generator(<arguments>) as <variable>: 
        <body> 

equivalent to this: 
    <setup> 
    try: 
        <variable> = <value> 
        <body> 
    finally: 
        <cleanup>
'---------自己实现contextmanager装饰器---------------'
class MyGeneratorContextManager(object):
    def __init__(self, gen):
        print("__init__ called")
        self.gen = gen
    def __enter__(self):
        print("__enter__ called")
        return self.gen.next()
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("__exit__called exc_type = %s, exc_val = %s, exc_tb = %s"\
              % (exc_type, exc_val, exc_tb))
        try:
            ' 即便不调用next也是没有关系的,只是不会做清理的处理,不会执行print("end foo"),但是finally还是程序结束前总会被执行的'
            return self.gen.next() 
        except StopIteration: 
            '是不会捕获with中的异常,但是这里抛出的异常可以覆盖with中抛出的异常!'
            print 'nothing to do with StopIteration'
        
def MyContextManager(func):
    def _deco(*args,**kwargs):
        print("func info:", func)
        return MyGeneratorContextManager(func(*args,**kwargs))
    return _deco

@MyContextManager
def foo(*args,**kwargs):
    try: # 尝试用老方法捕捉错误
        print("start foo:", args,kwargs)
        yield [1, 2, 3]
        print("end foo") # 需要调用self.gen.next()才能输出
    except (Exception, AssertionError):
        print("EXCEPTION ENCOUNTERED!")
    finally:
        print("FINALLY")

with foo("oh no!") as tmp: # tmp的值就是__enter__返回的值
    print("START WITH")
    print 'tmp:',tmp
    assert 1>2
    # 出错之后直接从with中跳出去,下面不可能被执行
    print("END WITH")

4,多个装饰器

#coding:utf-8  
def decorator1(func):  
	print 1
	def wrapper():  
		print 2
		func()
		print 3
	print 4		
	return wrapper  

def decorator2(func):  
	print 5
	def wrapper():  
		print 6       
		func()
		print 7	   
	print 8  
	return wrapper  
 
@decorator1  
@decorator2  
def test():  
	print 'hello python!'  
  
test()  # 等价于下面两行代码
# test = decorator2(test)
# test = decorator1(test)

<think>嗯,用户问的是Python中的@contextmanager的用法实现方式。首先,我得回忆一下这个装饰器的作用。它来自contextlib模块,对吧?用于创建上下文管理器,这样就不需要自己写一个类带__enter____exit__方法了。应该用生成器函数加上这个装饰器,把代码分成yield前后的部分。yield之前的部分相当于__enter__,之后的是__exit__。 那用户可能需要一个简单的例子来说明如何使用。比如打开文件,或者临时修改目录之类的常见场景。比如,写一个计时器的上下文管理器,记录代码块的执行时间。或者临时修改系统环境变量,执行完再恢复。这样的例子比较直观。 然后实现方式方面,可能需要解释@contextmanager如何将生成器函数转换为上下文管理器。当调用被装饰的函数时,它会返回一个生成器对象。装饰器内部的帮助函数处理__enter____exit__,在yield的时候暂停,返回资源,之后在退出上下文时执行yield之后的代码。 还要注意异常处理。用户可能需要知道如果在上下文块中发生异常,@contextmanager会怎样处理。默认情况下,异常会在生成器中重新抛出,所以需要在生成器函数里用try...finally来处理资源释放,确保即使有异常也能正确清理。 可能还需要提到,yield的值可以通过as关键字赋给变量。例如,在文件操作中,yield文件对象,这样用户就可以在with块中使用它。 用户可能还会混淆@contextmanager其他创建上下文管理器的方法,比如类的方式。需要比较两者的优缺点。使用装饰器更简洁,适合简单的资源管理,而类的方式更适合需要更复杂状态管理的情况。 另外,常见的错误可能包括忘记用try...finally来确保退出时的代码执行,或者在yield之后写了太多代码,导致资源释放不及时。需要提醒用户在这些地方注意。 最后,可能需要生成相关问题,比如如何管理异常,或者结合其他上下文管理器使用,或者性能方面的考虑。这样用户如果有进一步的问题,可以自行探索。</think>### Python @contextmanager 使用方法及示例 #### 1. 基础概念 `@contextmanager` 是 `contextlib` 模块提供的装饰器,用于将生成器函数快速转换为上下文管理器,替代传统的类实现方式(需定义 `__enter__` `__exit__` 方法)[^1]。 #### 2. 使用方法 1. **导入模块**: ```python from contextlib import contextmanager ``` 2. **定义生成器函数**: - `yield` 前的代码相当于 `__enter__`(资源分配) - `yield` 后的代码相当于 `__exit__`(资源释放) ```python @contextmanager def my_context(): print("Entering context") resource = allocate_resource() try: yield resource # 将资源传递给 with 代码块 finally: print("Exiting context") release_resource(resource) ``` #### 3. 使用示例 **示例1:文件操作** ```python @contextmanager def open_file(path, mode): f = open(path, mode) try: yield f # 返回文件对象供 with 块使用 finally: f.close() with open_file("test.txt", "w") as file: file.write("Hello World") ``` **示例2:计时器** ```python import time @contextmanager def timer(): start = time.time() try: yield # 无返回值 finally: print(f"Elapsed time: {time.time() - start:.2f}s") with timer(): time.sleep(1.5) # 输出: Elapsed time: 1.50s ``` #### 4. 异常处理机制 - 使用 `try...finally` 确保资源释放 - 若 with 块内发生异常,会在生成器中重新抛出: ```python @contextmanager def handle_error(): try: yield except Exception as e: print(f"Error handled: {e}") ``` #### 5. 实现原理 装饰器通过以下步骤实现: 1. 生成器函数被调用时返回生成器对象 2. `__enter__` 执行到 `yield` 并返回结果 3. `__exit__` 触发生成器继续执行后续代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值