Python范儿:Coding Pythonically

1 数学定义:解析(Comprehensions,或称推导式)

1.1 让代码飞

找到0-9之间的偶数


In [1]:
#number = range(10)
size = 10
even_numbers = []

n = 0
while n < size:
    if n % 2==0:
         even_numbers.append(n)
    n += 1
print even_numbers


[0, 2, 4, 6, 8]

我们做的事情在数学定义上看来像是什么呢?

$\{x|x \in \{0,1,2,....,9\}, s.t. x\%2==0 \}$


In [1]:
{ x for x in range(10) if x % 2==0 }


Out[1]:
{0, 2, 4, 6, 8}

这种代码形式称为Comprehensions,也就是解析(推导式)。

形式: {expr(item) for item in iterable if cond_expr(item)} 或者中括号、小括号包裹

  • 第一部分:元素,对元素的操作(运算与函数都可以)
  • 第二部分:遍历行为
  • 第三部分:筛选条件(可选)
  • 最后:用小括号,中括号,大括号包含住三部分,得到不同的数据结构或对象

In [2]:
print [ x**2 for x in xrange(10)]


[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

1.2 使用中括号进行列表解析

回忆enumerate

任务:把一个list里的元素和索引号找出来,更新回原来的list中去

循环操作可变类型


In [2]:
Lord_of_ring = ['Ainur','Dragons','Dwarves','Elves','Ents','Hobbits','Men','Orcs']

print type(enumerate(Lord_of_ring)),enumerate(Lord_of_ring)

for idx,element in enumerate(Lord_of_ring):
    Lord_of_ring[idx] ="{0}:{1}".format(idx,element)

print Lord_of_ring


<type 'enumerate'> <enumerate object at 0x1042d17d0>
['0:Ainur', '1:Dragons', '2:Dwarves', '3:Elves', '4:Ents', '5:Hobbits', '6:Men', '7:Orcs']

List Comprehension构造新列表


In [7]:
test =['Ainur','Dragons','Dwarves','Elves','Ents','Hobbits','Men','Orcs']

def _trans(idx,element):
    return '{0}:{1}'.format(idx,element)
print [_trans(idx,element) for idx,element in enumerate(test)]

print ['{0}:{1}'.format(idx,element) for idx,element in enumerate(test) ]


['0:Ainur', '1:Dragons', '2:Dwarves', '3:Elves', '4:Ents', '5:Hobbits', '6:Men', '7:Orcs']
['0:Ainur', '1:Dragons', '2:Dwarves', '3:Elves', '4:Ents', '5:Hobbits', '6:Men', '7:Orcs']

老朋友Iterable,isinstance()检查


In [8]:
import collections

print isinstance("Hello,world",   collections.Iterable)
print isinstance( test,           collections.Iterable)


True
True

字典也是Iterable:


In [9]:
language={"Scala":"Martin Odersky",\
          "Clojure":"Richy Hickey",\
          "C":"Dennis Ritchie",\
          "Standard ML":"Robin Milner"}

['{0:<12} created by {1:<15}'.format(la,ua)\
 for la,ua in language.iteritems()]


Out[9]:
['Standard ML  created by Robin Milner   ',
 'C            created by Dennis Ritchie ',
 'Clojure      created by Richy Hickey   ',
 'Scala        created by Martin Odersky ']

多重解析


In [3]:
print [(x+1,y+1) for x in xrange(4) for y in xrange(4)]
print [(x+1,y+1) for x in xrange(4) for y in xrange(4) if y<x]
print [(x+1,y+1) for x in xrange(4) for y in xrange(x)]


[(1, 1), (1, 2), (1, 3), (1, 4), (2, 1), (2, 2), (2, 3), (2, 4), (3, 1), (3, 2), (3, 3), (3, 4), (4, 1), (4, 2), (4, 3), (4, 4)]
[(2, 1), (3, 1), (3, 2), (4, 1), (4, 2), (4, 3)]
[(2, 1), (3, 1), (3, 2), (4, 1), (4, 2), (4, 3)]

1.3 使用小括号进行生成器

使用小括号做Comprehension返回生成器对象,占用O(1)内存空间:


In [11]:
num=range(0,20)

simple_generator=(x**2 for x in num if x > 0)

print simple_generator

for element in simple_generator:
    print element,


<generator object <genexpr> at 0x1043e9dc0>
1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 289 324 361

1.4 使用大括号解析得到集合或字典

使用小括号解析并不会返回一个不可变元组而是生成器,是一个需要强记的规则;然而大括号解析式就普通了许多。


In [4]:
x = range(10)

print { i for i in x if i%2==0 }
print { idx:i**2 for idx,i in enumerate(x) if i%2==0 }


set([0, 8, 2, 4, 6])
{0: 0, 8: 64, 2: 4, 4: 16, 6: 36}

注意生成字典时使用key:value的形式就可以了。

2 花样传参:zip与星号操作

  • zip: 拉链函数
  • *: 经常和zip在一起,用于传递参数。
  • **: 用于传递关键字型参数

2.1 Zip(拉链)

  • enumerate: 返回生成器,生成器每次给出下标和Iterable的内容
  • sorted: 返回列表,可以进行排序
  • zip: 把多个长度相同的列表当成数据列组成的数据表,返回一个包含着元组的列表,每个元组是数据表中的一行

In [13]:
war3_char = ['Orc','Humans','Undead','Night Elves']
dota_hero = ['Blade Master','Archmage','Death King','Demon Hunter']
Your_choice=zip(war3_char,dota_hero)
print Your_choice


[('Orc', 'Blade Master'), ('Humans', 'Archmage'), ('Undead', 'Death King'), ('Night Elves', 'Demon Hunter')]

取回原来的列表:


In [14]:
choice1,choice2,choice3,choice4 = Your_choice
print zip(choice1,choice2,choice3,choice4)


[('Orc', 'Humans', 'Undead', 'Night Elves'), ('Blade Master', 'Archmage', 'Death King', 'Demon Hunter')]

用*把Your_choice的内容而不是它本身作为参数传递


In [13]:
print zip(*Your_choice)


[('Orc', 'Humans', 'Undead', 'Night Elves'), ('Blade Master', 'Archmage', 'Death King', 'Demon Hunter')]

* 告诉Python即将传入的参数Your_choice不是单独一个序列,而是把Your_choice中的每一项作为参数

从下面的字典里按值来排序,取到值最大或者值最小的那条记录:


In [3]:
Base_Damage={'Blade Master':48,'Death King':65,'Tauren Chieftain':51}
print zip(Base_Damage.itervalues(),Base_Damage.iterkeys())
max_Damage=max(zip(Base_Damage.itervalues(),Base_Damage.iterkeys()))
min_Damage=min(zip(Base_Damage.itervalues(),Base_Damage.iterkeys()))

print max_Damage,min_Damage


[(48, 'Blade Master'), (65, 'Death King'), (51, 'Tauren Chieftain')]
(65, 'Death King') (48, 'Blade Master')

花样传参

刚才已经知道能把列表加星号保持有序地作为一个个参数(argument)传给方法/函数:


In [5]:
def triplesum(a,b,c):
    return a*100+b*10+c

print triplesum(*[1,2,3])
print triplesum(1,2,3)


123
123

带默认值的参数叫keyword arguments(kargs):


In [12]:
def triplesum_default(a=0,b=0,c=0,*args):
    return a*100+b*10+c

def ntuplesum_default(*args):
    sum = 0
    for i in args:
        sum*=10
        sum+=i
    return sum

print triplesum_default(*[1,2,3,4])
print ntuplesum_default(*[1,2,3,5])
print ntuplesum_default(1,2,3,5,6,7,8)
print triplesum_default(*[1,3])
print triplesum_default(**{'b':2,'c':3,'a':1})
print triplesum_default(**{'c':3,'a':1})


123
1235
1235678
130
123
103

3 变量之变:深浅拷贝

深浅拷贝:关系到变量的正确修改与复制

变量的属性:

  • 身份:就像身份证(或者内存地址)那样,id()
  • 属性:表示变量的类型,type()或者isInstance()确认
  • 值:这个地址存的数据,通过与名字绑定的方法来读取

3.1 浅拷贝

  • 完全切片(Slicing)操作[:]
  • 工厂函数,比如list(),tuple()
  • copy中的copy

In [6]:
import copy
unit = ['name',['Base_Damage',65.00]]
my_hero = unit[:]               #使用切片拷贝
your_hero = list(unit)          #使用工厂方法拷贝
its_hero = copy.copy(unit)      #使用浅拷贝
print [id(x) for x in unit,my_hero,your_hero,its_hero]
print [id(x[0]) for x in unit,my_hero,your_hero,its_hero]
print [id(x[1]) for x in unit,my_hero,your_hero,its_hero]


[4361778168, 4361777736, 4371155352, 4371155424]
[4297703024, 4297703024, 4297703024, 4297703024]
[4361686984, 4361686984, 4361686984, 4361686984]

假设一下,你的英雄打到了豪华套装(your_hero),或者游戏中的英雄(my_hero)有了一个嗜血的加成效果,所以数值会出现一些不一样的地方。


In [7]:
my_hero[0] = 'Kel\'Thuzad'
your_hero[0] = 'Mirana'
its_hero[0] = 'Morphling'
print my_hero,your_hero,its_hero
my_hero[1][1]=100.00                   # 先更改一个对象的值
print my_hero,your_hero,its_hero       #  可以看出值的情况不对,my_hero


["Kel'Thuzad", ['Base_Damage', 65.0]] ['Mirana', ['Base_Damage', 65.0]] ['Morphling', ['Base_Damage', 65.0]]
["Kel'Thuzad", ['Base_Damage', 100.0]] ['Mirana', ['Base_Damage', 100.0]] ['Morphling', ['Base_Damage', 100.0]]

为什么相互影响了呢?


In [8]:
print [id(x[0]) for x in unit,my_hero,your_hero,its_hero]
print [id(x[1]) for x in unit,my_hero,your_hero,its_hero]


[4297703024, 4371530688, 4371530928, 4371530784]
[4361686984, 4361686984, 4361686984, 4361686984]
  • 第一个对象是不可变的(字符串),而且还重新被赋值
  • 而第二个对象是可变的(一个列表),而且修改的是第二个对象里面的内容,第二个对象本身指向的地址不变

3.2 资深玩家的选择:深拷贝


In [9]:
import copy
unit = ['name',['Base_Damage',[65.00]]]
my_hero = copy.deepcopy(unit)
your_hero = copy.deepcopy(unit)

my_hero[0] = 'Kel\'Thuzad'
your_hero[0] = 'Mirana'
my_hero[1][1][0] = 100.00           

print my_hero,your_hero


["Kel'Thuzad", ['Base_Damage', [100.0]]] ['Mirana', ['Base_Damage', [65.0]]]

用id( )验证一下


In [10]:
print [id(x) for x in my_hero]
print [id(x) for x in your_hero]


[4371530880, 4361851032]
[4371531024, 4361851248]

3.3 不想Debug太烦?

  • 规避修改复杂可变类型内容,维护唯一拷贝
  • 规避使用可变类型
  • 理解Clojure

4 异常处理:Try-Except-Else-Finally

会犯错的是人,能原谅人的是……

  • try
  • except
  • finally

Python允许程序在运行当中检测错误。

每检测到一个错误,Python解释器就引发一个异常并报出详细的错误信息:


In [11]:
y = 6
x = 5
x,y = y,x
1/0


---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-11-76b625659a29> in <module>()
      2 x = 5
      3 x,y = y,x
----> 4 1/0

ZeroDivisionError: integer division or modulo by zero
  • 我们执行了一个除零操作(这显然是非法的),报出了"ZeroDivisionError: integer division or modulo by zero"
  • 在使用Python编写程序时,认真查看报错信息
  • 建议多使用Ipython notebook 完成小代码块

4.1 基本用法:try-except Exception

如果你需要添加错误检测和异常处理,需要把你想要书写的代码组封装在try-except语句中。


In [13]:
try:
    x = 1/0
    y = range(10)[10]
except Exception:
    print "Wow, such cute error"


Wow, such cute error

4.2 笔下无错,心中有错

常见的python中的异常,举例来说:

  • 有上文中已经出现的除零错误(ZeroDivisionError)
  • 尝试访问未声明的变量(NameError)
  • 语法错误 (Syntax Error)
  • 请求索引超过索引范围 (IndexError,常见于切片操作中)
  • 输入/输出错误 (IOError)

In [14]:
try:
    x = 1/0
    y = range(10)[10] 
except ZeroDivisionError,e1:
    print "Wow, such cute divisor"
except IndexError,e2:
    print "Wow, such cute index"
    
print e1


Wow, such cute divisor
integer division or modulo by zero

In [15]:
try:
    x = 1/2
    y = range(10)[10] 
except ZeroDivisionError,e1:
    print "Wow, such cute divisor"
except IndexError,e2:
    print "Wow, such cute index"
    
print e2


Wow, such cute index
list index out of range

4.3 异常处理:完全体

通过try-except-else-finally来感受异常处理的完全体吧!

  • try 下面是可能有异常的代码块
  • except 下面是对异常的处理
  • else 下面是在并没有异常的时候执行的代码块
  • finally 下面是收尾工作,无论是否有异常都执行

并用另一种方式记录异常。


In [22]:
import sys

try:
    x = 1/0
    y = range(10)[-1]
except Exception,error:
    x = 0
    y = 9
    print 'X,Y is corrected.'
    info = sys.exc_info()
else:
    print 'Catch no exceptions. Great!'
finally:
    z = x + y
    print z**2 + x**2 + y**2
    print 'Finished'
    
print '\n',error,'\n',info[0],'\n',info[1],'\n',info[2],info[2].tb_lineno


X,Y is corrected.
162
Finished

integer division or modulo by zero 
<type 'exceptions.ZeroDivisionError'> 
integer division or modulo by zero 
<traceback object at 0x1047b9a28> 4

In [ ]: