第2章 语法最佳实践——低于类级

本章记录以下内容:

  1. 列表推导
  2. 迭代器和生成器
  3. 装饰器
  4. with和contexlib

2.1 列表推导

在Python中总是要透着一种极简主义,这样才能显示出Python牛逼哄哄。所以Python语法中总是能写的短就写的短,这是Python的精髓。

列表推导就是为了简化列表生成,换一种说法就是用一行代码生成复杂的列表。

在C语言中你要生成一个0~100中偶数构成的数组你只能这么写:

int array[50] = {};
for(int i = 0; i < 100; i++)
{
    if(0 == i % 2)
        array[i] = i;
}

一共用了5行,当然更多时候是6行。

Python也有这种土逼的写法,当然也有更牛逼的写法,你可以这样写:

[i for i in xrange(100) if (i % 2) is 0]

Python的enmuerate内建函数十分有用,不仅能取元素还可以取序号

>>> l = ['a', 'b', 'c', 'd']
>>> for i, e in enumerate(l):
...   print "{0} : {1}".format(i,e)
... 
0 : a
1 : b
2 : c
3 : d
>>> 

结合列表推导,可以写出很简洁的代码:

>>> def handle_something(pos, elem):
...     return "{0} : {1}".format(pos, elem)
... 
>>> something = ["one", "two", "three", "four"]
>>> [handle_something(p, e) for p, e in enumerate(something)]
['0 : one', '1 : two', '2 : three', '3 : four']
>>>

当要对序列中的内容进行循环处理时, 就应该尝试使用List comprehensions


2.2 迭代器和生成器

迭代器是一种高效率的迭代方式,基于两个方法

  • next 返回容器的下一个项目
  • __iter__ 返回迭代器本身
>>> i = iter("abc")
>>> i.next()
'a'
>>> i.next()
'b'
>>> i.next()
'c'
>>> i.next()
Traceback (most recent call last):
  File "", line 1, in 
StopIteration
>>> 

在迭代器的最后,也就是没有对象可以返回的时候,就会抛出StopIteration异常,这个异常会被for捕获用作停止的条件。

要实现一个自定义的类迭代器,必须实现上面的两个两个方法,next用于遍历,__iter__用于返回。

>>> class MyIterator(object):
...   def __init__(self, step):
...     self.step = step
...   def next(self):
...     if self.step == 0:
...         raise StopIteration
...     self.step -= 1
...     return self.step
...   def __iter__(self):
...     return self
... 
>>> for el in MyIterator(4):
...     print el
... 
3
2
1
0
>>> 

书中没有对这段程序详细说明,以下是我的理解

流程是这样子的:

  1. 首先初始化类传入4这个参数
  2. 调用next方法, step - 1,返回 3,输出
  3. 调用__iter__方法,返回self,也就是把self.step当作参数传入
  4. 如此循环

2.2.1 生成器

对于yield的使用可以是程序更简单、高效。yield指令可以暂停一个函数返回结果,保存现场,下次需要时候继续执行。这个有点像函数的入栈和出栈的过程。但只是形式上相同罢了。

>>> def fibonacii():
...     a, b = 0, 1
...     while True:
...         yield b
...         a, b = b, a + b
... 
>>> fib = fibonacii()
>>> fib.next()
1
>>> fib.next()
1
>>> fib.next()
2
>>> fib.next()
3
>>> [fib.next() for i in range(10)]
[5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
>>> 

带有yield的函数不再是一个普通的函数,而是一个generator对象,可以保存环境,每次生成序列中的下一个元素。

  1. 书中没有说明生成器的原理,网上也没有很确定的说法,查阅资料后,个人理解是:当带有yield的函数生成的同时也会生成一个协程,这个协程用于保存yield的上下文,当再次代用该函数时候,切换到协程中取值。
  2. 书中提到“不必须提供使函数可停止的方法”,事实上说的也就是return。在yield函数中只能是return而且立马抛出StopIteration,不能是return a这样子的,否则抛出SyntaxError

这是抛出SyntaxError:

>>> def thorwstop():
...     a = 0
...     while True:
...         if a == 3:
...             return a
...         a += 1
...         yield b
... 
  File "", line 7
SyntaxError: 'return' with argument inside generator

这是抛出StopIteration:

>>> def thorwstop():
...     a = 0
...     while True:
...         if a == 3:
...             return
...         yield a
...         a += 1
>>>
>>> ts = thorwstop()
>>> ts.next()
0
>>> ts.next()
1
>>> ts.next()
2
>>> ts.next()
Traceback (most recent call last):
  File "", line 1, in 
StopIteration
>>> 

yield最大的用处的就要提供一个值的时候,不必事前得到全部元素,这样的好处不言而喻,省内存,效率高。这看起来和迭代器有些类似,但是要明确两点:

  1. 迭代器必须要知道下一个迭代的对象是谁,也就是迭代的是一个已经知道的值
  2. yield每次返回的可以是我们不知道的值,这个值可能是通过某个函数计算得出的

现在有这么一个应用场景:现在正在写一个股票软件,要获取某一只股票实时状态,涨了多少,跌了多少。这都是无法事先知道的,要通过函数实时获取,然后绘制的一个图上。

>>> from random import randint
>>> def get_info():
...     while True:
...         a = randint(0, 100)
...         yield a
... 
>>> def plot(values):
...     for value in values:
...         value = str(value) + "%"
...         yield value
... 
>>> p = plot(get_info())
>>> p.next()
'88%'
>>> p.next()
'61%'
>>> p.next()
'0%'
>>> p.next()
'7%'
>>> p.next()
'6%'
>>> p.next()
'19%'
>>>

yield不仅能传出也可以传入,程序也是停在yield出等待参数传入,使用send方法可以传入:

>>> def send_gen():
...     while True:
...         str = (yield)
...         print str
... 
>>> sg = send_gen()
>>> sg.next()
>>> sg.send("hello")
hello
>>> sg.send("python")
python
>>> sg.send("generator")
generator
>>> 

send机制和next一样,但是yield将编程能够返回的传入的值。因而,这个函数可以根据客服端代码来改变行为。同时还添加了throw和close两个函数,以完成该行为。它们将向生成器抛出一个错误:

  • throw允许客户端代码传入要抛出的任何类型的异常
  • close的工作方式是相同的,但是会抛出一个特定的GeneratorExit

一个生成器模版应该由try、except、finally组成:

>>> def fuck_generator():
...     try:
...         i = 0
...         while True:
...             yield "fuck {0}".format(i)
...             i += 1
...     except ValueError:
...         yield "catch your error"
...     finally:
...         print "fuck everthing, bye-bye"
... 
>>> gen = fuck_generator()
>>> gen.next()
'fuck 0'
>>> gen.next()
'fuck 1'
>>> gen.next()
'fuck 2'
>>> gen.throw(ValueError("fuck, fuck, fuck"))
'catch your error'
>>> gen.close()
fuck everthing, bye-bye
>>> gen.next()
Traceback (most recent call last):
  File "", line 1, in 
StopIteration
>>> 


2.2.2 协同程序

没看懂


2.2.3 生成器表达式

生成器表达式就是用列表推导的语法来替代yield,把[]替换成()。

>>> iter = (i for i in range(10) if i % 2 == 0)
>>> iter.next()
0
>>> iter.next()
2
>>> iter.next()
4
>>> iter.next()
6
>>> iter.next()
8
>>> iter.next()
Traceback (most recent call last):
  File "", line 1, in 
StopIteration
>>> 

对于创建简单的生成器,使用生成器表达式,可以减少代码量。


2.2.4 itertools模块

1. islice:窗口迭代器

返回一个子序列的迭代器,超出范围不执行。

itertools.islice(iterable, [start,] stop[, step])

  • iterable一个可迭代对象
  • start开始位置
  • stop结束位置
  • step步长
>>> from itertools import islice
>>> def fuck_by_step():
...   str = "fuck"
...   for c in islice(str, 1, None):
...     yield c
... 
>>> fuck = fuck_by_step()
>>> fuck.next()
'u'
>>> fuck.next()
'c'
>>> fuck.next()
'k'
>>> fuck.next()
Traceback (most recent call last):
  File "", line 1, in 
StopIteration
>>> 

islice函数就是从特定位置开始迭代,可以指定结束位置,范围为[), 可以指定步长。

2. tee: 往返式的迭代器

书中的例子,不能清楚表示tee的作用,有点看不懂的感觉。其实就是tee可以返回同一个迭代对象的多个迭代器,默认的2个。

>>> from itertools import tee
>>> iter_fuck = [1, 2, 3, 4]
>>> fuck_a, fuck_b = tee(iter_fuck)
>>> fuck_a.next()
1
>>> fuck_a.next()
2
>>> fuck_a.next()
3
>>> fuck_a.next()
4
>>> fuck_a.next()
Traceback (most recent call last):
  File "", line 1, in 
StopIteration
>>> fuck_b.next()
1
>>> fuck_b.next()
2
>>> fuck_b.next()
3
>>> fuck_b.next()
4
>>> fuck_b.next()
Traceback (most recent call last):
  File "", line 1, in 
StopIteration
>>> 

3. groupby:uniq迭代器

groupby是对可迭代对象重复元素进行分组。

itertools.groupby(iterable[, key])

  • iterable一个可迭代的对象
  • key处理函数,默认为标识处理
>>>from itertools import groupby

>>>def compress(data):
...    return ((len(list(group)), name) for name, group in groupby(data))
...
>>>def combainator(iter_data):
...    string = ""
...    for t in iter_data:
...        string = string + str(t[0]) + str(t[1])
...    return string
...
>>>raw_str = "get uuuuuuuuuuuuup"
>>>com_str = combainator(compress(raw_str))
>>>print com_str
1g1e1t1 13u1p
>>>

2.3 装饰器

书缺一页。。。。

2.3.1 如何编写装饰器

无参数的通用模式:

>>>def mydecorator(function):
...    def _mydecorator(**args, **kw):
...        #do something
...     
...        res = function(*args, **kw)
...        
...        #do something
...        return res
...    return _mydecorator

有参数的通用模式:

>>>def mydecorator(arg1, arg2):
...    def _mydecorator(function):
...        def __mydecorator(*args, **kw)
...            #do something
...     
...            res = function(*args, **kw)
...        
...            #do something
...            return res
...        return  __mydecorator
...    return _mydecorator

2.3.2 参数检查

from itertools import izip

def check_args(in_=(), out=(type(None),)):
    def _check_args(function):
        def _check_in(in_args):
            chk_in_args = in_args
            if len(chk_in_args) != len(in_):
                raise  TypeError("argument(in) count is wrong")
            
            for type1, type2 in izip(chk_in_args, in_) :
                if type(type1) != type2:
                    raise TypeError("argument's(in) type is't matched")
        
        def _chech_out(out_args):
            if type(out_args) == tuple:
                if len(out_args) != len(out):
                    raise TypeError("argument(out) count is wrong")
            else:
                if len(out) != 1:
                    raise TypeError("argument(out) count is wrong")
                if not type(out_args) in out:
                    raise TypeError("argument's(out) type is't matched")        
        def __chech_args(*args):
            
            _check_in(args)
            
            res = function(*args)
            
            _chech_out(res)
            
            return res
        return __chech_args
    return _check_args


@check_args((int, int), )
def meth1(i, j):
    print i,j
 
@check_args((str, list), (dict, ))    
def meth2(v_str, v_list):
    return {v_str : 1}
if __name__ == "__main__":
    
    meth1(1, 1)
    meth2("1", [1,2,3])

2.3.3 缓存

没看懂

import time
import hashlib
import pickle


cache = {}

def is_obssolete(entry, duration):
    return time.time() - entry["time"] > duration

def compute_key(function, args, kw):
    key = pickle.dumps(function.func_name, args, kw)
    return hashlib.sha1(key).hexdigest()

def memoize(duration=10):
    def _memoize(function):
        def __memoize(*args, **kw):
            key = compute_key(function, args, kw)
            
            if(key in cache and not is_obssolete(cache[key], duration)):
                print "we got a winner"
                return cache[key]["value"]
            
            result =  function(*args, **kw)
        
            cache[key] = {"value":result,
                          "time":time.time()}
            
            return result
        return __memoize
    return _memoize

2.3.4 代理

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

class User(object):
    def __init__(self, roles):
        self.roles = roles

class Unauthorized(Exception):
    pass
        

def protect(role):
    def _protect(function):
        def __protect(*args, **kw):
            
            user = globals().get("user")
            if user is None or role not in user.roles:
                raise Unauthorized("I won't tell you")
            return function(*args, **kw)
        return __protect
    return _protect        

@protect("admin")
def premession():
    print "premession ok"

if __name__ == '__main__':

    jack = User(("admin", "user"))
    bill = User(("user",))
    
#     user = jack
#     premession()
    
#     user = bill
#     premession()
    

2.3.5 上下文提供者

在函数运行前后执行一些其他的代码,例如:写一个线程安全的程序。

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

from threading import RLock
lock = RLock()
def synchronized(function):
    def _synchronized(*args, **kw):
        lock.acquire()
        try:
            return function(*args, **kw)
        finally:
            lock.release()
    return _synchronized

if __name__ == '__main__':
    pass

2.4 with 和 contextlib

程序中很多地方使用try...except...finally来实现异常安全和一些清理代码,应用场景有:

  • 关闭一个文件
  • 释放一个锁
  • 创建一个烂事的代码补丁
  • 在特殊的环境中运行受保护的代码

with语句覆盖和这些场景,可以完美替代try...except...finally。

with协议中依赖的是__enter____exit__两个方法,任何类都一个实现with协议。

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

class File(object):
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
    
    def __enter__(self):
        print 'entering this File class'
        self.fd = open(self.filename, self.mode)
        return self.fd
    
    def __exit__(self, exception_type, 
                 exception_value, 
                 exception_traceback):
        if exception_type is None:
            print 'without error'
        else:
            print "with a error({0})".format(exception_value) 
            
        self.fd.close()
        print 'exiting this File class'

__enter____exit__分别对应进入和退出时候的方法,__exit__还可以对异常进行捕捉,和try...except...finally的功能一模一样。

if __name__ == '__main__':
    with File("1.txt", 'r') as f:
        print "Hello World"

输出信息为:

'''
entering this File class
Hello World
without error
exiting this File class
'''

加上异常:

if __name__ == '__main__':
    with File("1.txt", 'r') as f:
        print "Hello World"
        raise TypeError("i'm a bug")

输出信息为:

'''
entering this File class
Hello World
with a error(i'm a bug)
exiting this File class
Traceback (most recent call last):
  File "E:\mycode\Python\ѧϰ\src\with.py", line 28, in 
    raise TypeError("i'm a bug")
TypeError: i'm a bug
'''

as的值关键字取决于__enter__的返回值,例子中也就是打开的文件描述符。

2.4.1 contextlib模块

标准库中contextlib模块就是用来增强with的上下文管理。其中最有用的是contextmanager,
这是一个装饰器,被该装饰器修饰的必须是以yield语句分开的__enter____exit__两部分的生成器。
yield之前的是__enter__中的内容,之后是__exit__中的内容。

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import contextlib

@contextlib.contextmanager
def talk():
    print "entering"
    try:
        yield 
    except Exception, e:
        print e;
    finally:
        print "leaving"


if __name__ == '__main__':
    with talk():
        print "do something."
        print "do something.."
        print "do something..."

输出是:

'''
entering
do something.
do something..
do something...
leaving
'''

如果要使用as关键字,那么yield要返回一个值,就和__enter__的返回值一样。

在contextlib模块还有closing和nested:
有些函数或者类不支持with协议,句柄之类的并不会自己关闭,可以使用contextlib模块中的closing方法,自动调用close()方法

#!/usr/bin/env python
# -*- coding: UTF-8 -*-


import contextlib

class Work(object):
    def __init__(self):
        print "start to work"
    def close(self):
        print "close and stop to work"

if __name__ == '__main__':
    print "没有异常"
    with contextlib.closing(Work()) as w:
        print "working"
    
    print '-'*30
    print "有异常"
    try:
        with contextlib.closing(Work()) as w:
            print "working"
            raise RuntimeError('error message')
    except Exception, e:
        print e

输出为:

'''
没有异常
start to work
working
close and stop to work
------------------------------
有异常
start to work
working
close and stop to work
error message
'''

可以看出无论是否执行成功都会调用close()方法。

nested的作用是可以同时打开多个with协议的语法。

在2.7之前:

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

在2.7之后可以直接使用:

with open('fileToRead.txt', 'r') as reader, \
        open('fileToWrite.txt', 'w') as writer:
        writer.write(reader.read())
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容