每日一记:(2016-12-02)Python:ContextManager(上下文管理器)

今天的日志就来说说python的一个优雅的语法糖——ContextManager(上下文管理器)

1. 那么什么叫ContextManager

上下文管理器的任务是 – 代码块执行前准备,代码块执行后收拾。其中代码块执行后包括了正常退出和产生异常。
当然这些功能可以用try-except语句实现,但是如果使用try-except代码就显得不那么优雅。而且经常会忘记会产生异常这种情况。

2. 如何使用上下文管理器?

先给个例子吧!
>>> with open('test.txt', 'w') as writer:
...     writer.write("aaaa")
...     writer.write('bbbb')
其实就是with语句啦,使用的时候也是很简单的说。当然也可以没有as之后的内容单独一个with open()也是可以的。

3. 自定义上下文管理器

要实现上下文管理器,必须实现两个方法 – 一个负责进入语句块的准备操作,另一个负责离开语句块的善后操作。
要实现上下文管理器,需要实现两个魔法方法__enter__以及__exit__(双下划线作为前缀及后缀)。

当一个对象被用作上下文管理器时:

__enter__ 方法将在进入代码块前被调用。

__exit__ 方法则在离开代码块之后被调用(即使在代码块中遇到了异常)。

下面是上下文管理器的一个例子,我们通过例子看一下__enter__、__exit__都是什么时候被调用的吧

>>> class PypixContextManagerDemo:
...     def __enter__(self):
...             print 'Entering the block'
...     def __exit__(self, *unseted):
...             print 'Exiting the block'
...
>>> with PypixContextManagerDemo():
...     print 'In the block'
...
  Entering the block
  In the block
  Exiting the block
这里有一个疑问:这上面都是对于类来说实现了上下文管理器,但是对于open(处理文件的函数)他就是一个函数,为啥也能用上下文管理器呢?
上面的例子还有几点不完善的地方:
  • 没有传递任何参数。
  • 在此没有使用“as”关键词。
  • 稍后我们将讨论__exit__方法的参数设置。
...     def __init__(self, filename, mode):
...             self.filename = filename
...             self.mode = mode
...     def __enter__(self):
...             self.openedFile = open(self.filename, self.mode)
...             return self.openedFile
...     def __exit__(self, *unseted):
...             self.openedFile.close()
...
>>> with PypixOpen(filename, mode) as writer:
...     writer.write("Hello world from our new Context Manager")
那么,上面解决了输入参数和as之后赋值问题,还有一个异常处理的问题,还没有解决呢?

如何处理异常

如果语句块内部发生了异常,__exit__方法将被调用,而异常将会被重新抛出(re-raised)。当处理文件写入操作时,大部分时间你肯定不希望隐藏这些异常,所以这是可以的。而对于不希望重新抛出的异常,我们可以让__exit__方法简单的返回True来忽略语句块中发生的所有异常(大部分情况下这都不是明智之举)。
完备的__exit__函数签名应该是这样的:
def __exit__(self,exc_type,exc_val,exc_tb)
__exit__方法中给的参数有exc_type, exc_val, exc_tb,那么就可以进行异常处理了。(exc_type是异常类型,exc_val是异常的值和exc_tb是异常的回溯信息)
调用上下文管理器的 __exit__() 方法,如果 with结构中产生异常,那么该异常的 type、value 和 traceback 会作为参数传给 __exit__(),否则传三个 None

  • 如果 with_suite 产生异常,并且__exit__() 的返回值等于 False,那么这个异常将被重新抛出到上层
  • 如果 with_suite 产生异常,并且__exit__() 的返回值等于 True,那么这个异常就被忽略,继续执行后面的代码
通过上面的内容我们可以自己进行构造__exit__()函数了。
例:
>>> def __exit__(self,exc_type,exc_val,exc_tb):
...     self.file.close()
...     if exc_type:
...             print exc_type, exc_val, exc_tb
...             return False
...     return True

4. contextmanager装饰器

@contextmanager

contextlib模块的contextmanager装饰器可以更方便的实现上下文管理器。

任何能够被yield关键词分割成两部分的函数,都能够通过装饰器装饰的上下文管理器来实现。任何在yield之前的内容都可以看做在代码块执行前的操作,而任何yield之后的操作都可以放在exit函数中。

from contextlib import contextmanager
@contextmanager
def listTrans(alist):
    thecopy=list(alist)
    yield thecopy
    alist[:]=thecopy
alist=[]
with listTrans(alist) as working:
    working.append(1)
    working.append(2)
print alist
 

yield返回的值相当于__enter__的返回值。

要注意的是,这不是异常安全的写法,也就是说,当出现异常时,yield后的语句是不会执行的,想要异常安全,可用try捕捉异常:

from contextlib import contextmanager
@contextmanager
def listTrans(alist):
    thecopy=list(alist)
    try:
        yield thecopy
    except RuntimeError:
        pass
    alist[:]=thecopy
alist=[]
with listTrans(alist) as working:
    working.append(1)
    working.append(2)
    raise RuntimeError
nested与closing

contextlib模块还有两个好玩的方法:nested,closing。

nested:用来更方便的减少嵌套写法:

当要嵌套的写上下文管理器时:

with open('toReadFile', 'r') as reader: 
    with open('toWriteFile', 'w') as writer: 
        writer.writer(reader.read())

python2.7后nested就过时了:

with open('fileToRead.txt', 'r') as reader,open('fileToWrite.txt', 'w') as writer: 
        writer.write(reader.read())


closing(object):创建上下文管理器,在执行过程离开with语句时自动执行object.close():

class Door(object) :
    def open(self) :
        print 'Door is opened'
    def close(self) :
        print 'Door is closed'
with contextlib.closing(Door()) as door :
    door.open()

5. 其他

Python定义了几个上下文管理器来支持简单的线程同步,提示文件或其他对象的关闭,以及更简单地操作主动小数算术上下文。

每日一记:(2016-12-01)Python特殊语法:filter、map、reduce、lambda

Python特殊语法:filter、map、reduce、lambda
filter(function, sequence)
对sequence中的item依次执行function(item),将执行结果为True的item组成一个List/String/Tuple(取决于sequence的类型)返回
例:
>>> def f(x):return x%2 != 0 and x%3 != 0
>>> filter(f, range(2, 25))
  [5, 7, 11, 13, 17, 19, 23]

>>> def f(x):return x != 'a'
>>> filter(f, 'abcdef')
  'bcdef'
map(function, sequence) :
对sequence中的item依次执行function(item),见执行结果组成一个List返回
例:
>>> def cube(x): return x*x*x
>>> map(cube, range(1,11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]
>>> def cube(x):return x+x
>>> map(cube, 'abcde')
  ['aa', 'bb', 'cc', 'dd', 'ee']
另外map也支持多个sequence,这就要求function也支持相应数量的参数输入:
例:
>>> def add(x, y):return x+y
>>> map(add, range(8), range(8))
  [0, 2, 4, 6, 8, 10, 12, 14]
reduce(function, sequence, starting_value)
对sequence中的item顺序迭代调用function,如果有starting_value,还可以作为初始值调用。注意reduce只接受两个参数的函数,因为他要前后迭代。
例:
可以用来对List求和:
>>> def add(x, y):return x+y
>>> reduce(add, range(1,11))
  55 (注:1+2+3+4+5+6+7+8+9+10)
>>> reduce(add, range(1,11), 20)
  75 (注:1+2+3+4+5+6+7+8+9+10+20)
lambda
python的官方文档是这么定义的:
lambda [arguments]: expression

等价于

def name(arguments):return expression

 

1 python lambda会创建一个函数对象,但不会把这个函数对象赋给一个标识符,而def则会把函数对象赋值给一个变量。

2 python lambda它只是一个表达式,而def则是一个语句。

 
如果你在列表推导式里用到python lambda,我感觉意义不是很大,因为python lambda它会创建一个函数对象,但马上又给丢弃了,因为你没有使用它的返回值,即那个函数对象。
也正是由于lambda只是一个表达式,它可以直接作为python 列表或python 字典的成员,比如:

>>> info = [lambda a: a**3, lambda b: b**3]
>>> info
  [<function <lambda> at 0x01C79AB0>, <function <lambda> at 0x01C79A70>]
在这个地方没有办法用def语句直接代替。因为def是语句,不是表达式不能嵌套在里面。
像if或for或print这种语句就不能用于lambda中,lambda一般只用来定义简单的函数。
下面举几个python lambda的例子吧
单个参数的:
>>> g = lambda x:x*2
>>> print g(3)
  6

多个参数的:
>>> m = lambda x,y,z:(x-y)*z
>>> print m(3,1,2)
  4
 

lambda只是一个表达式,函数体比def简单很多。

lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。

lambda表达式是起到一个函数速写的作用。允许在代码内嵌入一个函数的定义。

>>> f = lambda x,y,z: x+y+x
>>> f(1,2,3)
  4
 
>>> f = lambda x,y,z: x+y+z
>>> f(1,2,3)
  6
lambda表达式也可以用在def函数中。
>>> def action(x):
...     return lambda y:x+y
>>> a = action(2)
>>> a(22)
  24
利用lambda实现了简单的闭包。

这里定义了一个action函数,返回了一个lambda表达式。其中lambda表达式获取到了上层def作用域的变量名x的值。

a是action函数的返回值,a(22),即是调用了action返回的lambda表达式。

这里也可以把def直接写成lambda形式。如下

>>> b = lambda x: lambda y: x+y
>>> a = b(3)
>>> a(2)
  5
>>> (b(2))(2)
  4
我们也可以把filter map reduce 和lambda结合起来用,函数就可以简单的写成一行。
例如:
>>> l = 'learning:x:503:503::/home/learning:/bin/bash'
>>> map(lambda kmpath:str.strip(kmpath), str.split(l, ':'))
['learning', 'x', '503', '503', '', '/home/learning', '/bin/bash']

>>> filter(lambda x:x!='', map(lambda kmpath:str.strip(kmpath), str.split(l, ':')))
['learning', 'x', '503', '503', '/home/learning', '/bin/bash']

>>> filter(lambda x:x, map(lambda kmpath:str.strip(kmpath), str.split(l, ':')))
['learning', 'x', '503', '503', '/home/learning', '/bin/bash']
map,reduce,filter中的function都可以用lambda表达式来生成!map(function,sequence)把sequence中的值当参数逐个传给function,返回一个包含函数执行结果的list。