Sunday, September 16, 2012

[Python] with - context manager

1. Context manager
Context manager : là một đối tượng chịu trách nhiệm đóng resource khi resource ấy không cần dùng nữa.

Khi sử dụng context manager, người dùng (developer) sẽ chỉ cần khai báo và sử dụng resource, không cần lo phải đóng nó lại.

file là một ví dụ, bình thường khi dùng file, ta sẽ phải mở - dùng - đóng. Từ phiên bản python2.5 trở đi, file đã được implement để trở thành 1 context manager. Khi dùng nó với câu lệnh with, ta không cần đóng nó nữa:

>>> with open("/etc/hosts") as f:
...     f.read()
...
'127.0.0.1\tlappy\n::1\t\tlappy ip6-localhost ip6-loopback\nfe00::0\t\tip6-localnet\nff00::0\t\tip6-mcastprefix\nff02::1\t\tip6-allnodes\nff02::2\t\tip6-allrouters\n\n'
>>> f.read()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file
>>>
Một cách hàn lâm hơn, dưới đây là định nghĩa về context manager:
A context manager is an object that defines the runtime context to be established when executing a with statement. The context manager handles the entry into, and the exit from, the desired runtime context for the execution of the block of code. Context managers are normally invoked using the with statement (described in section The with statement), but can also be used by directly invoking their methods.
Typical uses of context managers include saving and restoring various kinds of global state, locking and unlocking resources, closing opened files, etc.

Để một đối tượng trở thành 1 context manager, nó cần implement giao thức "context management" hay nói cách khác. Chỉ cần thêm vào đối tượng đó 2 method:
__enter__() : định nghĩa một runtime context
__exit__(): thoát khỏi runtime context
khi đó đối tượng ấy trở thành 1 context manager và có khả năng định nghĩa 1 "runtime context".
Ví dụ:
từ python2.5 trở đi, mỗi file là một context manager:
>>> afile = open("/etc/hosts")
>>> type(afile)
<type 'file'>
>>> dir(afile)
['__class__', '__delattr__', '__doc__', '__enter__', '__exit__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'closed', 'encoding', 'errors', 'fileno', 'flush', 'isatty', 'mode', 'name', 'newlines', 'next', 'read', 'readinto', 'readline', 'readlines', 'seek', 'softspace', 'tell', 'truncate', 'write', 'writelines', 'xreadlines']

Đối với class file,
__enter__ trả về chính đối tượng gọi nó (return self)
__exit__ thực hiện đóng file (self.close())



2. with
Dạng của câu lệnh with
with_stmt ::=  "with" with_item ("," with_item)* ":" suite
with_item ::=  expression ["as" target]
 
Trong lập trình, bạn sẽ thường xuyên phải dùng đến đoạn code có dạng dưới :
    set things up
    try:
        do something
    finally:
        tear things down
 
Từ khóa with được đưa ra để viết nó dưới dạng ngắn gọn hơn bằng việc sử dụng context manager. Đối tượng ngay sau with (with_item) phải là một context manager.

Qúa trình chạy một câu lệnh with:
The execution of the with statement with one “item” proceeds as follows:
  1. The context expression (the expression given in the with_item) is evaluated to obtain a context manager.
  2. The context manager’s __exit__() is loaded for later use.
  3. The context manager’s __enter__() method is invoked.
  4. If a target was included in the with statement, the return value from __enter__() is assigned to it.
    Note
    The with statement guarantees that if the __enter__() method returns without an error, then __exit__() will always be called. Thus, if an error occurs during the assignment to the target list, it will be treated the same as an error occurring within the suite would be. See step 6 below.
  5. The suite is executed.
  6. The context manager’s __exit__() method is invoked. If an exception caused the suite to be exited, its type, value, and traceback are passed as arguments to __exit__(). Otherwise, three None arguments are supplied.
    If the suite was exited due to an exception, and the return value from the __exit__() method was false, the exception is reraised. If the return value was true, the exception is suppressed, and execution continues with the statement following the with statement.
    If the suite was exited for any reason other than an exception, the return value from __exit__() is ignored, and execution proceeds at the normal location for the kind of exit that was taken.
Lệnh open(path_of_file) trả về 1 context manager nên được gọi là context expression.

Từ phiên bản 2.7, python cho phép sử dụng nhiều context manager lồng nhau với câu lệnh with thay vì sử dụng contextlib.nested() :

with A() as a, B() as b:
    suite

3. contextlib
contextlib hỗ trợ tạo các contextmanager để dùng với câu lệnh with
thư viện này chứa 1 decorator là contextmanager() cho phép bạn viết một hàm generator thay vì định nghĩa 1 class mới.Generator này chỉ được yield đúng 1 lần:
from contextlib import contextmanager

@contextmanager
def tag(name):
    print "<%s>" % name
    yield
    print "</%s>" % name

>>> with tag("h1"):
...    print "foo"
...
<h1>
foo
</h1>

đoạn code trước yield sẽ chạy như method __enter()__ và giá trị trả về sẽ được gán cho biến theo sau từ khóa "as" của lệnh "with".
Code sau yeild sẽ chạy như __exit__(). Các exception sẽ được raise bởi lệnh yield

~~~~
PS:
CHÚ Ý: context manager vẫn tồn tại ngoài khối lệnh with

>>> f = 12
>>> print f
12
>>> with open("/etc/hosts") as f:
...     print f
...
<open file '/etc/hosts', mode 'r' at 0x7f54e54cb390>
>>> print f
<closed file '/etc/hosts', mode 'r' at 0x7f54e54cb390>

http://docs.python.org/reference/compound_stmts.html
http://docs.python.org/library/contextlib.html
http://requires-thinking.blogspot.com/2009/02/this-is-python-context-managers-and.html

No comments: