Contextlib - Data Classes#

在 Python 中上下文管理器 (context) 语法 with context_manager_constructor(...) as obj: 语法是一个非常强大的语言特性, 也是一种非常优雅的资源管理方式. 官方标准库 contextlib 提供了一些工具函数, 用于简化上下文管理器的创建.

contextmanager#

test_contextmanager_1.py
 1# -*- coding: utf-8 -*-
 2
 3"""
 4这个例子展示了 contextmanager 的基本用法. 如果你用它来装饰一个简单函数, 那么这个函数的入参和
 5yield 的值的 type hint 都会正常工作.
 6"""
 7
 8import contextlib
 9
10
11class User:
12    def __init__(self, name: str):
13        self.name = name
14
15    def say_hello(self):
16        print(f"Hello, I am {self.name}")
17
18
19@contextlib.contextmanager
20def start(name: str):
21    try:
22        print("Start")
23        user = User(name=name)
24        yield user
25        print("Happy End")
26    except Exception as e:
27        print(f"Exception: {e}")
28        raise e
29    finally:
30        print("Finally")
31
32
33# type hint in ``start()`` is working fine
34with start(name="Alice") as user:
35    # type hint in ``user`` is working fine
36    user.say_hello()
37
38"""
39Output:
40
41Start
42Hello, I am Alice
43Happy End
44Finally
45"""
46
47# type hint in ``start()`` is working fine
48with start(name="Alice") as user:
49    # type hint in ``user`` is working fine
50    user.say_hello()
51    raise ValueError("Something went wrong")
52
53"""
54Output:
55
56Start
57Hello, I am Alice
58Exception: Something went wrong
59Finally
60Traceback (most recent call last):
61  File "/path/to/learn_pylib-project/docs/source/contextlib/test_contextmanager_1.py", line 42, in <module>
62    raise ValueError("Something went wrong")
63ValueError: Something went wrong
64"""
test_contextmanager_2.py
 1# -*- coding: utf-8 -*-
 2
 3"""
 4这个例子展示了 contextmanager 和 classmethod 一起使用时的用法. @classmethod 必须在上面.
 5由于标准库实现的时候还没有 type hint, 这个函数的入参和 yield 的值的 type hint 都 **不会** 正常工作.
 6"""
 7
 8import typing as T
 9import contextlib
10
11
12class User:
13    def __init__(self, name: str):
14        self.name = name
15
16    def say_hello(self):
17        print(f"Hello, I am {self.name}")
18
19    @classmethod
20    @contextlib.contextmanager
21    def start(cls, name: str) -> T.Generator["User", None, None]:
22        try:
23            print("Start")
24            user = cls(name=name)
25            yield user
26            print("Happy End")
27        except Exception as e:
28            print(f"Exception: {e}")
29            raise e
30        finally:
31            print("Finally")
32
33
34# type hint in ``start()`` is NOT working fine in PyCharm
35with User.start(name="Alice") as user:
36    # type hint in ``user`` is NOT working fine in PyCharm
37    user.say_hello()
38
39"""
40Output:
41
42Start
43Hello, I am Alice
44Happy End
45Finally
46"""
test_contextmanager_3.py
 1# -*- coding: utf-8 -*-
 2
 3"""
 4这个例子展示了 contextmanager 和 classmethod 一起使用时的用法. @classmethod 必须在上面.
 5社区有一个库 `decorator <https://pypi.org/project/decorator/>`_ 对标准库做了一些改进,
 6如果你使用的是 ``decorator.contextmanager`` 那么这个函数的入参和 yield 的值的 type hint 都 **会** 正常工作.
 7"""
 8
 9from decorator import contextmanager
10
11
12class User:
13    def __init__(self, name: str):
14        self.name = name
15
16    def say_hello(self):
17        print(f"Hello, I am {self.name}")
18
19    @classmethod
20    @contextmanager
21    def start(cls, name: str):  # 无需显式使用 ``-> T.Generator["User", None, None]``
22        try:
23            print("Start")
24            user = cls(name=name)
25            yield user
26            print("Happy End")
27        except Exception as e:
28            print(f"Exception: {e}")
29            raise e
30        finally:
31            print("Finally")
32
33
34# type hint in ``start()`` is working fine in PyCharm
35with User.start(name="Alice") as user:
36    user.say_hello()  # type hint in ``user`` is working fine in PyCharm
37
38"""
39Output:
40
41Start
42Hello, I am Alice
43Happy End
44Finally
45"""