forked from cool-RR/python_toolbox
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path__init__.py
More file actions
135 lines (99 loc) · 4.99 KB
/
__init__.py
File metadata and controls
135 lines (99 loc) · 4.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# Copyright 2009-2017 Ram Rachum.
# This program is distributed under the MIT license.
'''
Defines the `ContextManager` and `ContextManagerType` classes.
Using these classes to define context managers allows using such context
managers as decorators (in addition to their normal use) and supports writing
context managers in a new form called `manage_context`. (As well as the
original forms).
Inherit all your context managers from `ContextManager` (or decorate your
generator functions with `ContextManagerType`) to enjoy all the benefits
described below.
Defining context managers
-------------------------
There are 3 different ways in which context managers can be defined, and each
has their own advantages and disadvantages over the others.
1. The classic way to define a context manager is to define a class with
`__enter__` and `__exit__` methods. This is allowed, and if you do this
you should still inherit from `ContextManager`. Example:
class MyContextManager(ContextManager):
def __enter__(self):
pass # preparation
def __exit__(self, exc_type, exc_value, exc_traceback):
pass # cleanup
2. As a decorated generator, like so:
@ContextManagerType
def MyContextManager():
# preparation
try:
yield
finally:
pass # cleanup
The advantage of this approach is its brevity, and it may be a good fit for
relatively simple context managers that don't require defining an actual
class.
This usage is nothing new; it's also available when using the standard
library's `contextlib.contextmanager` decorator. One thing that is allowed
here that `contextlib` doesn't allow is to yield the context manager itself
by doing `yield SelfHook`.
3. The third and novel way is by defining a class with a `manage_context`
method which returns a generator. Example:
class MyContextManager(ContextManager):
def manage_context(self):
do_some_preparation()
with other_context_manager:
yield self
This approach is sometimes cleaner than defining `__enter__` and
`__exit__`; especially when using another context manager inside
`manage_context`. In our example we did `with other_context_manager` in our
`manage_context`, which is shorter, more idiomatic and less
double-underscore-y than the equivalent classic definition:
class MyContextManager:
def __enter__(self):
do_some_preparation()
other_context_manager.__enter__()
return self
def __exit__(self, *exc):
return other_context_manager.__exit__(*exc)
Another advantage of this approach over `__enter__` and `__exit__` is that
it's better at handling exceptions, since any exceptions would be raised
inside `manage_context` where we could `except` them, which is much more
idiomatic than the way `__exit__` handles exceptions, which is by receiving
their type and returning whether to swallow them or not.
These were the different ways of *defining* a context manager. Now let's see
the different ways of *using* a context manager:
Using context managers
----------------------
There are 2 different ways in which context managers can be used:
1. The plain old honest-to-Guido `with` keyword:
with MyContextManager() as my_context_manager:
do_stuff()
2. As a decorator to a function
@MyContextManager()
def do_stuff():
pass # doing stuff
When the `do_stuff` function will be called, the context manager will be
used. This functionality is also available in the standard library of
Python 3.2+ by using `contextlib.ContextDecorator`, but here it is combined
with all the other goodies given by `ContextManager`.
That's it. Inherit all your context managers from `ContextManager` (or decorate
your generator functions with `ContextManagerType`) to enjoy all these
benefits.
This package also defines a bunch of helpful classes and modules related to
context managers. See their docstrings for more info.
'''
# todo: review the few external tests that I'm skipping.
# todo: test using as abc with other abstract functions
# todo: can make a helpful exception message for when the user decorates with
# `ContextManager` instead of `ContextManagerType`
# todo: for case of decorated generator, possibly make getstate (or whatever)
# that will cause it to be pickled by reference to the decorated function
from .abstract_context_manager import AbstractContextManager
from .context_manager_type_type import ContextManagerTypeType
from .context_manager_type import ContextManagerType
from .context_manager import ContextManager
from .self_hook import SelfHook
from .blank_context_manager import BlankContextManager
from .delegating_context_manager import DelegatingContextManager
from .functions import nested
from .modifiers import as_idempotent, as_reentrant