若想技术精进,当然得把基础知识打得牢牢的。
廖雪峰的官方网站 python3教程,该网站提供的教程浅显易懂,还附带了讲学视频,非常适合初学者正规入门。
以下是通过廖雪峰python官方网站学习的个人查漏补缺。
主要内容包括:1.切片 2.迭代 3.列表生成式 4.生成器 5.迭代器 6.函数式编程 7.map/reduce 8.filter 9.sorted 10.返回函数 11.匿名函数 12.装饰器 13.偏函数
1. 切片
切片:Python的切片非常灵活,一行代码就可以实现很多行循环才能完成的操作。
>>> L = ['Michael','Sarah','Tracy','Bob','Jack']
>>> L[0:3]
['Michael', 'Sarah', 'Tracy']
>>> L[:3]
['Michael', 'Sarah', 'Tracy']
L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引0,1,2,正好是3个元素。如果第一个索引是0,还可以省略:L[:3]。
>>> L = list(range(100))
>>> L[-10:] ##取后10个
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
>>> L[:10:2] ##前10个数,每两个取一个
[0, 2, 4, 6, 8]
>>> L[::5] ##所有数,每5个取一个
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
tuple也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作,只是操作的结果仍是tuple。
字符串'xxx'也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串。
2.迭代
很多语言比如C语言,迭代list是通过下标完成的,比如Java代码:
for (i=0; i<list.length; i++) {
n=list[i];
}
Python对list列表和tuple元组迭代通过 for ... in 来完成的。 注:list这种数据类型虽然有下标,但很多其他数据类型是没有下标的,但是,只要是可迭代对象,无论有无下标,都可以迭代,比如dict就可以迭代。
如何判断一个对象是可迭代对象?方法是通过collections模块的Iterable类型判断:
>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False
Python对dict字典的迭代,默认情况下迭代的是key: for key in dictionary 来完成。 如果要迭代value,可以使用 for value in dictinary.values() 来完成。 如果要同时迭代key和vaule,可以使用 for k, v in dictionary.items() 来完成。
d={'a':1, 'b':2, 'c':3}
for key in d: #默认情况下用key迭代
for value in d.values(): #使用value来迭代
for k, v in d.items(): #使用key和vaule同时迭代
如果要对list实现类似Java那样的下标循环怎么办?Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:
3.列表生成式
列表生成式:列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。
####生成 list:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>>list(range(1,11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
####生成 list :[1x1, 2x2, 3x3, ..., 10x10]
>>>[x * x for x in range(1,11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
####for循环后面还可以加上if判断,可以筛选出仅偶数的平方
>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]
####生成全排列
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
运用列表生成式,可以写出非常简洁的代码。例如,列出当前目录下的所有文件和目录名,可以通过一行代码实现:
>>> import os # 导入os模块
>>> [d for d in os.listdir('.')] # os.listdir可以列出文件和目录
###列表生成式也可以使用两个变量来生成list:
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']
###把一个list中所有的字符串变成小写:
>>> L = ['Hello', 'World', 'IBM', 'Apple']
>>> [s.lower() for s in L]
['hello', 'world', 'ibm', 'apple']
运用列表生成式,可以快速生成list,可以通过一个list推导出另一个list,而代码却十分简洁。
4.生成器
为什么是生成器???——通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[ ]改成( ),就创建了一个generator:
>>> L = [x * x for x in range(10)] ## [ ] 列表生成式
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10)) ##( ) 生成器
>>> g
<generator object <genexpr> at 0x1022ef630>
####如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值:
>>> next(g)
0
####生成器generator也是可迭代对象,使用for循环迭代
>>> g = (x * x for x in range(10))
>>> for n in g:
... print(n)
...
我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:
这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
5.迭代器
可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list列表、tuple元组、dict字典、set集合、str字符串等;
一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
注:生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。
把list、dict、str等Iterable变成Iterator可以使用iter()函数:
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
为什么list、dict、str等数据类型不是Iterator迭代器对象?
这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
凡是可作用于for循环的对象都是Iterable类型;凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象;Python的for循环本质上就是通过不断调用next()函数实现的。
小结
1)切片:list、tuple、str都可以切片,且切片操作非常灵活、代码简单。
2)迭代:使用for...in xxx: 语句, list、tuple、set、dict、str都是可迭代的。凡是可作用于for循环的对象都是Iterable类型。
3)列表生成式:使用简单语句生成列表[ ]
4)生成器:最简单的方法,将列表生成式[ ] 改为( ) 就是一个生成器; 将函数改为生成器函数,使用yield,每调用一次都会在这暂时停留一下,你不再调用,我就不再走。一般使用for循环遍历。
5)迭代器:凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列。
6.函数式编程
函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
1)变量可以指向函数
>>>f=abs ###变量f,指向函数abs
>>>f(-10)###直接调用abs()函数和调用变量f()完全相同
10
2)函数名也是变量
>>> abs = 10
>>> abs(-10) ###abs已经被更改为整数,其函数功能就不能用了
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
3)传入函数
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
def add(x, y, f):
return f(x) + f(y)
x = -5
y = 6
f = absf(x) + f(y) ==> abs(-5) + abs(6) ==> 11
把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
7.map/reduce
Python内置map()和reduce()函数。
map()高阶函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素(map函数本质,函数一一作用于元素),并把结果作为新的Iterator返回。
>>> def f(x):
... return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]) #map(函数名,可迭代对象) 函数一一作用于可迭代对象中的元素,最后返回一个迭代器:Iterator惰性序列 r。
>>> list(r) ###Iterator迭代器(惰性序列),使用list()将整个序列都计算出来,并返回一个list。
[1, 4, 9, 16, 25, 36, 49, 64, 81]
reduce()把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算(reduce函数本质,函数把结果再代入充当其中的一个参数),其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
###reduce例子
>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579
8.filter
Python内置filter()函数用于过滤序列。
和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素(filter函数本质,一一作用于元素,True/False决定是否保留)。
def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]
可见用filter()这个高阶函数,关键在于正确实现一个“筛选”函数,即能够返回True/False的函数。filter()函数返回的是一个Iterator迭代器,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list。
生成素数:
def _odd_iter():###定义一个奇数生成器,生成无限序列
n=1
while True:
n=n+2
yield n
############################################
def _not_divisible(n):###定义一个筛选函数
return lambda x: x%n>0
############################################
def primes():###定义一个生成器,不断返回下一个素数
yield 2
it=_odd_iter() #初始序列
while True:
n=next(it) #返回序列的第一个数
yield n
it=filter(_not_divisible(n), it) #构造新序列
############################################
#打印100以内的素数:
for n in primes():
if n<1000:
print(n)
else:
break
9.sorted
sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
############################################
##### 给sorted传入key函数,即可实现忽略大小写的排序:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']
sorted()也是一个高阶函数。用sorted()排序的关键在于实现一个映射函数。高阶函数的抽象能力是非常强大的,而且,核心代码可以保持得非常简洁。
10.返回函数
函数作为返回值:高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
def lazy_sum(*args):
def sum():
ax=0
for n in args:
ax=ax+n
return ax
return sum ##lazy_sum()函数的返回值为一个函数sum()
内部函数sum()可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数。
11.匿名函数
当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。
>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]
关键字 lambda 表示匿名函数, 冒号前面的 x 表示函数参数。
匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数。Python对匿名函数的支持有限,只有一些简单的情况下可以使用匿名函数。
12.装饰器
为什么有装饰器??
1)由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。 ----能懂
eg:abs是python的内置函数, f=abs 则,f(-11) 等同于 abs(-11)
2)函数对象有一个__name__属性,可以拿到函数的名字。 ----能懂
eg:abs函数,通过调用这个函数的name属性,可以得到其函数名,abs.__name__ f也可以 f.__name__
3)假设要增强 abs() 函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改 abs() 函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。 ----不是很懂
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:
####定义一个打印日志的迭代器####
def log(func): ###接收一个函数作为输入参数
def wrapper(*args, **kw):###定义函数
print('call %s():' %func.__name__)
return func(*args, **kw) ###返回输入的函数
return wrapper ####decorator装饰器的本质就是返回函数的高阶函数,所以此装饰器要返回函数 wrapper
我们要借助Python的@语法,把decorator置于函数的定义处:
@log ##借助@装饰器名 将其置于函数定义处 相当于abs=log(abs)
def abs():
### 由于log()是一个decorator,返回一个函数,所以,原来的abs()函数仍然存在,只是现在同名的abs变量指向了新的函数,于是调用abs()将执行新函数,即在log()函数中返回的wrapper()函数。
然后再调用 abs() 函数,不仅会运行 abs() 函数本身,还会在运行abs() 函数前打印一行日志。
一个完整的decorator的写法:
import functools
def log(func):
@functools.wraps(func)#重要,写装饰器的时候加着,防止原函数的__name__属性值被修改
def wrapper(*args, **kw):
print('call %s():' %func.__name__)
return func(*args, **kw)
return wrapper
带有参数的decorator的写法:
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' %(text, func.__name__))
return func(*agrs, **kw)
return wrapper
return decorator
在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。
decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。
13.偏函数
当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。
int2 = functools.partial(int, base=2) #固定参数 转换成二进制
max2 = functools.partial(max, 10) # 实际上会把10作为*args的一部分自动加到左边
##max2(5,6,7) 等价于 args=(10,5,6,7) max(*args)