-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathwith.py
More file actions
125 lines (104 loc) · 6.58 KB
/
with.py
File metadata and controls
125 lines (104 loc) · 6.58 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
# -*- coding: utf-8 -*-
#-------------------------------------------------------------------------------
# Copyright (c) 2015 Artur Eganyan
#
# This work is provided "AS IS", WITHOUT ANY WARRANTY, express or implied.
#-------------------------------------------------------------------------------
# Кратко:
# - with позволяет обработать начало и конец блока кода
# - with использует менеджер контекста, у которого есть методы __enter__() и __exit__()
# - __enter__() вызывается перед выполнением блока кода
# - __exit__() вызывается после выполнения блока кода
# - менеджер контекста можно создать в виде генератора, используя декоратор @contextmanager
# Инструкция with позволяет обработать начало и конец выполнения блока кода:
#
# with <менеджер контекста> [as <переменная>]:
# <код>
#
# <менеджер контекста> - это объект с методами __enter__() и __exit__().
# Метод __enter__() вызывается перед выполнением <кода>, и его результат
# присваивается в <переменную>. Метод __exit__() вызывается после выполнения
# кода, независимо от того, как код завершился - нормально, исключением,
# возвратом (return) или прерыванием цикла (break/continue, если with
# внутри цикла).
#
# Хорошая статья про with: "The Python "with" Statement by Example",
# http://preshing.com/20110920/the-python-with-statement-by-example/
class MyContextManager:
def __enter__(self):
print u"Начало выполнения кода"
def __exit__(self, *args):
print u"Конец выполнения кода"
# Замечание: в with передается экземпляр менеджера, а не класс
with MyContextManager(): # Вызовет менеджер.__enter__()
print 1
print 2
print 3
# Вызовет менеджер.__exit__()
def f():
with MyContextManager() as c: # Вызовет менеджер.__enter__()
print 1
print 2
return # Здесь with будет прерван
print 3
# Вызовет менеджер.__exit__()
print u"Эта строка не выполнится"
f()
# Инструкция with очень похожа на try/except/finally, просто она удобнее для
# определенных задач. Если в блоке кода произойдет исключение, оно будет
# передано в __exit__() как три параметра: тип исключения, значение и стек
# вызовов. И если __exit__() вернет True, то будет считаться, что исключение
# обработано, а иначе оно будет сгенерировано повторно.
class MyContextManager():
def __enter__(self):
print u"Начало выполнения кода"
def __exit__(self, exc_type, exc_value, exc_traceback):
print u"Произошло исключение", exc_type, exc_value
print u"Конец выполнения кода"
return True
with MyContextManager():
print 1
raise Exception("test")
print 2
# Произошло исключение <type 'exceptions.Exception'> test
# Менеджер контекста можно создать и в виде генератора, поместив перед ним
# декоратор @contextmanager. У такого генератора yield должен выполняться
# ровно один раз, возвращая значение, которое будет связано с переменной в
# with (или возвращая None).
from contextlib import contextmanager
@contextmanager
def myContextManager():
print u"Начало выполнения кода"
yield # Здесь можно вернуть значение для переменной with
print u"Конец выполнения кода"
with myContextManager(): # Выполнит функцию до yield
print 1
print 2
print 3
# Выполнит функцию после yield
# Если в блоке кода with произойдет исключение, оно будет сгенерировано
# на инструкции yield. Там генератор может его перехватить. Если из
# генератора не выйдет никакого исключения, то оно будет считаться
# обработанным (аналогично возврату True из __exit__()).
@contextmanager
def myContextManager():
print u"Начало выполнения кода"
try:
yield
except Exception as e:
print u"Произошло исключение", e
finally:
print u"Конец выполнения кода"
with myContextManager(): # Выполнит функцию до yield
print 1
raise Exception("test")
print 2
# Выполнит функцию после yield, сгенерировав исключение
# (по сути, выполнит генератор.throw(исключение))
# Замечание: Судя по всему, вариант с генератором устроен примерно следующим
# образом. Декоратор @contextmanager преобразует генератор в класс "менеджер
# контекста". Этот менеджер в функции __enter__() вызывает генератор и
# возвращает результат yield, который with поместит в переменную (если она
# указана). В функции __exit__() менеджер вызовет генератор.next(), если не
# было исключения, или генератор.throw(), если было. Если при этом из
# генератора выйдет исключение, то __exit__() вернет False, а иначе - True.