快速上手
行结尾可加可不加分号;
除法运算的结果为浮点数;
执行整数除法可以使用//
操作符;
乘方运算符**
:
>>> 1//2
0
>>> 1/2
0.5
>>> (-2)**2
4
十六进制使用0x
开头,八进制使用0o
开头,二进制使用0b
开头:
>>> 0xF
15
>>> 0o10
8
>>> 0b10
2
Python中变量没有默认值,所以使用前必须赋值;
以下示例是input、print、pow、abs、round等内置函数使用:
>>> x = input("The square of: ")
The square of: 10
>>> int(x) ** 2
100
>>> print("Hello world!")
Hello world!
>>> print(2**2)
4
>>> pow(2,2)
4
>>> abs(-199)
199
>>> round(123.12)
123
>>> round(123.72)
124
使用import导入模块:
>>> import math
>>> math.sqrt(9)
3.0
>>> from math import sqrt
>>> sqrt(25)
5.0
Python本身对复数有支持,cmath库对复数进行扩展:
>>> 1j*1j
(-1+0j)
>>> import cmath
>>> cmath.sqrt(-1)
1j
海龟绘图小示例,使用turtle库(本身依赖于tkintery库)画一个正方形,其中forward前进n个像素点,left函数为方向逆时针旋转n度:
from turtle import *
forward(100)
left(90)
forward(100)
left(90)
forward(100)
left(90)
forward(100)
input("press enter to continue")
Python的注释以#开头;
Python使用单引号和双引号都可以表示字符串,字符串拼接使用+
运算符;另外,直接把两个字符串放一起,也可以实现拼接:
>>> a="Let's say " '"Hello world!"'
>>> a
'Let\'s say "Hello world!"'
repr函数配合print使用可保留原始字符串的样子:
>>> print(repr("Hello,\nworld"))
'Hello,\nworld'
>>> print(str("Hello,\nworld"))
Hello,
world
长字符串可以使用三引号(引号使用'
和"
都行);反斜杠换行;原始字符串表示(以r
开头,注意最后一个字符不能是反斜杠):
>>> print('''That's great, like a "boss"!''')
That's great, like a "boss"!
>>> print("Hello,\
... world!")
Hello,world!
>>> print(r"C:\Program Files")
C:\Program Files
unicode的支持,以及编码的转换,以及使用b
开头的字符串可以直接转化成bytes
对象,以及可变bytes bytearray
:
>>> print("\N{cat}\U0001F60A\u00C6")
🐈😊Æ
>>> "Hello".encode("ASCII")
b'Hello'
>>> "Hello".encode("UTF-8")
b'Hello'
>>> "Hello".encode("UTF-32")
b'\xff\xfe\x00\x00H\x00\x00\x00e\x00\x00\x00l\x00\x00\x00l\x00\x00\x00o\x00\x00\x00'
>>> "Hello\N{cat}".encode("UTF-8")
b'Hello\xf0\x9f\x90\x88'
>>> "Hello\N{cat}".encode("ASCII")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character '\U0001f408' in position 5: ordinal not in range(128)
>>> "Hello\N{cat}".encode("ASCII", "replace")
b'Hello?'
>>> "Hello\N{cat}".encode("ASCII", "ignore")
b'Hello'
>>> x = bytearray(b'Hello')
>>> x[1] = ord(b'c')
>>> x
bytearray(b'Hcllo')
Python中默认使用UTF-8编码。
列表与元组
Python中的容器主要有序列(元组、列表等)、映射(字典等)、集合。
列表可修改,元组不可修改:
>>> edward = ['Edward Gumby', 42]
>>> john = ['John Smith', 50]
>>> database = [edward, john]
>>> database
[['Edward Gumby', 42], ['John Smith', 50]]
>>> ['rd']*2 + ['rs']
['rd', 'rd', 'rs']
序列的索引从0开始,可使用负数从最后往前数,-1表示最后一个元素。
字符串也是一个序列,Python中没有字符的概念,所以str[0]
表示只有str第一个字符表示的字符串。
切片
>>> tag = '01234567'
>>> tag[1:3]
'12'
切片使用冒号来提取特定范围内的元素,冒号前后表示对应的索引,最终结果包含第一个索引,而不包含第二个索引;
索引可以是负数,也可以省略,省略第一个表示从头开始,省略第二个表示直到最后一个元素(包含);
还可以使用步长来间隔获取元素,负数的步长表示从右往左提取:
>>> tag[1:-1]
'123456'
>>> tag[:]
'01234567'
>>> tag[:-1]
'0123456'
>>> tag[-3:-1]
'56'
>>> tag[-3:]
'567'
>>> tag[::2]
'0246'
>>> tag[-2::-2]
'6420'
其它操作
乘法操作可以复制序列元素,加法可以拼接两个序列,另外在Python中使用None关键字表示什么也没有;
in运算符用于判断指定元素是否在序列中,注意对于两个字符串使用in操作,表示的含义就变成检查子串了:
>>> 'ml' in ['mlh', 'abc']
False
>>> 'mlh' in ['mlh', 'abc']
True
>>> 'ml' in 'mlh'
True
获取长度,最小值,最大值:
>>> len([1,2,3])
3
>>> max(32,2,99)
99
>>> max([32,2,99])
99
>>> min([32,2,99])
2
列表
使用list类创建列表,列表是可以修改的,所以可以进行赋值,还有其它操作:
>>> a = list("Hello")
>>> a
['H', 'e', 'l', 'l', 'o']
>>> a[1] = 'a'
>>> ''.join(a)
'Hallo'
del a[1]: 删除元素;
给切片赋值可以实现删除,插入,替换等多种功能;
append:追加元素;
clear:清空列表;
copy:复制列表,如果正常赋值只是做一个关联,复制需要使用copy;
count:计算元素出现的个数;
extend:与加法拼接类似,只是会修改第一个列表值;
index:查找对应的元素,未找抛出异常,找到返回索引;
insert:插入对象到列表;
pop:从列表中删除参数指定的元素,默认为最后一个元素;
remove:删除第一个指定值的元素;
reverse:原地反转列表;
sort:原地排序;
sorted:并非list上的函数,而是将list做为参数传入,返回排序后的副本;
以上的sort和sorted支持key和reverse两个参数:
key:是一个函数,用于对元素生成一个关键字,按这个关键字排序;
reverse:接受bool类型值,表示是否倒序排列:
>>> a = [1,2,3,4]
>>> a[1:]
[2, 3, 4]
>>> a[1:] = [4,5,6,7]
>>> a
[1, 4, 5, 6, 7]
>>> a[1:3] = []
>>> a
[1, 6, 7]
>>> a.append(8)
>>> a
[1, 6, 7, 8]
>>> b = a.copy()
>>> a.clear()
>>> a
[]
>>> b
[1, 6, 7, 8]
>>> b.count(8)
1
>>> b.extend([8,9,10])
>>> b.count(8)
2
>>> b.index(8)
3
>>> b.pop(3)
8
>>> b.index(8)
3
>>> b.reverse()
>>> b
[10, 9, 8, 7, 6, 1]
>>> b.sort()
>>> b
[1, 6, 7, 8, 9, 10]
>>> sorted(b, reverse=True)
[10, 9, 8, 7, 6, 1]
元组
元组与列表类似,只是无法修改元组的值,元组的切片还是元组。tuple可以将序列转为元组。
>>> 1,2,3
(1, 2, 3)
>>> 1,
(1,)
>>> 3*(1+2,)
(3, 3, 3)
>>> x = 1,2,3
>>> x[0:1]
(1,)
>>> tuple('abc')
('a', 'b', 'c')
>>> tuple([1,2,3])
(1, 2, 3)
使用字符串
格式化输出
百分号运算符除了可以取模运算,还可以用于格式化输出字符串:
>>> "Your name is %s, age :%d\n" % ("Lee", 12)
'Your name is Lee, age :12\n'
这里的%s
与%d
与C语言中的print函数类似,还可以使用%.3f
这样的表示来获取指定位数的小数。
还可以使用Template的方式来处理占位符的替换;
或者使用{}
这样的方式配合format函数使用,更加灵活(如果需要输出共括号时,使用两个花括号);
如果变量与替换字段同名,可以在字符串前加f
轻松处理:
>>> from string import Template
>>> Template("Hello $name, welcome to $city").substitute(name="Lee", city="China")
'Hello Lee, welcome to China'
>>> "{3} {0} {2} {1} {3} {0}: {percent:.2f} }} {{ {arr[1]:03d}".format("be", "not", "or", "to", percent=0.324223432442343424, arr=[1,2,3])
'to be or not to be: 0.32 } { 002'
>>> "Hello {name} , welcome to {country}!".format_map({"name" : "Lee", "country":"China"})
'Hello Lee , welcome to China!'
>>> from math import pi
>>> f"pi is {pi:2.6f}"
'pi is 3.141593'
使用花括号的格式化还支持其它的语义,例如在变量后跟!s
,!r
, !a
表示使用str、repr和ascii进行转换。更多的资料可以参考这里:
>>> "{str!s} {str!r} {str!a}".format(str="ok!\n")
"ok!\n 'ok!\\n' 'ok!\\n'"
字符串方法
string模块中定义了很多字符串的操作方法,但大部分在新版本的Python中都迁移到字符串方法上了,所以一般不再使用string模块中的方法,但有一些常用的常量:
>>> import string
>>> string.digits
'0123456789'
>>> string.ascii_letters
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
>>> string.printable
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'
>>> string.ascii_uppercase
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
这里列举了一些字符串常用方法,更多的可以参考这里;
center:将字符串以给定长度居中显示;
find:在指定的范围内查到对应子串,返回位置,不存在返回-1,如果只需要判断是否包含字串,使用in操作符;
join:对字符串序列进行合并;
split:对字符串进行分割,可指定最多分割数量;
lower:字符串转小写;
replace:字符串替换;
strip:与php的trim一样,去掉头尾的空格或者指定字符;
translate:单字母替换指定字符,并可以删除指定字符;
isspace/isdigit/isupper:这一系列的is函数用于判断字符串是否满足指定规则:
>>> "abc".center(11)
' abc '
>>> "abc".center(10, '-')
'---abc----'
>>> "abc".center(1, '-')
'abc'
>>> "i am ok ".find("i ")
0
>>> "i am ok ".find("i m")
-1
>>> "i am ok ".find("i", 3, 5)
-1
>>> "i am ok ".find("am", 1, 5)
2
>>> "+".join(["1","2"])
'1+2'
>>> "+".join(("1","2"))
'1+2'
>>> "+".join((1,2))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: sequence item 0: expected str instance, int found
>>> "1 2 3 4".split(" ")
['1', '2', '3', '4']
>>> "1 2 3 4".split(" ",2)
['1', '2', '3 4']
>>> "i am ok".replace("ok", "bad")
'i am bad'
>>> table = str.maketrans('cs', 'kz', ' ')
>>> 'this is an incredible test'.translate(table)
'thizizaninkredibletezt'
字典
创建字典以及对应的操作,其中键的类型可以是任何不可变的类型,如字符串,元组,浮点数等:
>>> {"Alice":10, "Bob":20}
{'Alice': 10, 'Bob': 20}
>>> dict([["Alice", "Bob"],[10,20]])
{'Alice': 'Bob', 10: 20}
>>> dict(Alice=10, Bob=20)
{'Alice': 10, 'Bob': 20}
>>> d = dict(Alice=10, Bob=20)
>>> d["Alice"]
10
>>> d["Lee"] = 21
>>> d
{'Alice': 10, 'Bob': 20, 'Lee': 21}
>>> del d["Lee"]
>>> d
{'Alice': 10, 'Bob': 20}
>>> "Lee" in d
False
>>> "Bob" in d
True
常用的方法如下,更多查看这里:
clear:清空字典;
copy:复制字典;
fromkeys:创建并返回一个包含给定键的字典,可以设置默认值;
get:获取某一个键的值,与直接下标访问的区别是如果不存在,get会返回None,而下标访问会报错;
items:返回二元组的一个列表,包含了所以键值对,这个函数返回内容只是字典的另一个视图,不会进行复制;
keys:返回一个字典视图,其中包含指定字典中的键;
pop:删除指定key的元素,并返回,可指定没有这个元素时的默认值;
popitem:随机弹出一个键值项;
setdefault:如果某一个key不存在,则设置上某值;
update:更新值;
values:返回一个字典视图,其中包含指定字典中的值,可重复:
>>> d = dict(Alice=10, Bob=20)
>>> d.copy()
{'Alice': 10, 'Bob': 20}
>>> e = d.copy()
>>> d.clear()
>>> d
{}
>>> e
{'Alice': 10, 'Bob': 20}
>>> e.get('abc')
>>> e['abc']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'abc'
>>> e.fromkeys(["Lee","Chen"])
{'Lee': None, 'Chen': None}
>>> e
{'Alice': 10, 'Bob': 20}
>>> dict.fromkeys(["Lee","Chen"])
{'Lee': None, 'Chen': None}
>>> e.items()
dict_items([('Alice', 10), ('Bob', 20)])
>>> it = e.items()
>>> len(it)
2
>>> e["Bob"] = 21
>>> it
dict_items([('Alice', 10), ('Bob', 21)])
>>> e.keys()
dict_keys(['Alice', 'Bob'])
>>> e.pop("Lee", 22)
22
>>> e.popitem()
('Bob', 21)
>>> e
{'Alice': 10}
语句
print支持更多的参数,可以打印多个参数,并指定分隔符和结束符号,而import也可以为引入的包创建别名:
>>> print("Hello", "world", "my", "friend", sep = ', ', end=" -_- \n");
Hello, world, my, friend -_-
>>> import math as myMath
>>> myMath.sqrt(9)
3.0
>>> from math import sqrt as mySqrt
>>> mySqrt(9)
3.0
赋值
序列解包,可以使用*
来收集多余的值:
>>> x,y,z = 1,2,3
>>> x,y = y,x
>>> x,y,z
(2, 1, 3)
>>> key,val = {"Bob":12, "Alice":11}.popitem()
>>> (key,val)
('Alice', 11)
>>> x,*y,z = 1,2
>>> x,y,z
(1, [], 2)
>>> x,*y,z = 1,2,3,4,5
>>> x,y,z
(1, [2, 3, 4], 5)
>>> x,*y,z = 1,2,5
>>> x,y,z
(1, [2], 5)
代码块
Python中使用冒号指示接下来是一个代码块,相同缩进的代码被认为同一个代码块,不相同则此代码块结束。
条件
在Python中这些值被认为是假False None 0 "" () [] {}
使用例子:
num = int(input('Enter a number: '))
if num > 0:
print('The number is positive')
elif num < 0:
print('The number is negative')
else:
print('The number is zero')
在Python中有一些特殊的条件,例如支持链式比较,is
和is not
判断两对象是否为一个,使用and/or/not
进行布尔运算(短路逻辑),assert
断言直接在为假时让程序异常退出:
>>> x=10
>>> 9 < x < 100
True
>>> x = y = [1,2]
>>> z = [1,2]
>>> x is y
True
>>> x is not z
True
>>> x == z
True
>>> assert x
>>> assert not x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
>>> assert not x, x is not empty
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'empty' is not defined
循环
# 直到你输入了非空的名字之后才退出
name = ''
while not name:
name = input('Please enter your name: ')
print('Hello, {}!'.format(name))
# 每一行输出一个单词
words = ["this", "is", "a", "man"]
for word in words:
print(word)
# 输出1到100
for number in range(1,101):
print(number)
# 对于map的遍历
d = {'x': 1, 'y': 2, 'z': 3}
for key, value in d.items():
print(key, 'corresponds to', value)
# 输出
# x corresponds to 1
# y corresponds to 2
# z corresponds to 3
# 对于同时需要遍历两个数组的情况,可以使用索引,也可以使用zip拼接成元组之后遍历
names = ['anne', 'beth', 'george', 'damon']
ages = [12, 45, 32, 102]
for i in range(len(names)):
print(names[i], 'is', ages[i], 'years old')
for name, age in zip(names, ages):
print(name, 'is', age, 'years old')
# 以上两个都输出
# anne is 12 years old
# beth is 45 years old
# george is 32 years old
# damon is 102 years old
# zip可以拼接做任意个数量,即使两个参数的数量不一致,则按最短的来
print(list(zip([1,2,3],range(0,100), [1,2])))
# 输出 [(1, 0, 1), (2, 1, 2)]
# enumerate可以将序列自动添加索引,在需要对序列遍历并需要索引的时候很管用
list(enumerate([1,2,3]))
# 输出 [(0, 1), (1, 2), (2, 3)]
# reversed返回一个倒序的视图,用于反向遍历
list(reversed([1,2,3]))
# 输出 [3, 2, 1]
# break和continue的用法与其它语言基本一致
while True:
word = input('Please enter a word: ')
if not word:
break
print('The word was ', word)
# for可以配合else使用,只有当break没有执行到时,才会执行else下的内容
# 以下这个程序用于判断输入的数字是不是素数
n = int(input('enter a number: '))
for i in range(2,97):
if n % i == 0:
print("%d is a not prime number" % (n))
break
else:
print("%d is a prime number" % (n))
推导
推导类似于数学上的集合表示方式,与Haskell中的列表内包一致,例如[x*x|x<-[0...10]]
表示0~10的平方列表,[x|x<-[0...10],y%3==0]
表示0~10内可以被3整数的数,这些表达式使用Python的推导式表示如下:
>>> [x*x for x in range(0, 11)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
>>> [x for x in range(0, 11) if x%3==0]
[0, 3, 6, 9]
>>> [(x,y) for x in range(0, 4) for y in range(100, 102)]
[(0, 100), (0, 101), (1, 100), (1, 101), (2, 100), (2, 101), (3, 100), (3, 101)]
>>> {x:"{} * {} = {}".format(x,x,x*x) for x in range(0,10)}
{0: '0 * 0 = 0', 1: '1 * 1 = 1', 2: '2 * 2 = 4', 3: '3 * 3 = 9', 4: '4 * 4 = 16', 5: '5 * 5 = 25', 6: '6 * 6 = 36', 7: '7 * 7 = 49', 8: '8 * 8 = 64', 9: '9 * 9 = 81'}
可以看到改成使用花括号就可以写出字典的生成器了。
其它语句
pass:空语句,什么也不做,用于占位,解决Python不支持空语句块的问题;
del:取消变量的引用指向,并不能真正释放值,因为依赖于垃圾回收;
exec:将字符串当成Python脚本来执行,可以指定命名空间;
eval:与exec类似,执行语句,返回结果,而exec是不返回结果的:
>>> from math import sqrt
>>> scope={}
>>> exec("sqrt=1", scope)
>>> sqrt(4)
2.0
>>> scope["sqrt"]
1
>>> scope = {}
>>> scope['x'] = 2
>>> scope['y'] = 3
>>> eval('x * y', scope)
6
>>> exec('x = 4', scope)
>>> eval('x * x', scope)
16
函数
# 基本的函数定义,斐波那契数列
def fibs(num):
'generate a list of Fibonacci'
res = [0, 1]
for i in range(num-2):
res.append(res[i]+res[i+1])
return res
print(fibs(10))
# 输出[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
print(fibs.__doc__)
# 输出 generate a list of Fibonacci
help(fibs)
# 以下是输出
# Help on function fibs in module __main__:
#
# fibs(num)
# generate a list of Fibonacci
# 如果函数没有返回值,即空的return 语句,则实际会返回None
def noReturn():
return
print(noReturn())
# 输出None
# 对于传入的参数如果是可变类型,则修改会影响参数的值,可以通过切片(切片生成的序列是复制的)来避免这一点
def modify(a):
a[0] = 1
b = [0,1,2]
modify(b)
print(b)
# 输出[1,1,2]
# 如果传切片就不会有修改原值
b = [0,1,2]
modify(b[:])
print(b)
# 输出[0,1,2]
# 关键字参数与默认值,以及可变长参数
# 对于有默认值的参数,可以用关键字指定,带*号的变量表示会收集所有无关键字并且未匹配的参数,**表示收集关键字参数
def print_params(x, y, z=3, r=4, *pospar, **keypar):
print("x,y,z,r:", x, y, z, r, "\tpospar:", pospar, "\tkeypar", keypar)
# 有默认值的参数并非都要指定,可以跳过
print_params(1,2,r=9)
# 输出 x,y,z,r: 1 2 3 9 pospar: () keypar {}
# *和**指定的收集参数,都可以为空
print_params(1,2,z=3,r=4,k1=5,k2=6)
# 输出 x,y,z,r: 1 2 3 4 pospar: () keypar {'k1': 5, 'k2': 6}
print_params(1,2,3,4,5,6,7,8,k1=9,k2=10)
# 输出 x,y,z,r: 1 2 3 4 pospar: (5, 6, 7, 8) keypar {'k1': 9, 'k2': 10}
# 以下调用报错,因为关键字参数必须放在最后
# print_params(1,2,z=100,r=32,433,k=1,k3=2)
# 参数分配,将序列或者字典分配到对应的参数中去
def add(x,y,z):
return x+y+z
# 以下例子的结果都是6
add(1,2,3)
add(*(1,2,3))
add(*[1,2,3])
add(**{"x":1,"y":2,"z":3})
# 作用域
x = 10
y = 11
def g(y):
global x
x = x+1
y = y+1
g(y)
# 可以看到x已经变了,但y却没有影响
print(x,y)
# 闭包
def f(x):
def g(y):
return x+y
return g
# 每一次调用f()返回的函数,都包含了当时的x值
f1 = f(10)
f2 = f(11)
print(f1(1))
print(f2(1))
# 递归
# 阶乘
def factorial(n):
if n == 1:
return 1
else:
return n * factorial(n-1)
# 返回120
print(factorial(5))
# 幂
def power(x, n):
if n == 0:
return 1
else:
return x * power(x, n-1)
print(power(2,10))
# 二分查找
def search(sequence, number, lower = 0, upper = None):
if upper is None:
upper = len(sequence) - 1
if lower > upper:
return -1
mid = int((lower+upper) / 2)
if sequence[mid] == number:
return mid
elif sequence[mid] > number:
return search(sequence, number, 0, mid-1)
else:
return search(sequence, number, mid+1, upper)
l = [1,2,3,4,5,6,7,8,9]
i = search(l, 4, 0, len(l)-1)
print(i) # 输出3
类
# 类的创建和基本使用
# 这个self是约定命名,可以是任何变量名
class Person:
def set_name(self, name):
self.name = name
def get_name(self):
return self.name
def greet(self):
print("Hello, world! I'm {}.".format(self.name))
p1 = Person()
p1.set_name("Jonh")
p2 = Person()
p2.set_name("Lee")
p1.greet() # Hello, world! I'm Jonh.
p2.greet() # Hello, world! I'm Lee.
print(p1.get_name()) # Jonh
# 可以直接对属性进行设置
p1.name = "Mark"
print(p1.name) # Mark
# 类方法的调用,也可以使用类名的方式调用,我理解在类上的调用应该与此等价
Person.greet(p1) # Hello, world! I'm Mark.
# Python中没有私有属性,可以通过以下方法来达到私有属性的效果
class Sec :
__name = "J" # 两个下划线开头的属性或者方法,实际上都会添加下划线开头加类名的前缀
def set_name(self, name):
self.__name = name
def get_name(self):
return self.__name
def greet(self):
print("Hello, world! I'm {}.".format(self.__name))
s = Sec()
s.greet() # Hello, world! I'm J.
s.__name = "K"
s.greet() # Hello, world! I'm J.
s._Sec__name = "K" # 双下划线开头的会添加前缀,实际中不推荐这样使用
s.greet() # Hello, world! I'm K.
# 也可以对一个不存在的属性赋值
s.__k = "s"
# 单下划线也被视为不能直接修改约定,在使用from module import *中不会导入以下划线开头的名称
# 类名空间下的成员是所有对象共享的,也是独立于对象的,如果对象的属性没有定义,则使用类的属性
print(Sec._Sec__name) # J
# 所以类也可以被当成命名空间来使用,因为在类中可以直接执行语句
class C:
print("Class C being defined")
# 以上输出 Class C being defined
# 继承的基本使用
class Base:
def init(self):
self.p = "base"
def show(self):
print("I'm ", self.p)
# 在定义类时添加括号表示继承的基类
class Extend(Base):
def init(self):
self.p = "extend"
b = Base()
b.init()
b.show() # I'm base
e = Extend()
e.init()
e.show() # I'm extend
# 与继承相关的方法
# 是否是继承关系
print(issubclass(Extend, Base)) # True
# 获取基类,由于支持多重继承,所以还一个属性__bases__
print(Extend.__base__) # <class '__main__.Base'>
# object是所有类的基类
print(Base.__base__) # <class 'object'>
# 以下两个都返回True,判断对象是否是某个类
print(isinstance(Extend(), Base))
print(isinstance(Extend(), Extend))
# 获取对象属于哪个类
print(Extend().__class__) # <class '__main__.Extend'>
# 多重继承
# 如果写成Super(Base, Extend)则无法运行,因为不满足MRO,可阅读参考文献
# 所以在多重继承中注意两个超类含有同一个属性或者方法的情况
class Super(Extend, Base): pass
s = Super()
s.init()
s.show() # I'm extend
# 判断某一个对象是否有某一个属性,以及这个属性是否可调用
print(hasattr(s, "show")) # True
# getattr的第三个参数表示不存在时的默认值,如果不提供默认值且不存在,则报错
print(getattr(s, "p")) # extend
# callable检查是否可调用
print(callable(getattr(s, "show", None))) # True
# 设置对象的属性,返回None
setattr(s, "p1", "p1val")
# 获取所有属性
print(s.__dict__) # {'p': 'extend', 'p1': 'p1val'}
# 抽象基类
# 在Python中并没有提供原生的语法来支持抽象类,但可以使用abc这个官方模块解决此问题
from abc import ABC, abstractclassmethod
class Talker(ABC):
@abstractclassmethod
def talk(self): pass
# 以下语句会报错TypeError: Can't instantiate abstract class Talker with abstract methods talk
# t = Talker()
# 如果只是继承了而没有实现抽象方法,还是会报错
class Foo(Talker): pass
# t = Foo()
class Knigget(Talker):
def talk(self):
print("Ni")
k = Knigget()
# 一般的抽象类应用场景中会有isinstance判断
if isinstance(k, Talker):
k.talk() # Ni
# 但这与Python的鸭子类型的编程思想,如果一个类有talk方法,但没有继承Talker,则在此场景中没有办法调用talk
# register提供了将某一类注册为另外一个类子类的办法
class Herring:
def talk(self):
print("Blue")
h = Herring()
print(isinstance(h, Talker)) # False
# 将Herring 注册为Talker的子类
Talker.register(Herring)
print(isinstance(h, Talker)) # True
print(issubclass(Herring, Talker)) # True
# 这种做法的问题是,如果Herring没有实现talk方法,则此时调用talk方法会直接报错,如下:
class Clam: pass
Talker.register(Clam)
c = Clam()
if isinstance(c, Talker):
c.talk() # 这里将报错,因为Clam没有实现talk方法,但又是Talker的子类
异常
使用raise可以抛出异常:
>>> raise Exception
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
Exception
>>> raise Exception("hyperdrive overload")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
Exception: hyperdrive overload
>>> class MyException(Exception): pass
...
>>> raise MyException("My")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
__main__.MyException: My
Exception是几乎所有异常类的基类,raise可以直接跟类名,也可以跟对象。
可以直接继承Exception来定义自己的异常类。
除了Exception异常类,还有以下常用的内置异常类:
AttributeError :引用属性或给它赋值失败时引发;
OSError: 操作系统不能执行指定的任务(如打开文件)时引发,有多个子类;
IndexError : 使用序列中不存在的索引时引发,为LookupError的子类;
KeyError : 使用映射中不存在的键时引发,为LookupError的子类;
NameError : 找不到名称(变量)时引发;
SyntaxError : 代码不正确时引发;
TypeError :将内置操作或函数用于类型不正确的对象时引发;
ValueError : 将内置操作或函数用于这样的对象时引发:其类型正确但包含的值不合适;
ZeroDivisionError:除法或求模运算的第二个参数为零时引发。
捕获异常
# 异常的基本用法
try:
x = int(input('Enter the first number: '))
y = int(input('Enter the second number: '))
print(x / y)
except ZeroDivisionError: # 如果y为0,则为ZeroDivisionError,走此流程
print("The second number can't be zero!")
except (Exception, TypeError) as e: # 其它的Exception,走此流程,并捕获了异常的对象,将其打印出来,这里的TypeError多余,只是为了显示一次except可以捕获多个异常类型
print("Other error occur: ", e)
except as e: # 其它非Exception的异常走此流程,例如Ctrl+C中止程序的异常(KeyboardInterrupt),他的基类是BaseException(这类也是Exception的基类)
print("not Exception occur")
else: # 如果没有异常,会执行此语句块
print("Calculate complete")
finally: # 无论是否有异常,都会执行此语句块,一般用于做清理工作,例如连接的关闭等
print("clear")
有时候需要捕获异常之后记录日志并重新抛出异常,可以使用无参数的raise:
try: 1/0
except ZeroDivisionError as e:
print("The second number can't be zero!")
raise
# The second number can't be zero!
# Traceback (most recent call last):
# File "test.py", line 1, in <module>
# try: 1/0
# ZeroDivisionError: division by zero
try: 1/0
except ZeroDivisionError as e:
print("The second number can't be zero!")
raise ValueError
# 如果引发了别的异常,则在原先的异常会被存储在异常上下文中,在最终输出时体现出来
# 这里也可以写成raise ValueError from e
# The second number can't be zero!
# Traceback (most recent call last):
# File "test.py", line 1, in <module>
# try: 1/0
# ZeroDivisionError: division by zero
#
# During handling of the above exception, another exception occurred:
#
# Traceback (most recent call last):
# File "test.py", line 4, in <module>
# raise ValueError
# ValueError
try: 1/0
except ZeroDivisionError as e:
print("The second number can't be zero!")
raise ValueError from None
# 如果需要禁用上下文,可以使用from None
# The second number can't be zero!
# Traceback (most recent call last):
# File "test.py", line 4, in <module>
# raise ValueError from None
# ValueError
警告
warning包提供了一些警告相关的工具,他们会在控制台输出警告信息而不打断程序的运行。
此包也提供了对这些警告的控制,例如过滤某些警告,将某些警告上升为异常等等。
更多内容可以参考此:
from warnings import warn,filterwarnings
warn("I've got a bad feeling about this.")
print("I'm ok")
# test.py:2: UserWarning: I've got a bad feeling about this.
# warn("I've got a bad feeling about this.")
# I'm ok
filterwarnings("ignore")
warn("I've got a bad feeling about this.") # 不再输出
filterwarnings("error")
warn("I've got a bad feeling about this.") # 直接抛出异常
魔法方法、特性和迭代器
构造函数
class FooBar:
# 构造函数
def __init__(self, value = 42):
self.somevar = value
# 析构函数,在垃圾回收时被调用,但这个调用的时机很难把握
def __del__(self):
self.somevar = 1111
print("del", self.somevar)
print(FooBar().somevar)
print(FooBar("hhh").somevar)
# 以下是这个输出,很奇怪的是析构函数的输出早于正常输出
# del 1111
# 42
# del 1111
# hhh
# 函数重写
class Bird:
def __init__(self):
self.hungry = True
def eat(self):
if self.hungry:
print("Aaaah..")
else:
print("No, thanks")
class SongBird(Bird):
def __init__(self):
# 调用超类的构造函数方法,也可以使用Bird.__init__(self),但使用super可以在有多个超类的情况下也能正确处理
super().__init__()
self.sound = "Squawk!"
def sing(self):
print(self.sound)
s = SongBird()
s.eat()
序列协议
def check_index(key):
"""
指定的键是否是可接受的索引?
键必须是非负整数,才是可接受的。如果不是整数, 将引发TypeError异常;如果是负数,将引发Index Error异常(因为这个序列的长度是无穷的)
"""
if not isinstance(key, int):
raise TypeError
if key < 0: raise IndexError
class ArithmeticSequence:
def __init__(self, start = 0, step = 1, max = 1000):
"""
初使化这个算术序列
start -序列中的第一个值
step -两个相邻序列的差
max -最大值,包括此最大值
changed -一个字典,保存用户修改过的值
"""
self.start = start
self.step = step
self.max = max
self.changed = {}
def __getitem__(self, key):
"""
从序列中获取一个元素
"""
check_index(key)
try: return self.changed[key]
except KeyError:
return self.start + self.step * key
def __setitem__(self, key, value):
"""
修改算术序列中的元素
"""
check_index(key)
self.changed[key] = value
def __len__(self):
"""
返回序列的长度
"""
return int((self.max-self.start)/self.step)
def __delitem__(self, key):
"""
删除对应的change值,返回默认值
"""
check_index(key)
del self.changed[key]
s = ArithmeticSequence(0, 1)
print(s[4], s[5]) # 依赖于__getitem__方法,输出 4 5
s[5] = 10 # 依赖于__setitem__方法,将值 设置到changed里
print(s[5], s[4]) # 输出10 4
print(len(s)) # 依赖于__len__方法,输出 1000
del s[5] # 依赖于__delitem__
print(s[5]) # 输出5
通过继承list来实现扩展列表的功能:
# 从list中继承来扩展列表
class CounterList(list):
def __init__(self, *args):
super().__init__(*args)
self.counter = 0
# 有一些操作访问的方法并不都调用__getitem__,所以像pop这一类的操作无法增加计数
def __getitem__(self, index):
self.counter += 1
return super().__getitem__(index)
c = CounterList([0,1,2,3,4,5,6,7])
print(c[1] + c[2]) # 3
print(c.counter) # 2
更多的魔法方法可以参考Special method names。
特性(property)
一般的属性我们会为其设置get和set方法。通过property可以把一个虚拟的属性当成正常的属性来设置和获取:
class Rectangle:
def __init__(self):
self.width = 0
self.height = 0
def set_size(self, size):
self.width, self.height = size
def get_size(self):
return (self.width, self.height)
size = property(get_size, set_size)
r1 = Rectangle()
# 正常我们访问size时,是通过get和set方法来的
r1.set_size((10,20))
print(r1.get_size()) # (10, 20)
# 添加了size = property(get_size, set_size)之后,就可以直接给属性赋值和访问了
# property方法还支持可选的fdel,删除函数,doc文档 等参数
r1.size = (20, 40)
print(r1.size, r1.get_size()) # (20, 40) (20, 40)
property的实现原理是通过property类中的__get__
、__set__
、__delete__
这些魔术方法来处理的。这些方法被定义为描述符协议,可以拦截对属性的访问,设置和删除。
除了使用property来实现对象属性的访问拦截,也可以通过以下这些方法来实现;相比函数property,这些魔法方法使用起来要棘手些(从某种程度上说,效率也更低),但在同时处理多个特性时很有用:
__getattribute__(self, name):在属性被访问时自动调用(只适用于新式类);
__getattr__(self, name):在属性被访问而对象没有这样的属性时自动调用;
__setattr__(self, name, value):试图给属性赋值时自动调用;
__delattr__(self, name):试图删除属性时自动调用。
class Rectangle:
def __init__(self):
self.width = 0
self.height = 0
# 所有的设置都调用__setattr__,所以也要处理好非size的属性赋值,对__dict__的赋值不会再次调用__setattr__
def __setattr__(self, name, value):
if name == 'size':
self.width, self.height = value
else:
self.__dict__[name] = value
# 只有在没有找到对应属性的情况下才会调用__getattr__,所以非size的,都直接报错,这个错一定要是AttributeError才能在hasattr和getattr方法下正确处理
def __getattr__(self, name):
if name == "size":
return (self.width, self.height)
else:
raise AttributeError()
# 不使用__getattr__也可以使用__getattribute__方法,注意这个方法是代理所有的访问,包括__dict__的问题,所以需要调用到超类的此方法
# def __getattribute__(self, name):
# if name == "size":
# return (self.width, self.height)
# else:
# return super().__getattribute__(name)
r = Rectangle()
r.size = (10, 20)
r.a = 100
print(r.size, r.a, r.width) # (10, 20) 100 10
类方法和静态方法
# 静态方法与类方法
class MyClass:
# 这个注解也可以换成使用这一语句smeth = staticmethod(smeth)
@staticmethod
def smeth():
print('This is a static method')
# 这个注解也可以换成使用这一语句cmeth = classmethod(smeth)
@classmethod
def cmeth(cls):
print('This is a class method of ', cls)
MyClass.smeth() # This is a static method
MyClass.cmeth() # This is a class method of <class '__main__.MyClass'>
迭代器
实现了__iter__
的对象可以使用for来迭代;__iter__
方法返回包含有__next__
方法的迭代器对象,这个__next__
方法没有参数,返回下一个值,如果没有值了,需要引发StopIteration异常:
class Fibs:
def __init__(self, max = 1000):
self.a = 0
self.b = 1
self.max = max
def __next__(self):
self.a, self.b = self.b, self.a + self.b
if self.b > self.max:
raise StopIteration()
return self.b
def __iter__(self):
return self
for i in Fibs():
print(i)
print(list(Fibs())) # [1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]
生成器
# 包含yield语句的函数被称之为生成器,生成器可以生成多个值,每一次使用yield生成一个值之后就被冻结,等待被重新唤醒。
# 调用生成器的函数,返回了生成器的迭代器,可以像使用普通迭代一样来使用生成器的迭代器
def p(max):
for i in range(max):
yield i**2
gp = p(10)
print(gp) # <generator object p at 0x10d486550>
print(next(gp), next(gp), next(gp), next(gp), next(gp), next(gp)) # 0 1 4 9 16 25
# 也可以使用类似于列表推导的方式(将中括号换成小括号)来创建生成器
print((i**2 for i in range(10))) # <generator object <genexpr> at 0x10d5584d0>
# 如果直接既有的小括号内使用生成器推导,则可以省掉生成器使用的小括号,如下:
print(i**2 for i in range(10)) # <generator object <genexpr> at 0x10d5584d0>
# 将一个n层的嵌套列表,展开成一级列表
def flatten(nested):
try:
# 字符串即使单个字符也可以迭代,所以需要对字符串做特殊处理
try: nested + ''
except TypeError: pass
else: raise TypeError
for sublist in nested:
for element in flatten(sublist):
yield element
except TypeError:
yield nested
print(list(flatten([[[1], 2], 3, 4, [5, [6, 7]], 8]))) # [1, 2, 3, 4, 5, 6, 7, 8]
print(list(flatten(['foo', ['bar', ['baz']]]))) # ['foo', 'bar', 'baz']
# 外部可以通过生成器的迭代器的send方法往生成器中发送数据,体现出来的就是yield的返回值,这与next的区别就是next唤醒的yield,返回值为None
def seqWithSkip(begin):
i = begin
while True:
skip = (yield i)
if skip is not None: i+=skip
i+=1
sws = seqWithSkip(100)
print(next(sws), next(sws), next(sws)) # 100 101 102
print(sws.send(100), next(sws), next(sws)) # 203 204 205
八皇后问题
# 八皇后问题,state为已经确认的前几行皇后的位置,判断下一行的位置nextX是否会冲突
# 冲突的定义是有两个皇后在同一列,或者在同一行或者在对角线上
def conflict(state, nextX):
nextY = len(state)
for i in range(nextY):
if state[i] == nextX or abs(state[i] - nextX) == abs(i - nextY):
return True
return False
# 使用递归的方式来解题,queens函数根据皇后数量(棋盘的大小)和给出的状态,补全剩下的状态
# 下一个状态肯定是从0~num中选一个,而且必须满足条件的,假设取出了pos
# 如果这个pos是最后一个(这也是递归的终结条件),则直接返回(pos,)此元组
# 如果非最后一个,则将此位置放入state中,递归求剩下的状态,返回的满足条件的元组再补上pos就是最终要求的状态了
def queens(num = 8, state = ()):
for pos in range(num):
if not conflict(state, pos):
if len(state) == num - 1:
yield (pos,)
else:
for result in queens(num, state + (pos,)):
yield (pos,) + result
print(list(queens(4))) # [(1, 3, 0, 2), (2, 0, 3, 1)]
print(list(queens(8)))
模块
可以使用sys.path.append
添加模块的查找路径,注意模块只会导入一次,可以使用importlib模块的importlib.reload(queens)
来重新导入。
>>> import sys
>>> sys.path
['', '/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python37.zip', '/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7', '/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/lib/python3.7/lib-dynload', '/Users/liqingshou/Library/Python/3.7/lib/python/site-packages', '/usr/local/lib/python3.7/site-packages', './']
>>> sys.path.append("./")
>>> import queens
>>> list(queens.queens(4))
[(1, 3, 0, 2), (2, 0, 3, 1)]
>>> import importlib
>>> importlib.reload(queens)
模块的测试很重要,可以在模块中添加测试代码。为了不在正常导入模块时运行测试代码,可以使用以下方式if __name__ == '__main__': test()
,这样就不会在导入的时候运行了。
更专业的测试代码应该使用独立的程序来处理。
除修改sys.path
的方法来改变模块的搜索目录外,还可以通过环境变量的方式:
$ PYTHONPATH=$PYTHOPATH:./ python3
Python 3.7.7 (default, Mar 10 2020, 15:43:33)
[Clang 11.0.0 (clang-1100.0.33.17)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import queens
>>> list(queens.queens(4))
[(1, 3, 0, 2), (2, 0, 3, 1)]
在某一个目录当中添加__init__.py
文件,则这个目录可以被识别为一个包,可以像导入模块一样导入包。
例如目录结构为:
drawing
├── __init__.py
├── colors.py
└── shapes.py
则可以使用以下语句:
import drawing: 可使用__init__.py中定义的内容,但不能使用colors和shapes的内容;
import drawing.colors:可使用colors的内容,但必须使用drawing.colors全限定名,当然__init__.py中的内容也可以使用;
from drawing import shapes:可直接通过shapes.xxx来使用shapes模块中的内容,当然__init__.py中的内容也可以使用。
探索模块
使用dir函数,可以获得模块中的所有属性,包括类、函数、变量等,另外如果模块中包含了__all__
这样的变量,那么使用from module import *
的话只会导入__all__
所包含的属性,其它的只能通过精确导入才行。
# drawing/colors.py
def green():
"""
just a demo function of green
"""
pass
def red():pass
__all__ = ["green"]
>>> from drawing.colors import *
>>> green()
>>> red()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'red' is not defined
使用help函数可以获取对应函数的文档help(green)
,如果想获取doc字段可以使用print(green.__doc__)
。
通过模块的__file__
属性可以获取这个模块的文件路径:
>>> import drawing; print(drawing.__file__)
/Users/liqingshou/Work/xiaochai/batman/Python/drawing/__init__.py
一些标准库
sys
argv: 命令行参数,包括脚本名;
exit([arg]):退出当前程序,可通过可选参数指定返回值或错误消息;
modules:一个字典,将模块名映射到加载的模块;
path:一个列表,包含要在其中查找模块的目录的名称;
platform: 一个平台标识符,如sunos5或win32;
stdin:标准输入流——一个类似于文件的对象;
stdout:标准输出流——一个类似于文件的对象;
stderr:标准错误流——一个类似于文件的对象。
os
environ:包含环境变量的映射;
system(command):在子shell中执行操作系统命令;
sep:路径中使用的分隔符;
pathsep 分隔不同路径的分隔符;
linesep:行分隔符(‘\n’、’\r’或’\r\n’);
urandom(n):返回n个字节的强加密随机数据:
>>> os.environ
environ({'USER': 'liqingshou', 'COMMAND_MODE': 'unix2003', ...})
>>> os.system("pwd")
/Users/liqingshou/Work/xiaochai/batman/Python
0
>>> os.sep
'/'
>>> os.pathsep
':'
>>> os.linesep
'\n'
>>> os.urandom(10)
b'\x85\x9cs\xfa\xecA\xc5\xfa@\xa4'
fileinput
input([files[, inplace[, backup]]]):帮助迭代多个输入流中的行,如果指定了inplace为True,则原文件将被标准输出的内容所替代;
filename():返回当前文件的名称;
lineno():返回(累计的)当前行号;
filelineno():返回在当前文件中的行号;
isfirstline():检查当前行是否是文件中的第一行;
isstdin():检查最后一行是否来自sys.stdin;
nextfile():关闭当前文件并移到下一个文件;
close():关闭序列:
# 对于一系列输入了的文件,在开头添加文件名,在行末添加行号,只取前5行
import fileinput
for line in fileinput.input(inplace=False):
if fileinput.isfirstline():
print("# filename:", fileinput.filename(),
", isstdin:", fileinput.isstdin())
if fileinput.filelineno() >= 5:
fileinput.nextfile()
line = line.rstrip()
print('{:<50} # {:2d} # {} # '.format(
line, fileinput.lineno(),
fileinput.filelineno()))
fileinput.close()
# 运行命令 python3 test.py test.py test.py
# 运行结果如下:
# filename: test.py , isstdin: False
# import fileinput # 1 # 1 #
# for line in fileinput.input(inplace=False): # 2 # 2 #
# if fileinput.isfirstline(): # 3 # 3 #
# print("# filename:", fileinput.filename(), # 4 # 4 #
# ", isstdin:", fileinput.isstdin()) # 5 # 5 #
# # filename: test.py , isstdin: False
# import fileinput # 6 # 1 #
# for line in fileinput.input(inplace=False): # 7 # 2 #
# if fileinput.isfirstline(): # 8 # 3 #
# print("# filename:", fileinput.filename(), # 9 # 4 #
# ", isstdin:", fileinput.isstdin()) # 10 # 5 #
集合
新版本的Python中内置支持了set集合,创建方式使用花括号表示,注意空的花括号会被识别为字典。
集合上支持一些操作:
union:取并集,与符号|一致;
intersection: 取交集,与符号&一致;
issubset:调用者是否是参数指定集合的子集;
differece:调用集合排除掉参数集合后的结果,与符号-一致;
symmetric_difference:取两个集合不一样的用户,与^符号一致;
add:往集合中添加元素;
remove:从集合中删除元素:
>>> type({})
<class 'dict'>
>>> type({1})
<class 'set'>
>>> {1,2,2,2,2,2,3}
{1, 2, 3}
>>> a = {1,2,3}
>>> b = {2,3,4}
>>> a | b
{1, 2, 3, 4}
>>> a.union(b)
{1, 2, 3, 4}
>>> a & b
{2, 3}
>>> a.intersection(b)
{2, 3}
>>> a.issubset(b)
False
>>> a.issubset(a)
True
>>> a.difference(b)
{1}
>>> a-b
{1}
>>> b-a
{4}
>>> a.symmetric_difference(b)
{1, 4}
>>> a ^ b
{1, 4}
>>> a.add(5)
>>> a
{1, 2, 3, 5}
集合是可变的,所以不能用作字典的键;
另外集合的成员只能是不可变的值,所以现实中的集合的集合需要借助于frozenset类型(不可变集合)来实现:
>>> a = {1}
>>> b = {2}
>>> a.add(b)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'set'
>>> a.add(frozenset(b))
>>> a
{1, frozenset({2})}
堆
Python中的堆数据结构使用列表与一些函数配合实现,主要提供的函数有以下这些:
heappush(heap, x):将x压入堆中;
heappop(heap):从堆中弹出最小的元素;
heapify(heap):让列表具备堆特征;
heapreplace(heap, x):弹出最小的元素,并将x压入堆中;
nlargest(n, iter):返回iter中n个最大的元素;
nsmallest(n, iter):返回iter中n个最小的元素:
>>> l = [99,2,87,27,19,50,92,43,39]
>>> from heapq import *
>>> l
[99, 2, 87, 27, 19, 50, 92, 43, 39]
>>> heapify(l)
>>> l
[2, 19, 50, 27, 99, 87, 92, 43, 39]
>>> heappop(l)
2
>>> l
[19, 27, 50, 39, 99, 87, 92, 43]
>>> heappop(l)
19
>>> heapreplace(l, 1)
27
>>> l
[1, 39, 50, 43, 99, 87, 92]
>>> nlargest(2, l)
[99, 92]
>>> l
[1, 39, 50, 43, 99, 87, 92]
>>> nsmalle
双端队列
双端队列在collections模块中的deque类型,以下示例展示了常用的函数使用方式:
>>> from collections import deque
>>> q = deque([1,2,3,4,5])
>>> q
deque([1, 2, 3, 4, 5])
>>> q.append(6)
>>> q.appendleft(0)
>>> q
deque([0, 1, 2, 3, 4, 5, 6])
>>> q.pop()
6
>>> q.popleft()
0
>>> q
deque([1, 2, 3, 4, 5])
>>> q.rotate(3)
>>> q
deque([3, 4, 5, 1, 2])
>>> q.rotate(-1)
>>> q
deque([4, 5, 1, 2, 3])
time
asctime([tuple]):将时间元组转换为字符串;
localtime([secs]):将秒数转换为表示当地时间的日期元组;
mktime(tuple):将时间元组转换为当地时间;
sleep(secs):休眠(什么都不做)secs秒;
strptime(string[, format]):将字符串转换为时间元组;
time():当前时间(从新纪元开始后的秒数,以UTC为准);
以上的tuple为8元组,分别为(年,月,日,时,分,秒,星期,儒略日,夏令时):
>>> l = time.localtime()
>>> l
time.struct_time(tm_year=2020, tm_mon=6, tm_mday=20, tm_hour=20, tm_min=12, tm_sec=52, tm_wday=5, tm_yday=172, tm_isdst=0)
>>> l = list(l)
>>> l[0] = 2019
>>> time.asctime(tuple(l))
'Sat Jun 20 20:12:52 2019'
>>> time.time()
1592655205.469694
random
random():返回一个0~1(含)的随机实数;
getrandbits(n):以长整数方式返回n个随机的二进制位;
uniform(a, b):返回一个a~b(含)的随机实数;
randrange([start], stop, [step]):从range(start, stop, step)中随机地选择一个数;
choice(seq):从序列seq中随机地选择一个元素;
shuffle(seq[, random]):就地打乱序列seq;
sample(seq, n):从序列seq中随机地选择n个值不同的元素:
>>> import random
>>> random.random()
0.2918300245721943
>>> random.getrandbits(10)
831
>>> random.uniform(20,30)
21.144689897763417
>>> random.randrange(1000, 2000, 1)
1890
>>> random.choice(range(100))
49
>>> l = list(range(10))
>>> random.shuffle(l)
>>> l
[9, 8, 2, 4, 1, 5, 3, 7, 0, 6]
>>> random.sample(l, 3)
[8, 4, 6]
shelve
shelve算是一个将python的数据结构序列化到文件中的一个方便的类:
$ python3
Python 3.7.7 (default, Mar 10 2020, 15:43:33)
>>> import shelve
>>> s = shelve.open('test.dat')
>>> s['x'] = ['a','b','c']
>>> s['y'] = {"a" : 1}
>>>
$ python3
Python 3.7.7 (default, Mar 10 2020, 15:43:33)
>>> import shelve
>>> s = shelve.open('test.dat')
>>> s['x']
['a', 'b', 'c']
>>> s['y']
{'a': 1}
import shelve
def enter_command():
cmd = input("Enter command(? for help):")
cmd = cmd.strip().lower()
return cmd
def store_person(db):
id = input("Enter ID:")
person = {}
person['name'] = input('Enter name:')
person['age'] = input('Enter age:')
db[id] = person
def lookup_person(db):
id = input("Enter ID:")
print(db[id])
def print_help():
print("""The available commands are:
store: stores information
lookup: looks up a person by ID
quit: save change and exit
?: prints this message
""")
def main():
database = shelve.open("./database.dat")
try:
while True:
cmd = enter_command()
if cmd == 'store':
store_person(database)
elif cmd == 'lookup':
lookup_person(database)
elif cmd == '?':
print_help()
elif cmd == 'quit':
return
finally:
database.close()
if __name__ == '__main__': main()
# 以下是运行结果
# ➜ Python git:(master) ✗ python3 database.py
# Enter command(? for help):'?
# The available commands are:
# store: stores information
# lookup: looks up a person by ID
# quit: save change and exit
# ?: prints this message
#
# Enter command(? for help):'store
# Enter ID:1
# Enter name:Lee
# Enter age:10
# Enter command(? for help):'store
# Enter ID:2
# Enter name:Chen
# Enter age:12
# Enter command(? for help):'quit
# ➜ Python git:(master) ✗ python3 database.py
# Enter command(? for help):lookup
# Enter ID:1
# {'name': 'Lee', 'age': '10'}
# Enter command(? for help):lookup
# Enter ID:2
# {'name': 'Chen', 'age': '12'}
# Enter command(? for help):quit
re正则
compile(pattern[, flags]) : 根据包含正则表达式的字符串创建模式对象;
search(pattern, string[, flags]): 在字符串中查找模式;
match(pattern, string[, flags]) :在字符串开头匹配模式,注意与search的不同,match是整个字符串匹配,而search可以部分匹配;
split(pattern, string[, maxsplit=0]):根据模式来分割字符串;
findall(pattern, string): 返回一个列表,其中包含字符串中所有与模式匹配的子串;
sub(pat, repl, string[, count=0]):将字符串中与模式pat匹配的子串都替换为repl ;
escape(string):对字符串中所有的正则表达式特殊字符都进行转义:
>>> p = re.compile('[0-9]+')
>>> p
re.compile('[0-9]+')
>>> re.search(p, "ik2131,k21lsdf")
<re.Match object; span=(2, 6), match='2131'>
>>> re.match(p, "ik2131,k21lsdf")
>>> re.match(p, "32423423")
<re.Match object; span=(0, 8), match='32423423'>
>>> re.search('[a-z]+', '4323kljjk32jlj342')
<re.Match object; span=(4, 9), match='kljjk'>
>>> p.split("3423kjlkjl2432kjl34j24l34")
['', 'kjlkjl', 'kjl', 'j', 'l', '']
>>> re.findall('[a-z]+', '4323kljjk32jlj342')
['kljjk', 'jlj']
>>> p.sub("....", '4323kljjk32jlj342')
'....kljjk....jlj....'
>>> re.escape("[0-9]+")
'\\[0\\-9\\]\\+'
>>> re.sub("([0-9]+?)", r"\1\1", "kjl23423,32424")
'kjl2233442233,3322442244'
>>> re.sub("([0-9]+)", r"\1\1", "kjl23423,32424")
'kjl2342323423,3242432424'
match和search如果没有匹配返回None,匹配上时会返回re.Match对象,此对象上可以应用如下方法:
groups():以元组的方式返回所有的匹配编组;
group([group1, …]):获取与给定子模式(编组)匹配的子串;
start([group]):返回与给定编组匹配的子串的起始位置 ;
end([group]):返回与给定编组匹配的子串的终止位置(与切片一样,不包含终止位置);
span([group]):返回与给定编组匹配的子串的起始和终止位置:
>>> str = "Info, Name:Lee,Age:23,From:China"
>>> g = re.match(".*Name:([a-zA-Z.]+),[ ]?Age:([0-9]+).*", str)
>>> g.groups()
('Lee', '23')
>>> g.group(2)
'23'
>>> str[g.start(1):g.end(1)]
'Lee'
>>> g.span(1)
(11, 14)
其它模块
argparse:解析命令行参数,比sys.argv更加好用;
cmd:这个模块让你能够编写类似于Python交互式解释器的命令行解释器。你可定义命令, 让用户能够在提示符下执行它们;
csv:csv文件的解析与写入;
json:json字符串的处理;
datetime: 更多的日期操作支持;
difflib:比较两个序列的相似程度;
enum:对于枚举的支持;
functools、hashlib、itertools、logging、statistics、timeit、profile、trace等等。
文件
open函数用于打开文件,可以指定一个模式选项:
r:读取模式(默认值);
w:写入模式;
x:独占写入模式;
a:追加加模式 ;
b:二进制模式(与其他模式结合使用);
t:文本模式(默认值,与其他模式结合使用);
+:读写模式(与其他模式结合使用),r+和w+都表示读写,但是w+会截断文件。
open返回的对象,可以使用write,read,close这些函数;sys.stdin
也是一个资源对象:
>>> f = open("./a", "w+")
>>> f.write("helloworld\n")
11
>>> f.writelines(["1\n", "2\n"])
>>> f.close()
>>> f = open("./a", "r+")
>>> f.read(1)
'h'
>>> f.readline()
'elloworld\n'
>>> f.seek(0)
0
>>> f.readlines()
['helloworld\n', '1\n', '2\n']
>>> f.close()
文件打开后需要调用close()关闭文件,为了防止文件被锁定无法修改,或者占用太多的文件描述符号。一般我们使用以下语句来处理关闭文件:
f = open("./a", "w+")
try:
# 写入数据
f.write("xxx\n")
finally:
f.close()
# with语句专门用于处理此情况,称之为上下文管理器
with open("./a", "w+") as f:
f.write("xxx\n")
在Python中,打开的文件是可以直接迭代的,相当于每一次读取一行的文件内容:with open("./clz.py", "r") as f: list(f)
。
GUI
通过简单的文件加载编辑器来演示tkinter的使用:
from tkinter import *
from tkinter.scrolledtext import ScrolledText
def load():
with open(filename.get()) as f:
# 表示删除第一行第0个字符开始,到结束
contents.delete('1.0', END)
contents.insert(INSERT, f.read())
def save():
with open(filename.get(), 'w') as f:
f.write(contents.get('1.0', END))
top = Tk()
top.title("Editor")
contents = ScrolledText()
contents["bg"] = "black"
contents["fg"] = "white"
contents.pack(side=BOTTOM, expand=True, fill=BOTH)
filename = Entry()
filename.pack(side=LEFT, expand=True, fill=X)
openBtn = Button(text="Open", command=load, highlightbackground='black')
openBtn.pack(side=LEFT)
saveBtn = Button(text="Save", command=save, highlightbackground='black')
saveBtn.pack(side=LEFT)
mainloop()
数据库
Python中对数据库定义了一些标准,所有的第三方数据库需要按照这个标准来实现。
全局变量
apilevel:用于指代这个模块所支持的标准数据库接口的版本号,Python中的DB API2.0指出这个值可以是1.0或者2.0,如果不存在这个变量,说明这个模块不兼容DB API2.0标准;
threadsafety:模块的线程安全级别,3表示绝对的线程安全,2表示线程可以共享模块和连接但不能共享游标,1表示可共享模块,但不能共享连接,0表示也不能共享模块;
paramstyle:在SQL查询中使用的参数风格支持,format表示使用标准的字符串格式,如%s等;pyformat使用字典的形式如%(foo)s;qmark使用问号占位,numeric使用数字编号占位如:1、:2这样;named使用命名占位如:foobar。
异常
DB API定义的一些通用异常:
StandardError: 所有异常的超类;
Warning:发生非致命问题时引发;
Error:所有错误条件的超类;
InterfaceError:与接口(而不是数据库)相关的错误;
DatabaseError:与数据库相关的错误的超类;
DataError:与数据相关的问题,如值不在合法的范围内;
OperationalError :数据库操作内部的错误;
IntegrityError :关系完整性遭到破坏,如键未通过检查;
InternalError :数据库内部的错误,如游标无效;
ProgrammingError :用户编程错误,如未找到数据库表;
NotSupportedError:请求不支持的功能,如回滚。
一些标准函数
connect:连接数据函数,其它参数支持有dsn(数据源名称,因数据库而异);user(用户名);password(密码);host(主机);database(数据库名称)。connect会返回连接对象,表示当前的数据库会话,支持以下方法:
close():关闭连接,之后连接对象以及其游标都不可用;注意对于支持事务的数据库,关闭连接会把没有提交的事务回滚了,所以注意在关闭之前提交事务;
commit():提交事务,如果此数据库不支持事务,则这个函数啥也不做;
rollback():回滚操作,如果数据库不支持事务,则抛出异常;
cursor():返回连接的游标对象,使用游标来执行SQL查询和查看结果。
游标对象支持下以方法和属性:
callproc(name[, params]) :使用指定的参数调用指定的数据库过程(可选);
close():关闭游标。关闭后游标不可用;
execute(oper[, params]) :执行一个SQL操作——可能指定参数;
executemany(oper, pseq) :执行指定的SQL操作多次,每次都序列中的一组参数;
fetchone() :以序列的方式取回查询结果中的下一行;如果没有更多的行,就返回None;
fetchmany([size]) :取回查询结果中的多行,其中参数size的值默认为arraysize;
fetchall():以序列的序列的方式取回余下的所有行;
nextset() :跳到下一个结果集,这个方法是可选的;
setinputsizes(sizes) :用于为参数预定义内存区域;
setoutputsize(size[, col]):为取回大量数据而设置缓冲区长度;
description :由结果列描述组成的序列(只读);
rowcount :结果包含的行数(只读);
arraysize:fetchmany返回的行数,默认为1。
类型
Date(year, month, day) :创建包含日期值的对象;
Time(hour, minute, second) :创建包含时间值的对象;
Timestamp(y, mon, d, h, min, s) :创建包含时间戳的对象;
DateFromTicks(ticks) :根据从新纪元开始过去的秒数创建包含日期值的对象;
TimeFromTicks(ticks) 根据从新纪元开始过去的秒数创建包含时间值的对象;
imestampFromTicks(ticks) :根据从新纪元开始过去的秒数创建包含时间戳的对象;
Binary(string):创建包含二进制字符串值的对象;
STRING:描述基于字符串的列(如CHAR);
BINARY:描述二进制列(如LONG或RAW);
NUMBER:描述数字列;
DATETIME:描述日期/时间列;
ROWID:描述行ID列。
一些例子
使用sqlite存储和查询数据:
import sqlite3
conn = sqlite3.connect('my.db')
curs = conn.cursor()
try:
curs.execute("""
create table people(
id int primary key,
name string,
age int
)
""")
except sqlite3.DatabaseError as e:
print("create table:", e, "; but continue")
data = [
[1, "Lee", 10],
[2, "Lucy", 12],
[3, "John", 9],
[4, "Lily", 10],
[5, "Green", 11]
]
sql = "insert into people values(?,?,?)"
for row in data :
try:
curs.execute(sql, row)
except sqlite3.DatabaseError as e:
print("insert error:", e, data, ",oh..")
curs.execute("select * from people where age >= 11")
#print(list(curs.description))
# [('id', None, None, None, None, None, None), ('name', None, None, None, None, None, None), ('age', None, None, None, None, None, None)]
#print(list(curs.fetchall()))
# [('id', None, None, None, None, None, None), ('name', None, None, None, None, None, None), ('age', None, None, None, None, None, None)]
names = [f[0] for f in curs.description]
for row in curs.fetchall():
for pair in zip(names, row):
print('{}:{}'.format(*pair))
print()
# id:2
# name:Lucy
# age:12
#
# id:5
# name:Green
# age:11
如果是mysql的话,需要安装pymysql扩展pip3 install cryptography pymysql
;另外可以使用dokcer启动一个mysql:
$ docker run -p 3306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql
$ docker exec -it mysql bash
root@088053eba10b:/# mysql -uroot -p123456
mysql> create database test;
import pymysql
conn = pymysql.connect("localhost","root","123456","test",charset='utf8')
curs = conn.cursor()
try:
curs.execute("""
create table people(
id int primary key,
name varchar(100),
age int
)
""")
except pymysql.DatabaseError as e:
print("create table:", e, "; but continue")
data = [
[1, "Lee", 10],
[2, "Lucy", 12],
[3, "John", 9],
[4, "Lily", 10],
[5, "Green", 11]
]
# pymysql中的paramstyle为pyformat,而且所有的参数必须为字符串,即%s
sql = "insert into people values(%s,%s,%s)"
for row in data :
try:
# 由于mysql需要将所有的参数转成字符串
curs.execute(sql, row)
except pymysql.DatabaseError as e:
print("insert error:", e, row, ",oh..")
curs.execute("select * from people where age >= 11")
#print(list(curs.description))
# [('id', None, None, None, None, None, None), ('name', None, None, None, None, None, None), ('age', None, None, None, None, None, None)]
#print(list(curs.fetchall()))
# [('id', None, None, None, None, None, None), ('name', None, None, None, None, None, None), ('age', None, None, None, None, None, None)]
names = [f[0] for f in curs.description]
for row in curs.fetchall():
for pair in zip(names, row):
print('{}:{}'.format(*pair))
print()
# id:2
# name:Lucy
# age:12
#
# id:5
# name:Green
# age:11
# 一定要commit, 因为mysql支持回滚,所以如果没有commit,则之前的添加全部没有了
conn.commit()
conn.close()
网络
使用socket展示一个简单的客户端和服务端:
import socket
import sys
def server():
s = socket.socket()
s.bind(("127.0.0.1", 8888))
s.listen(5) # 这个参数是backlog
while True:
c, addr = s.accept()
# Got connection from ('127.0.0.1', 64504)
print('Got connection from ', addr)
c.sendall(b'Thank you for connecting')
c.close()
def client():
s = socket.socket()
s.connect(("127.0.0.1", 8888))
print(s.recv(1024))
# 一个简单地只能每一次处理一个请求的server和client示例
if len(sys.argv) >= 2 and sys.argv[1] == "server":
server()
else:
client()
使用urllib和urllib2这两个网络库可以很方便地访问网络文件:
urlopen返回的对象是类似于文件资源的对象,所以可以使用类似read等方法以及迭代等,读取出来的内容可以通过正则来获取想要的数据。
urlretrieve可以下载文件到指定路径,如果文件没有指定,会存储在一个临时的位置,可以调用urlcleanup函数(不带参数)来直接清理对应的文件。
>>> import urllib.request as urlreq
>>> webpage = urlreq.urlopen('http://baidu.com')
>>> webpage.read()
b'<html>\n<meta http-equiv="refresh" content="0;url=http://www.baidu.com/">\n</html>\n'
>>> urlreq.urlretrieve("http://baidu.com", "./a")
('./a', <http.client.HTTPMessage object at 0x1057ce730>)
>>> import urllib.parse
>>> urllib.parse.urlencode({"callback":"http://baidu.com","name":"Lee"})
'callback=http%3A%2F%2Fbaidu.com&name=Lee'
SocketServer
之前的server/client的示例,同时只能处理一个请求,使用SocketServer这一模块,可以有多种方式同时处理多个请求:
先来看一个简单的server端:
# 使用socketserver来创建服务端
from socketserver import TCPServer, StreamRequestHandler
def sServer():
class Handler(StreamRequestHandler):
def handle(self):
addr = self.request.getpeername()
print('Got connection from', addr)
self.wfile.write(b'Thank you for connecting')
server = TCPServer(('127.0.0.1', 8888), Handler)
server.serve_forever()
使用forking和创建线程来同时处理多个请求的代码实现很简单:
# 通过每来一个请求就fork进程的方式来同时处理多个请求
# 在client请求时,可以使用ps -ef| grep python| grep server来查看新起的进程
def forkingServer():
class Server(ForkingMixIn, TCPServer): pass
server = Server(('127.0.0.1', 8888), Handler)
server.serve_forever()
# 通过每来一个请求创建一个线程的方式来同时处理多个请求
def threadServer():
class Server(ThreadingMixIn, TCPServer): pass
server = Server(('127.0.0.1', 8888), Handler)
server.serve_forever()
使用多路复用的方式来实现同时处理多个请求:
import select
# 这个例子中使用select来同时保持多个连接,并且可以随时响应任意连接的数据写入
def selectServer():
s = socket.socket()
s.bind(("127.0.0.1", 8888))
s.listen(5) # 这个参数是backlog
inputs = [s]
while True:
rs, ws, es = select.select(inputs, [], [])
for r in rs:
if r is s:
c, addr = s.accept()
print('Got connection from ', addr)
inputs.append(c)
else:
try:
data = r.recv(1024)
disconnected = not data
except socket.error:
disconnected = True
if disconnected:
print(r.getpeername(), "disconnected")
inputs.remove(r)
else:
print("from ", r.getpeername(), ":", data)
需要对应的给client端进行一些改造,使得能够响应输入并发送这些数据:
def iclient():
s = socket.socket()
s.connect(("127.0.0.1", 8888))
while True:
i = input("enter:")
s.sendall( bytes(i, "utf8"))
使用poll和epoll(mac中没有epoll只有kqueue)的例子就不在这里处理了。
使用twisted基于事件的网络库pip3 install twisted
:
from twisted.internet import reactor
from twisted.internet.protocol import Protocol, Factory
def twistedServer():
class SimpleLogger(Protocol):
def connectionMade(self):
print('Got connection from', self.transport.client)
def connectionLost(self, reason):
print(self.transport.client, 'disconnected')
def dataReceived(self, data):
print("from ", self.transport.client, ":", data)
factory = Factory()
factory.protocol = SimpleLogger
reactor.listenTCP(8888, factory)
reactor.run()
Web
在网页抓取工作中,由于HTML编写得并不都十分规范,所以就需要使用像Tidy之类的工具将其修复。
例如如下这个不规范的html文件,使用tidy -o fixed.html bad.html
命令转换完成之后的效果如下面的代码:
<h1>Pet Shop <h2>Complaints</h3>
<p>There is <b>no <i>way</b> at all</i> we can accept returned parrots.
<h1><i>Dead Pets</h1>
<p>Our pets may tend to rest at times, but rarely die within the
warranty period.
<i><h2>News</h2></i>
<p>We have just received <b>a really niparrot.
<p>It's really nice.</b>
<h3><hr>The Norwegian Blue</h3>
<h4>Plumage and <hr>pining behavior</h4>
<a href="#norwegian-blue">More information<a>
<p>Features:
<body>
<li>Beautiful plumage
<!DOCTYPE html>
<html>
<head>
<meta name="generator" content=
"HTML Tidy for HTML5 for Apple macOS version 5.6.0">
<title></title>
</head>
<body>
<h1>Pet Shop</h1>
<h2>Complaints</h2>
<p>There is <b>no <i>way</i></b> <i>at all</i> we can accept
returned parrots.</p>
<h1><i>Dead Pets</i></h1>
<p><i>Our pets may tend to rest at times, but rarely die within the
warranty period. </i></p>
<h2><i>News</i></h2>
<p>We have just received <b>a really niparrot.</b></p>
<p><b>It's really nice.</b></p>
<hr>
<h3>The Norwegian Blue</h3>
<h4>Plumage and</h4>
<hr>
<h4>pining behavior</h4>
<a href="#norwegian-blue">More information</a>
<p>Features:</p>
<li>Beautiful plumage</li>
</body>
</html>
也可以使用subprocess模块来通过Python来调用tidy命令,也可以使用pytidylib这个封装器来使用。
有了标准的html之后,可以使用HTMLParser来进行内容的解析了,它是基于事件的方式来处理,需要继承HTMLParser类,并实现对应的回调函数:
from html.parser import HTMLParser
class GrabH1(HTMLParser):
h1 = False
def handle_starttag(self, tag, attrs):
if tag == "h1":
self.h1 = True
def handle_data(self, data):
if self.h1 == True:
print("DATA:", data)
def handle_endtag(self, tag):
if self.h1 and tag == "h1":
self.h1 = False
text = open("./fixed.html").read()
parser = GrabH1()
parser.feed(text)
parser.close()
# DATA: Pet Shop
# DATA: Dead Pets
Beautiful Soup
Beautiful Soup是一个可以从HTML或XML文件中提取数据的Python库。
安装:pip install beautifulsoup4
,文档参考:
# beautiful soup试用
from urllib.request import urlopen
from bs4 import BeautifulSoup
text = urlopen('https://www.crummy.com/software/BeautifulSoup/').read()
soup = BeautifulSoup(text, 'html.parser')
links = {}
for a in soup.find_all('a'):
try:
links[a.string] = a["href"]
except KeyError: pass
print(links)
# {'Download': '#Download', 'Documentation': 'bs4/doc/', 'Hall of Fame': '#HallOfFame', 'For enterprise': 'enterprise.html', 'Source': 'https://code.launchpad.net/beautifulsoup',...}
Web Server
书中说的使用cgi来架设web服务器在目前看来已经淘汰了,我们使用uwsgi和fastcgi来搭建服务器吧。
$ pip3 install uwsgi
$ brew install uwsgi
uwsgi提供了直接作为web server的能力,假设我们有文件uwsgi.py如下,可以直接运行uwsgi --http :9090 --wsgi-file uwsgi.py
,就可以在浏览器上访问http://localhost:9090/ 获得Hello World的输出内容:
# uwsgi在启动时就会运行指定的python文件,并在每一次请求的时候查找application函数,这可以通过配置来修改
def application(env, start_response):
start_response('200 OK', [('Content-Type','text/html')])
return [b"Hello World"]
也可以与nginx配合,使用uwsgi协议来进行通信:
$ uwsgi --socket 127.0.0.1:3031 --wsgi-file uwsgi.py
nginx的配置如下:
# 配置文件地址为:/usr/local/etc/nginx/nginx.conf
# 启动命令 brew services start nginx
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:3031;
}
这样在浏览器中访问http://localhost:8080 即可访问与之前一致的页面(nginx监听的是8080端口)。
Flask
使用Python的Web框架Flask:
创建文件myflaskapp.py:
from flask import Flask, request
app = Flask(__name__)
@app.route('/')
def index():
return "<span style='color:red'>I am app 1</span>"
@app.route('/page', methods=['POST', 'GET'])
def page():
return "GET:{}\nMETHOD:{}\nPOST:{}\n".format(request.args, request.method, request.form)
@app.route('/hello/<name>')
def hello(name=None):
return render_template('hello.html', name=name)
flask支持Jinja2模板引擎,并会在templates目录下找render_template指定的文件,以下是templates/hello.html
<!doctype html>
<title>Hello from Flask</title>
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello World!</h1>
{% endif %}
运行uwsgi程序uwsgi --socket 127.0.0.1:3001 --wsgi-file myflaskapp.py --callable=app
,这个app就是上面代码中app = Flask(name)创建的入口函数,如果这个启动比较麻烦,可以使用配置文件uwsgi.ini,启动时uwsgi uwsgi.ini
:
[uwsgi]
socket=:3001
wsgi-file=./myflaskapp.py
callable=app
$ curl 'http://localhost:8080/page?abcdefg=2xxx&dkfj=sfa' -d 'a=1&b=1'
GET:ImmutableMultiDict([('abcdefg', '2xxx'), ('dkfj', 'sfa')])
METHOD:POST
POST:ImmutableMultiDict([('a', '1'), ('b', '1')])
还有一些其它的web框架:Django、TurboGears、web2py、Grok、Zope2、Pyramid。
测试
介绍两个测试库doctest和unittest,这两测试库Python都自带,以及代码检查工具pylint和pychecker。
doctest
从函数的文档中获取交互式命令相关的内容做为测试case:
def square(x):
'''
计算平方并返回结果
>>> square(2)
4
>>> square(3)
9
'''
return x * x
if __name__ == '__main__':
import doctest
# doctest将测试本文件内的函数,用例是从函数的注释中获取
doctest.testmod()
# $ python3 doct.py -v
# Trying:
# square(2)
# Expecting:
# 4
# ok
# Trying:
# square(3)
# Expecting:
# 9
# ok
# 1 items had no tests:
# __main__
# 1 items passed all tests:
# 2 tests in __main__.square
# 2 tests in 2 items.
# 2 passed and 0 failed.
# Test passed.
unittest
使用unittest来测试刚才的square函数,我们可以把测试用例使用单独的文件来写。unittest.main()方法运行所有的测试,包括所有TestCase的子类以及类中的test打头的方法。另外它还提供了setUp和tearDown等在测试不同阶段可以回调的函数,方便做数据的准备了清理。
from doct import square
import unittest
class ProductTestCase(unittest.TestCase):
def test_integers(self):
for x in range(-10, 10):
self.assertEqual(square(x), x*x, 'Integer psquare failed')
def test_floats(self):
for x in range(-10, 10):
self.assertEqual(square(x/10), x/10*(x/10), 'Float psquare failed')
if __name__ == "__main__":
unittest.main()
# $ python3 unitt.py -v
# test_floats (__main__.ProductTestCase) ... ok
# test_integers (__main__.ProductTestCase) ... ok
#
# ---------------------------------------------------------------------
# Ran 2 tests in 0.000s
pylint和pychecker
pylint将给出一些代码的建议,如是否换行,缺少空格等等:
$ brew install pylint
$ pylint doct.py
************* Module doct
doct.py:1:14: C0303: Trailing whitespace (trailing-whitespace)
doct.py:4:17: C0303: Trailing whitespace (trailing-whitespace)
doct.py:6:17: C0303: Trailing whitespace (trailing-whitespace)
doct.py:18:15: C0303: Trailing whitespace (trailing-whitespace)
doct.py:23:15: C0303: Trailing whitespace (trailing-whitespace)
doct.py:39:15: C0303: Trailing whitespace (trailing-whitespace)
doct.py:47:0: C0304: Final newline missing (missing-final-newline)
doct.py:1:0: C0114: Missing module docstring (missing-module-docstring)
doct.py:1:0: C0103: Argument name "x" doesn't conform to snake_case naming style (invalid-name)
--------------------------------------------------------------------
Your code has been rated at -8.00/10 (previous run: -8.00/10, +0.00)
性能分析
标准库中的profile用于性能分析,他可以给出每一个函数的调用次数以及执行的时间,还可以将结果保存于文件中,使用pstats这个模块来分析。profile模块也有对应更快件的C语言版本cProfile
扩展Python
使用Jython扩展
Jython是使用Java编写的Python解释器,所以扩展Jython只需要编写对应的Java类即可:
public class JythonTest{
public void greeting(){
System.out.println("Hello");
}
}
然后编译成class文件,并在运行Jython时指定对应的CLASSPATH,即可直接import对应的Java类了:
$ javac JythonTest.java
$ CLASSPATH=JythonTest.class jython
Jython 2.7.2 (v2.7.2:925a3cc3b49d, Mar 21 2020, 10:03:58)
[OpenJDK 64-Bit Server VM (Oracle Corporation)] on java13.0.2
>>> import JythonTest
>>> t = JythonTest()
>>> t.greeting()
Hello
扩展标准版本Python:CPython
编写扩展思路与PHP写扩展的思路是一样的,只是Python中有一个swig工具可以简化这一部分的工作。
例如我们要写一个简单的回文检测函数,涉及到palindrome.c和palindrome.i两个文件,代码如下:
#include <string.h>
int is_palindrome(char *text) {
int i, n=strlen(text);
for (i = 0; i <= n/2; ++i) {
if (text[i] != text[n-i-1])
return 0;
}
return 1;
}
%module palindrome
%{
#include <string.h>
%}
extern int is_palindrome(char *text);
使用swig -python -module myp palindrome.i
命令会自动生成palindrome_wrap.c文件,并指定扩展的模块名称为myp,生成myp.py这个模块的封装代码;
然后将这两个文件.c文件编译成共享链接库:
gcc -I/usr/local/Cellar/python@3.8/3.8.3/Frameworks/Python.framework/Versions/3.8/include/python3.8/ -shared palindrome_wrap.c palindrome.c -lpython3.8 -L/usr/local/Cellar/python@3.8/3.8.3/Frameworks/Python.framework/Versions/3.8/lib/ -o _myp.so
其中-I指定头文件的搜索目录,-L指定了链接库的搜索目录,而-l指定了需要链接的python的动态链接库,这是我在mac上的路径,其它环境有可能不一样;注意动态链接库的名称需要是模块名前添加下划线。
这样就可以使用myp这个模块了:
$ PYTHONPATH=$PYTHOPATH:./ python3
>>> import myp
m>>> myp.is_palindrome("abba")
1
>>> import _myp
>>> _myp.is_palindrome("abbaa")
0
可以看出如果使用import myp则加载的是myp.py,而这个封装文件最终也是使用的_myp.so这个动态库,与直接使用import _myp是一样的结果。
以下是不用swig手写的回文字符串检测palindrome1.c:
#include <Python.h>
#include <string.h>
/**
* 定义的目标函数,函数的返回值必须是*PyObject,self指向模块本身,args参数包含所有传入的参数
*/
static PyObject *is_palindrom(PyObject *self, PyObject *args){
int i, n;
const char *text;
// 传入的参数可以使用PyArg_ParseTuple按一定的格式解析到对应的变量中,其中s表示是字符串
if (!PyArg_ParseTuple(args, "s", &text)){
return NULL;
}
n = strlen(text);
// 这次将返回值改成返回True和False
for (i = 0; i<= n/2; i++){
if (text[i] != text[n-i-1]){
Py_INCREF(Py_False);
return Py_False;
}
}
// 需要增加Py_True的引用计数的数量
Py_INCREF(Py_True);
// 如果是要返回整数1的话可以使用Py_BuildValue("i", 1);
return Py_True;
}
// 导出的方法列表定义,在PyModuleDef中使用,而PyModuleDef又在PyInit_modulename上使用
static PyMethodDef PalindromeMethods[] = {
// 名称(这影响在python中调用时的名字)、具体的函数、参数类型、文档
{"is_palindrom1", is_palindrom, METH_VARARGS, "Dected palindromes"},
// 列表结束标志
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef palindrome =
{
PyModuleDef_HEAD_INIT,
"palindrome1", // 模块名,好像改完之后没有啥效果,只是在help时会在NAME里体现
"", // 文档
-1, // 存储在全局变量中的信号状态
PalindromeMethods // 方法列表
};
// 初始化模块的函数,PyInit_modulename,名字必须是这样的命名规则
PyMODINIT_FUNC PyInit_palindrome1(void){
return PyModule_Create(&palindrome);
}
// 编译: gcc -I/usr/local/Cellar/python@3.8/3.8.3/Frameworks/Python.framework/Versions/3.8/include/python3.8/ -shared -lpython3.8 -L/usr/local/Cellar/python@3.8/3.8.3/Frameworks/Python.framework/Versions/3.8/lib/ palindrome1.c -o palindrome1.so -v
// >>> import palindrome1
// >>> palindrome1.is_palindrom1("FD")
// False
// >>> palindrome1.is_palindrom1("ABBA")
// True
另外一种不用手工编译的方式就是使用setuptools提供的方法:
from setuptools import setup, Extension
setup(
name = 'palindrome1',
version = '1.0',
ext_modules = [
Extension('palindrome1', ['palindrome1.c'])
]
)
$ python3 setup.py build_ext
running build_ext
building 'palindrome1' extension
creating build
creating build/temp.macosx-10.15-x86_64-3.8
clang -Wno-unused-result -Wsign-compare -Wunreachable-code -fno-common -dynamic -DNDEBUG -g -fwrapv -O3 -Wall -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk -I/Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/usr/include -I/Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/Tk.framework/Versions/8.5/Headers -I/usr/local/include -I/usr/local/opt/openssl@1.1/include -I/usr/local/opt/sqlite/include -I/usr/local/Cellar/python@3.8/3.8.3/Frameworks/Python.framework/Versions/3.8/include/python3.8 -c palindrome1.c -o build/temp.macosx-10.15-x86_64-3.8/palindrome1.o
creating build/lib.macosx-10.15-x86_64-3.8
clang -bundle -undefined dynamic_lookup -isysroot /Library/Developer/CommandLineTools/SDKs/MacOSX10.15.sdk build/temp.macosx-10.15-x86_64-3.8/palindrome1.o -L/usr/local/lib -L/usr/local/opt/openssl@1.1/lib -L/usr/local/opt/sqlite/lib -o build/lib.macosx-10.15-x86_64-3.8/palindrome1.cpython-38-darwin.so
$ cd build/lib.macosx-10.15-x86_64-3.8
$ PYTHONPATH=$PYTHOPATH:./ python3
>>> import palindrome1
>>> palindrome1.is_palindrom1("daf")
False
>>> palindrome1.__file__
'/Users/liqingshou/Work/xiaochai/batman/Python/ext/build/lib.macosx-10.15-x86_64-3.8/palindrome1.cpython-38-darwin.so'
>>>
程序打包
程序打包的概念与PHP的phar文件一样,在Python中有两种格式egg和wheel(后缀为whl),wheel将会逐渐取代egg。另外pi2exe扩展可以打包成windows平台的exe文件,而且不需要用户安装额外的解释器。
通过在PyPI上注册还可以让别人使用pip来安装你开发的包,这块可以参考官方教程。
# setuptools可以帮忙处理一些事情
# 运行python3 setup.py install将在dist目录下生成egg文件
# setup.py文件内容
from setuptools import setup
setup(
name = "hello",
version = "1.0",
description = "simple example",
author = "Lee",
py_modules = ["hello"]
)