Python 高级进阶知识(一)

参考 Python学习手册 第四版

1 from vs import

  • import 模块 : 导入的一整个模块(python中模块对应一个py文件)

    因为import使用一个变量名引用整个模块对象,所以必须通过模块名称来得到该模块的属性。

    程序在第一次导入指定模块的时候,会执行三个步骤

    1. 找到模块文件
    2. 编译成字节码(需要时)
    3. 执行模块的代码来创建其所定义的对象

    这三个步骤,只在模块第一次被导入时才进行,第二次导入则不会,只会提取内存中已加载的模块对象。

    从技术上讲,python将载入的模块存储到一个名为“sys.modules`的表中,并在一次导入操作的开始检查该表,如果模块不存在,将会启动这三个步骤。

  • from... import : 导入模块中的一个变量

    因为from会把变量名复制到另一个作用域,所以就可以直接在脚本中使用复制后的变量名,而不需要通过模块访问

举个粒子

先建立一个ttttest.py脚本文件,输入一行代码。

cnblog_name = "XiiX" 

使用import需要通过模块名称来访问,导入的是一个模块

In [6]: import ttttest

In [7]: ttttest.cnblog_name
Out[7]: 'XiiX'
    
In [8]: type(ttttest)
Out[8]: module

使用from可以直接访问,可以看到导入的就是一个变量

In [10]: from ttttest import cnblog_name

In [11]: cnblog_name
Out[11]: 'XiiX'

In [12]: type(cnblog_name)
Out[12]: str

import和from其实是一个赋值语句

请注意,它们不是编译期间的声明,而是一个可以执行的语句。

  • import 将整个模块对象赋值给一个变量名
  • form 将一个或多个变量名赋值给另一个模块中同名的对象

2 列表解析

在处理序列的操作和列表的方法中,python有一个更高级的操作——列表解析表达式(list comprehension expression)。列表解析常常具有处理速度上的优势(会比手动的for循环语句快一倍以上,因为它们的迭代在解释器内存是以C语言的速度执行的而不是以手动python代码执行),能够在python的任何序列类型中发挥作用,甚至一些不属于序列的类型。

虽然列表解析速度上很有优势,但是只适用于小规模的任务,对于大规模的数值运算,使用Numpy包的效率会更高。

举个粒子:

  • 假设我们需要从矩阵中提取出第二列,我们可以通过一行代码就能解决这个问题

    In [2]: M
    Out[2]: [[1, 2, 4], [2, 5, 6], [1, 2, 3]]
    
    In [3]: col2 = [row[1] for row in M]
    
    In [4]: col2
    Out[4]: [2, 5, 2]
    

列表解析源自集合的概念,是一种通过对序列中的每一项运行一个表达式来创建一个新列表的方法。列表解析是编写在方括号中的(提醒你在创建一个新列表),并且使用了同一个变量名(这里是row)的表达式和循环结构组成。

实际操作中,我们还可以加一些额外的操作,比如算术运算,过滤等操作

# +1
In [5]: [row[1] +1 for row in M]
Out[5]: [3, 6, 3]

# 只取出偶数
In [6]: [row[1] for row in M if row[1]%2==0 ]
Out[6]: [2, 2]

再举一些粒子,大家也可以发挥自己的想象力

  • 取出矩阵中的对角线上的值,非常的方便

    In [8]: [M[i][i] for i in range(len(M))]
    Out[8]: [1, 5, 3]
    
  • 分离字符串,不过list()也能做

    In [9]: [c+',' for c in "Hello"]
    Out[9]: ['H,', 'e,', 'l,', 'l,', 'o,']
    
    In [10]: list('Hello')
    Out[10]: ['H', 'e', 'l', 'l', 'o']
    

还可以使用嵌套的循环

In [13]: [x+y for x in "abc" for y in "XiiX"]
Out[13]: ['aX', 'ai', 'ai', 'aX', 'bX', 'bi', 'bi', 'bX', 'cX', 'ci', 'ci', 'cX']

3 range迭代器

python3.0中,range返回一个迭代器,该迭代器根据需要产生范围内的数字,而不是在内存中构建一个结果列表。会比较节省空间。

如果需要一个列表的话,可以使用list(range(...))强制产生一个真正的范围列表

In [15]: r = range(0,10)

In [16]: r
Out[16]: range(0, 10)

In [17]: list(r)
Out[17]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

python3.0中,range对象只支持迭代、索引以及len函数,不支持其他的序列操作。

In [18]: len(r)
Out[18]: 10

In [19]: r[0]
Out[19]: 0

In [20]: r[2]
Out[20]: 2

In [22]: I = iter(r)

In [23]: next(I)
Out[23]: 0

In [24]: next(I)
Out[24]: 1

4 map、zip和filter迭代器

和range类似,,map、zip以及filter内置函数在Python3.0中也转变为迭代器以节约内存空间。

但是和range不同,它们都是自己的迭代器——在遍历其结果一次之后,它们就用尽了,换句话说,不能在它们的结果上引用在那些结果中保持不同位置的多个迭代器。

In [35]: m = map(abs, (-2,-1,0))

In [36]: m
Out[36]: <map at 0x7fb371ca4130>

In [37]: next(m)
Out[37]: 2

In [38]: next(m)
Out[38]: 1

In [39]: next(m)
Out[39]: 0

In [40]: next(m)
StopIteration

可以看到,在遍历一次之后,它们就用尽了。无法再继续遍历。

如果还是不明白的话,我们再来观察一下对zip的试验。可以看到两次迭代之后,用list强制转换z,之后只剩一个值, 所以值是在消耗的,一旦迭代一次过后,这个值就没有了。

注意:list(z)会一次性将其全部消耗

In [49]: z = zip((1,2,3),(10,20,30))

In [50]: next(z)
Out[50]: (1, 10)

In [51]: next(z)
Out[51]: (2, 20)

In [52]: list(z)
Out[52]: [(3, 30)]
    
In [53]: next(z)
StopIteration: 

多个迭代器vs单个迭代器

range可以同时存在多个迭代器,并且支持len和索引,但是不是自身的迭代器,是使用iter产生的迭代器。

可以看到range本身没有迭代器,要使用iter产生,产生的迭代器之间互不干扰是独立的。

In [54]: r = range(3)

In [55]: next(r)
TypeError: 'range' object is not an iterator

In [56]: I1 = iter(r)

In [57]: I2 = iter(r)

In [58]: next(I1)
Out[58]: 0

In [59]: next(I2)
Out[59]: 0

相反,zip和map和filter之间不支持相同结果上的多个迭代器同时存在。

从下方试验的代码可以看到,即使定义了两个迭代器,但是由于迭代是不断消耗的(与range不同),所以两个迭代器实际上指向的位置是一致的。

In [60]: z = zip((1,2,3),(1,2,3))

In [61]: I1 = iter(z)

In [62]: I2 = iter(z)

In [63]: next(I1)
Out[63]: (1, 1)

In [64]: next(I1)
Out[64]: (2, 2)

In [65]: next(I2)
Out[65]: (3, 3)

In [66]: next(I2)
StopIteration: 

In [67]: next(I1)
StopIteration: 

map

在程序中对列表和其他序列常常要做的一件事就说对每一个元素进行一个操作,并把其结果集合起来,这样的情况就可以使用map来进行操作。

例如,在一个列表counter中更新所有的数字,这虽然可以写一个for循环,完成,但是通过map会更加高效。

lambda的使用见下一节

In [5]: counter = [1,2,3,4]

## for
In [6]: temp = []

In [7]: for x in counter:
   ...:     temp.append(x + 10)
   ...: 

In [8]: temp
Out[8]: [11, 12, 13, 14]
    
## map
In [9]: list(map(lambda x:x+10, counter))
Out[9]: [11, 12, 13, 14]

zip

使用zip可以配对来自多个序列的参数

In [12]: list(zip((1,2,3),(4,5,6),(7,8,9)))
Out[12]: [(1, 4, 7), (2, 5, 8), (3, 6, 9)]

filter

函数式编程工具filter,在python的内置函数中,map函数是用来进行函数式编程的这类工具中最简单的内置函数代表。

函数式编程的意思就是对序列应用一些函数的工具,例如,基于某一测试函数过滤出一些元素(filter)

举个例子,从一个序列中挑选出大于0的元素

In [11]: list(filter( (lambda x: x>0), range(-5,3) ))
Out[11]: [1, 2]

5 lambda表达式

lambda名称是来自LISP,而LISP则是从lambda calculus (一种符号逻辑形式)取得到的名称。

除了def以外,python还提供了一种生成函数对象的表达式形式,lambda。就像def一样,这个表达式创建了一个能够调用的函数,但是它返回了一个函数而不是将这个函数赋值给一个变量名。这也是lambda有时也叫做匿名函数的原因。

lambda表达式的一般写法

lambda argument1, argument1, ... argumentN : expression using arguments
  • lambda是一个表达式,而不是一个语句。因为这一点,lambda可以出现在python语法不允许def出现的地方——例如,在一个列表常量中或者函数调用的参数中。
  • lambda的主体是一个单个的表达式,而不是一个代码块。主体非常简单,也决定了其不能执行复杂的任务
In [68]: def func(x,y,z): return x+y+z

In [69]: func(1,24,1)
Out[69]: 26

In [70]: f = lambda x,y,z:x+y+z

In [71]: f(2,3,4)
Out[71]: 9

这里的f被赋值给一个lambda表达式创建的函数对象,这也就是def所完成的任务。只不过def的赋值是自动进行的。

此外,默认参数也能够在lambda参数中使用

In [72]: x = (lambda a='fee', b='fie', c='foe': a+b+c)

In [73]: x("wee")
Out[73]: 'weefiefoe'

为什么要使用lambda?

总体而言,使用lambda起到了一种函数速写的作用,允许在使用的代码内嵌入一个函数的定义。虽然能用def来代替,但是使用lambda会更简洁。

lambda通常用来编写跳转表(jump table),也就是行为的列表或者字典。

In [1]: L = [lambda x : x**2, lambda x : x**3, lambda x : x**4 ]

In [2]: for f in L:
   ...:     print(f(2))
   ...: 
4
8
16

像这样的情况,def无法直接写入列表,只能先定义好三个def,然后再创建列表,毫无疑问,使用lambda会更为高效。

还可以写入字典里

In [3]: dict = {'already':(lambda:2+2), 'got':(lambda:2*4),'one':(lambda:2**3)}

In [4]: dict['got']()
Out[4]: 8

下一章会讲一下python的对象