【笔记】Python随手记(持续)
Jul 6, 2019笔记pythonPython随手记(持续)
语法
1.字符串多行表示:'''
1 | print(''' |
这个特性类似于js的\
``。
2.布尔值:True
/False
分别对应js的true
/false
。
3.与/或/非:and
/or
/not
分别对应js的&&
/||
/not
。
4.数据类型(基本)
数字(整数/浮点数)、字符串、布尔值、空值None
。
5.除法
/
/
除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数:
1 | 10 / 3 |
1 | 9 / 3 |
//
//
称为地板除,两个整数的除法仍然是整数:1
210 // 3
/* 3 */
6.字符串编码操作:ord()
/chr()
1 | ord('A') |
1 | chr(65) |
对应js的String.prototype.charCodeAt(0)
/String.fromCharCode()
。
对bytes类型的数据用带b前缀的单引号或双引号表示:
由于Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes。如:1
b'ABC'
‘ABC’和b’ABC’不同,前者是str,后者虽然内容显示得和前者一样,但bytes的每个字符都只占用一个字节。
以Unicode表示的str通过encode()方法可以编码为指定的bytes。
1 | 'ABC'.encode('ascii') |
1 | '中文'.encode('utf-8') |
1 | '中文'.encode('ascii') |
如果bytes中只有一小部分无效的字节,可以传入errors=’ignore’忽略错误的字节:1
2b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore')
/*'中'*/
len()
要计算str包含多少个字符,可以用len()函数:1
2len('Hello')
/* 5 */
如果换成bytes,len()函数就计算字节数:1
2len(b'\xe4\xb8\xad\xe6\x96\x87')
/* 6 */
1 | len('中文'.encode('utf-8')) |
7.字符串格式化
和C语言是一致的,用%实现1
2'Hello, %s' % 'world'
/* 'Hello, world' */
1 | 'Hi, %s, you have $%d.' % ('Michael', 1000000) |
占位符 | 替换内容 |
---|---|
%d |
整数 |
%f |
浮点数 |
%s |
字符串 |
%x |
十六进制整数 |
其中,格式化整数和浮点数还可以指定是否补0和整数与小数的位数1
2'%2d-%02d' % (3, 1)
/*3-01*/
1 | '%.2f' % 3.1415926 |
如果你不太确定应该用什么,%s永远起作用,它会把任何数据类型转换为字符串
1 | 'Age: %s. Gender: %s' % (25, True) |
有些时候,字符串里面的%是一个普通字符怎么办?这个时候就需要转义,用%%
来表示一个`%
1 | 'growth rate: %d %%' % 7 |
format()
使用字符串的format()
方法,它会用传入的参数依次替换字符串内的占位符{0}
、{1}
……,不过这种方式写起来比%要麻烦得多:
1 | 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125) |
8.List/Tuple
List
类似于js的数组,list是一种有序的集合,可以随时添加和删除其中的元素。
1 | ['Michael', 'Bob', 'Tracy'] |
获取list长度:len()
。
取值:[索引值]
,如1
2
3names = ['Michael', 'Bob', 'Tracy']
names[0]
/* 'Michael' */
但与js不同的是
1.超出范围会报错,如:
1
2name[3]
/* IndexError:... */2.负值索引为倒数,如:
1
2name[-1]
/* 'Tracy' */
操作方法
append()
:末尾添加元素,类似于js的push(),如1
2names.append('Jack')
/* ['Michael', 'Bob', 'Tracy', 'Jack'] */pop()
:末尾删除元素,类似于js的pop(),如1
2names.pop();
/* ['Michael', 'Bob'] */pop(index)
:删除指定索引元素,类似于js的splice(index, 1),如1
2names.pop(1)
/* ['Michael', 'Tracy'] */insert(index, value)
:在指定索引添加元素,类似于js的splice(index - 1, 0, value),如1
2names.insert(1, 'Tom')
/* ['Michael', 'Tom', 'Bob', 'Tracy'] */
Tuple
tuple和list非常类似,但是tuple一旦初始化就不能修改,没有append(),insert()这样的方法。
不可变的tuple有什么意义?因为tuple不可变,所以代码更安全。如果可能,能用tuple代替list就尽量用tuple。如
1 | t = (1, 2) |
但要注意元组元素只有一个的时候,像刚才这样定义会有问题1
2t = (1)
/* 1 */
这是因为括号()既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1。
所以,只有1个元素的tuple定义时必须加一个逗号,,来消除歧义:1
t = (1,)
元组中的可变性
如果元组中某一个元素是list,那么该元素的元素可变,
1 | t = ('a', 'b', ['A', 'B']) |
Dict
dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。类似js的Object,不过不一样的是如果取值事key不存在,dict就会报错。
1 | d = { |
要避免key不存在的错误,有两种办法,一是通过in判断key是否存在:1
2'Jack' in d
/* False */
二是通过dict提供的get()
方法,如果key不存在,可以返回None,或者自己指定的value:1
2
3
4
5
6
7
8d.get('Jack')
/* None,无返回 */
d.get('Jack', 123)
/* 123 */
d.get('Michael', 123)
/* 95 */
要删除一个key,用pop(key)方法
1 | d.pop('Jack'); |
Set
set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。类似js的Set
1 | s = set([1, 2, 3]) |
add(key)
:添加keyremove(key)
:删除key
9.条件判断
if elif else:
分别对应js的if () {} else if () {} else {}
1 | if <条件判断1>: |
10.循环
break
语句可以提前退出循环,continue
语句跳过当前的这次循环。
for in
类似js的for in
1 | names = ['Michael', 'Bob', 'Tracy'] |
range(num)
转为有序序列,并支持循环
1 | for val in range(6): |
可以通过list()
方法转为list,如:1
2list(range(6))
/* [0,1,2,3,4,5] */
while
类似于js的while
1 | sum = 0 |
10.数学计算
abs()
:类似js的Math.abs()max()
:类似js的Math.max(),不过可以接收任意多个参数。如max(1,2,3,4,5)
11.类型转换
int()
:转为整数float()
:转为浮点数str()
:转为字符串bool()
:转为布尔值
12.定义函数
定义一个函数要使用def
语句,依次写出函数名、括号、括号中的参数和冒号:
,然后,在缩进块中编写函数体,函数的返回值用return
语句返回。
如:1
2
3
4
5def my_abs(x):
if x >= 0:
return x
else:
return -x
空函数
def nop():
pass
pass语句什么都不做,那有什么用?实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。
pass还可以用在其他语句里。1
2
3
4if a > 10:
pass
else:
pass
返回多个值
1 | import math |
即返回值是一个tuple。
关键字参数
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
1 | def person(name, age, **kw): |
命名关键字参数
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。
仍以person()
函数为例,我们希望检查是否有city和job参数:
1 | def person(name, age, **kw): |
调用1
person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456)
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。这种方式定义的函数如下:
1 | def person(name, age, *, city, job): |
和关键字参数**kw
不同,命名关键字参数需要一个特殊分隔符*
,*
后面的参数被视为命名关键字参数。
调用方式如下:
1 | person('Wayne', 24, city='Hangzhou', job='Engineer') |
命名关键字参数可以有缺省值,从而简化调用:
1 | def person(name, age, *, city='Beijing', job): |
调用1
2person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer
参数组合
在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
比如定义一个函数,包含上述若干种参数:
1 | def f1(a, b, c=0, *args, **kw): |
递归函数
简单,举一个阶乘例子就好:
1 | def fact(n): |
与js一样,python也有调用栈溢出问题,解决的一种方案是通过尾递归优化
13.高级用法
切片
我们先创建一个0-99的数列:
1 | L = list(range(100)) |
可以通过切片轻松取出某一段数列。比如前10个数:
1 | L[:10] |
后10个数
1 | L[-10:] |
前11-20个数:1
2L[10:20]
/* [10, 11, 12, 13, 14, 15, 16, 17, 18, 19] */
前10个数,每两个取一个:1
2L[:10:2]
/* [0, 2, 4, 6, 8] */
所有数,每5个取一个:1
2L[::5]
/* [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95] */
甚至什么都不写,只写[:]就可以原样复制一个list:1
2L[:]
/* [0, 1, 2, 3, ..., 99] */
tuple也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作,只是操作的结果仍是tuple:
1 | (0, 1, 2, 3, 4, 5)[:3] |
字符串’xxx’也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串:
1 | 'ABCDEFG'[:3] |
总得来说,比js的slice灵活。。。
迭代
Python的for循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上。
list这种数据类型虽然有下标,但很多其他数据类型是没有下标的,但是,只要是可迭代对象,无论有无下标,都可以迭代,比如dict就可以迭代:
1 | d = {'a': 1, 'b': 2, 'c': 3} |
默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values()
,如果要同时迭代key和value,可以用for k, v in d.items()
。
所以,当我们使用for循环时,只要作用于一个可迭代对象,for循环就可以正常运行,而我们不太关心该对象究竟是list还是其他数据类型。
那么,如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断
1 | from collections import Iterable |
如果要对list实现类似Java那样的下标循环怎么办?Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:
1 | for i, value in enumerate(['A', 'B', 'C']): |
1 | for x, y in [(1, 1), (2, 4), (3, 9)]: |
列表生成式
列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。
比如要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用list(range(1, 11)):1
2list(range(1, 11))
/* [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] */
但如果要生成[1x1, 2x2, 3x3, …, 10x10]怎么做?方法一是循环:1
2
3
4
5L = []
for x in range(1, 11):
L.append(x * x)
/* [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] */
但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list:1
2[x * x for x in range(1, 11)]
/* [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] */
for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:1
2[x * x for x in range(1, 11) if x % 2 == 0]
/* [4, 16, 36, 64, 100] */
还可以使用两层循环,可以生成全排列:1
2[m + n for m in 'ABC' for n in 'XYZ']
/* ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ'] */
三层和三层以上的循环就很少用到了。
运用列表生成式,可以写出非常简洁的代码。例如,列出当前目录下的所有文件和目录名,可以通过一行代码实现:1
2
3import os # 导入os模块,模块的概念后面讲到
[d for d in os.listdir('.')] # os.listdir可以列出文件和目录
/* ['.emacs.d', '.ssh', '.Trash', 'Adlm', 'Applications', 'Desktop', 'Documents', 'Downloads', 'Library', 'Movies', 'Music', 'Pictures', 'Public', 'VirtualBox VMs', 'Workspace', 'XCode'] */
for循环其实可以同时使用两个甚至多个变量,比如dict的items()可以同时迭代key和value:1
2
3
4
5
6
7
8
9d = {'x': 'A', 'y': 'B', 'z': 'C' }
for k, v in d.items():
print(k, '=', v)
/*
y = B
x = A
z = C
*/
列表生成式也可以使用两个变量来生成list:1
2
3
4d = {'x': 'A', 'y': 'B', 'z': 'C' }
[k + '=' + v for k, v in d.items()]
/* ['y=B', 'x=A', 'z=C'] */
最后把一个list中所有的字符串变成小写:1
2
3L = ['Hello', 'World', 'IBM', 'Apple']
[s.lower() for s in L]
/* ['hello', 'world', 'ibm', 'apple'] */
在一个列表生成式中,for前面的if … else是表达式,而for后面的if是过滤条件,不能带else。
生成器
有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:1
2
3
4
5L = [x * x for x in range(10)]
/* [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] */
g = (x * x for x in range(10))
/* <generator object <genexpr> at 0x1022ef630> */
创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。
可以通过next()函数获得generator的下一个返回值:1
2
3
4
5
6
7
8
9
10next(g)
/* 0 */
/* next... */
/* 81 */
next(g)
/*
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
*/
generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
正确的方法是使用for循环,因为generator也是可迭代对象:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15g = (x * x for x in range(10))
for n in g:
print(n)
/*
0
1
4
9
16
25
36
49
64
81
*/
如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:1
2
3
4
5
6
7def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
迭代器
可以直接作用于for循环的数据类型有以下几种:
- 一类是集合数据类型,如list、tuple、dict、set、str等;
- 一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
可以使用isinstance()
判断一个对象是否是Iterable对象:
1 | from collections.abc import Iterable |
而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
可以使用isinstance()
判断一个对象是否是Iterator对象:1
2
3
4
5
6
7
8
9
10
11
12from collections.abc import Iterator
isinstance((x for x in range(10)), Iterator)
/* True */
isinstance([], Iterator)
/* False */
isinstance({}, Iterator)
/* False */
isinstance('abc', Iterator)
/* False */
生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。
把list、dict、str等Iterable变成Iterator可以使用iter()
函数:1
2
3
4
5isinstance(iter([]), Iterator)
/* True */
isinstance(iter('abc'), Iterator)
/* True */
为什么list、dict、str等数据类型不是Iterator?
这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
14.函数式编程
高阶函数
“函数是一等公民”,函数也可以作为参数。
python内置了map()和reduce()函数。
map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
如:1
2
3
4
5
6def f(x):
return x * x
r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
list(r)
/* [1, 4, 9, 16, 25, 36, 49, 64, 81] */
map()传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator,Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。
map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数。
reduce把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:1
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
比方说对一个序列求和,就可以用reduce实现:1
2
3
4
5
6from functools import reduce
def add(x, y):
return x + y
reduce(add, [1, 3, 5, 7, 9])
/* 25 */
和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。1
2
3
4def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
内置的sorted()函数就可以对list进行排序:1
2sorted([36, 5, -12, 9, -21])
/* [-21, -12, 5, 9, 36] */
此外,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:1
2sorted([36, 5, -12, 9, -21], key=abs)
/* [5, 9, -12, -21, 36] */
返回函数
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。如:1
2
3
4
5
6
7
8
9
10
11def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
f = lazy_sum(1, 3, 5, 7, 9)
f();
/* 25 */
闭包:注意到返回的函数在其定义内部引用了局部变量args,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。
匿名函数
在Python中,对匿名函数提供了有限支持。还是以map()函数为例,计算f(x)=x2时,除了定义一个f(x)的函数外,还可以直接传入匿名函数:1
2list(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,返回值就是该表达式的结果。
用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:1
2
3f = lambda x: x * x
f(5);
/* 25 */
装饰器
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。1
2
3
4
5
6def now():
print('2015-3-25')
f = now
f()
/* 2015-3-25*/
函数对象有一个__name__
属性,可以拿到函数的名字:1
2
3
4now.__name__
/* 'now' */
f.__name__
/* 'now' */
现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:1
2
3
4
5def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:1
2
3
def now():
print('2015-3-25')
调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:1
2
3now()
call now():
/* 2015-3-25 */
由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。
wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。
偏函数
Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。
假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:1
2def int2(x, base=2):
return int(x, base)
functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:1
2
3
4import functools
int2 = functools.partial(int, base=2)
int2('1000000')
/* 64 */
所以,简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
Author
My name is Micheal Wayne and this is my blog.
I am a front-end software engineer.
Contact: michealwayne@163.com