赞
踩
编码方式历史进程:
编码方式解释:https://www.zhihu.com/question/52346583/answer/977492415
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bzrx2lmz-1598977365406)(D:\Data\Desktop\image-20200717184859482.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8uym5A0w-1598977365408)(D:\Data\Desktop\image-20200717184910019.png)]
有序列表,元组
不可变有序列表
因为是不可变,所以代码更安全,如果可能,就尽量用tuple代替list
注意点:t = (1)这个输出是为1,为不是(1)
for…in循环
while循环
break语句,直接退出循环
n = 1
while n <= 100:
if n > 10: # 当n = 11时,条件满足,执行break语句
break # break语句会结束当前循环
print(n)
n = n + 1
print('END')
continue语句可以提前结束本轮循环
n = 0
while n < 10:
n = n + 1
if n % 2 == 0: # 如果n是偶数,执行continue语句
continue # continue语句会直接继续下一轮循环,后续的print()语句不会执行
print(n)
set和dict类似,也是一组key的集合,但不存储value,key也不能重复,也为不可变对象
要创建一个set,需要提供一个list作为输入集合:
s = set([5,2,3])
set中的元素不重复,重复元素会自动过滤
s = set([1,1,2,3,3,4])
print(s)
{1,2,3,4}
add(key)添加元素
remove(key)删除元素
set可以看成数学意义上的无序和无重复的集合,所以两个set可以做数学意义上的交集、并集
s1 = set([1, 2, 3])
s2 = set([2, 3, 4])
print(s1 & s2) # {2,3}
print(s1 | s2) #{1,2,3,4}
函数就是最基本的一种代码抽象的方式
int()
float()
str()
bool()
…
函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个"别名"
a = abs #变量a指向abs函数
print(a(-1)) # 1,所以也可以通过a调用abs函数
必须参数(位置参数)、默认参数、可变参数(*args)、关键字参数(**kw)、命名关键字参数
参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数
默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误
要注意定义可变参数和关键字参数的语法
以及调用函数时如何传入可变参数和关键字参数的语法
使用*args和**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法
命名的关键字参数是为了限制调用者传入的参数名,同时可以提供默认值
定义命名的关键字参数再没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数
使用递归函数的有点是逻辑简单清晰,缺点是过深的调用是导致栈溢出
针对尾递归优化的语言可以通过尾递归防止栈溢出。
尾递归事实上适合循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。
Python 标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。
练习:汉诺塔的移动
请编写move(n, a, b, c)
函数,它接收参数n
,表示3个柱子A、B、C中第1个柱子A的盘子数量,然后打印出把所有盘子从A借助B移动到C的方法
def move(n, a, b, c): if n == 1: print(a, '-->', c) else: move(n-1, a, c, b) #把前n-1块从柱子A移至柱子B print(a, '-->', c) #把最大块从柱子A移至柱子C move(n-1, b, a, c) #把前n-1块从柱子B移至柱子C move(3, 'A', 'B', 'C') #输入: A --> C A --> B C --> B A --> C B --> A B --> C A --> C
一行代码能解决的事情,绝不写5行代码,代码越少,开发效率越高
slice切片操作
去空格函数
strip([char]):收尾去除char,不填则去除空格
str = '000123456000'
str.strip('0') #收尾去除0
# '123456'
str = "123abcrunoob321"
print (str.strip( '12' )) # 字符序列为 12
# 3abcrunoob3,从结果来看 去除的12字符不分顺序
ltrip()
rtrip()
利用切片操作,实现一个trim()函数,去除字符串首尾的空格,注意不要调用str的strip()
方法:
def trim(s): while(s[:1] == ' '): s = s[1:] while(s[-1:] == ' '): s = s[:-1] return s s = ' hello' print(s[:1]) # 测试: if trim('hello ') != 'hello': print('测试失败!') elif trim(' hello') != 'hello': print('测试失败!') elif trim(' hello ') != 'hello': print('测试失败!') elif trim(' hello world ') != 'hello world': print('测试失败!') elif trim('') != '': print('测试失败!') elif trim(' ') != '': print('测试失败!') else: print('测试成功!')
使用for循环去遍历可迭代对象
判断对象是否是可迭代对象
from collections.abs import Iterable
print(isinstance('abc',Iterable)) # True
print(isinstance(123,Iterable)) # False
print(isinstance([1,2,3],Iterable)) # True
想通过for循环获取list的下标,使用内置函数enumerate
for index,value in enumerate(['a','b','c'])
print(index,value)
# 0 a
# 1 b
# 2 c
for循环同时引用两个变量
dict = {'a':1, 'b':2, 'c':3}
for x,y in dict.items():
print(x,y)
# a 1
# b 2
# c 3
请使用迭代查找一个list中最小和最大值,并返回一个tuple:
解题思路:
# 第一种:未使用for循环,使用while # def findMinAndMax(L): # if L == []: # return (None, None) # if len(L) == 1: # return (L[0],L[0]) # n = 0 # m = 0 # while m < len(L)-1: # if L[n] > L[n+1]: # L[n],L[n+1] = L[n+1],L[n] # n += 1 # m += 1 # return (L[0], L[-1]) #第二种:只使用for循环就可以解决 def findMinAndMax(L): if len(L) == 0: return (None, None) min = L[0] max = L[0] for i in L: if i > max: max = i if i < min: min = i return (min, max) # 测试 if findMinAndMax([]) != (None, None): print('测试失败!') elif findMinAndMax([7]) != (7, 7): print('测试失败!') elif findMinAndMax([7, 1]) != (1, 7): print('测试失败!') elif findMinAndMax([7, 1, 3, 9, 5]) != (1, 9): print('测试失败!') else: print('测试成功!')
单层循环
[x * x for x in range(1, 11)]
两层循环
[m + n for m in 'ABC' for n in 'XYZ']
if…else
[x for x in range(1, 11) if x % 2 == 0]
错误写法:
[x if x % 2 == 0 for x in range(1, 11)]
for循环前面是一个表达式,而后面是一个筛选条件
所以在列表生成式for循环前面使用if语句的话要加上else
[x if x % 2 == 0 else -x for x in range(1, 11)]
for循环后面则为筛选条件,使用if不能加上else
[x for x in range(1, 11) if x % 2 == 0]
练习:
请修改列表生成式,通过添加if
语句保证列表生成式能正确地执行:
L1 = ['Hello', 'World', 18, 'Apple', None]
# 答案----------------------------
L2 = [i.lower() for i in L1 if isinstance(i,str)]
# --------------------------------
# 测试:
print(L2)
if L2 == ['hello', 'world', 'apple']:
print('测试通过!')
else:
print('测试失败!')
介绍:
优势:
创建方法:
使用场景:
实现多任务,协程
generator其中两种创建方式
把一个列表生成式[]改为(),就创建了一个generator
L = [x * x for x in range(10)] # L为list
g = (x * x for x in range(10)) # g为generator
在函数的定义中包含yield关键字即可,普通函数就变为generator
def fib(max): # 斐波那契数列
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
获取generator中的值
获取generator中的返回值
通过捕获StopIteration错误,返回值包含在StopIteration中的value里
f = fib(6) # 斐波那契数列对象
while True:
try:
x = next(f)
print(x)
except StopIteration as e:
print(e.value)
break
练习:杨辉三角
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zJiyK7g4-1598977365409)(D:\Data\Desktop\image-20200719175610008.png)]
把每一行看做一个list,试写一个generator,不断输出下一行的list:
# 答案----------------------------- def triangles(): s = [1] while True: yield s l = [] sum = 0 for i in s: l.append(sum+i) sum = i l.append(s[len(s)-1]) s = l # --------------------------------- # 期待输出: # [1] # [1, 1] # [1, 2, 1] # [1, 3, 3, 1] # [1, 4, 6, 4, 1] # [1, 5, 10, 10, 5, 1] # [1, 6, 15, 20, 15, 6, 1] # [1, 7, 21, 35, 35, 21, 7, 1] # [1, 8, 28, 56, 70, 56, 28, 8, 1] # [1, 9, 36, 84, 126, 126, 84, 36, 9, 1] n = 0 results = [] for t in triangles(): results.append(t) n = n + 1 if n == 10: break for t in results: print(t) if results == [ [1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1], [1, 5, 10, 10, 5, 1], [1, 6, 15, 20, 15, 6, 1], [1, 7, 21, 35, 35, 21, 7, 1], [1, 8, 28, 56, 70, 56, 28, 8, 1], [1, 9, 36, 84, 126, 126, 84, 36, 9, 1] ]: print('测试通过!') else: print('测试失败!')
凡是可作用于for循环的对象都是Iterable类型
凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列
集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数把Iterable对象变成Iterator对象
Python的for循环本质上就是通过不断调用next()函数实现的
for x in [1,2,3,4]
pass
上面等价于下面
# 首先获得Iterator对象
it = iter([1,2,3,4])
# 循环
while True:
try:
# 获得下一个值
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break
https://www.liaoxuefeng.com/wiki/1016959663602400/1017328525009056
变量可以指向函数
以abs()函数为例
f = abs
f(-10)
# 10
函数名也是变量
函数名是什么?函数名其实就是指向函数的变量
以abs()为例,完全可以把函数名abs堪称变量,它指向一个可以计算绝对值的函数
如果把abs指向其他对象
abs = 10
abs(-10)
# 会报错,因为abs变量指向了整数10,此时abs不是指向计算绝对值的函数
# 要恢复abs函数,需要重启Python交互环境
由于abs
函数实际上是定义在import builtins
模块中的,所以要让修改abs
变量的指向在其它模块也生效,要用import builtins; builtins.abs = 10
。
传入函数
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数
简单的高阶函数
def add(a,b,f)
return f(a) + f(b)
a = -5
b = 6
f = abs
result = add(a,b,f)
print(result)
# 11
map()参数:
reduce()参数:
练习:
利用map和reduce编写一个str2float函数,把字符串‘123.456’转换成浮点数123.456
最快的方法是用float()函数把字符串转为浮点数,但要探究,就不能用这个方法
from functools import reduce
DIGITS = {'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9,'0':0}
# 字符转化为数字
def char2num(s):
retrn DIGITS[s]
# 字符串转化为整数
def str2int(s):
return reduce(lambda x,y:x*10+y,map(char2num,s))
# 字符串转化为浮点数
def str2float(s):
L = s.split('.')
return reduce(lambda x,y:x*10+y,map(char2num,L[0])) + reduce(lambda x,y:x*10+y,map(char2num,L[1]))/(10**len(L[1]))
内置函数
filter()接受一个函数和一个序列,返回值是一个Iterator,也就是一个惰性序列
和map()不同点在于,filter()虽然也是把传入的函数依次作用在每个元素,但是根据函数的返回值是否是True还是False来决定保留还是丢弃该元素(即filter()会自动对参数一函数的返回值进行bool判断,是True则留下,False丢弃)
例子:
判断列表的元素是否是基数,基数则留下,偶数则删除
def is_odd(n):
return n%2 == 1
list(filter(is_odd,[1,2,3,4,5,6,7]))
# [1,3,5,7]
把一个序列中的空字符串删掉
def not_empty(s):
return s and s.strip()
list(filter(not_empty,['AB',' ',None,'C']))
# ['AB','C']
练习:用filter求素数(埃式筛法)
# 构造一个从3开始的奇数序列 def _odd_iter(): n = 1 while True: n += 2 yield n # 筛选函数 def _not_divisible(n): return lambda x: x % n > 0 # 定义一个生成器,不断返回下一个素数 def primes(): yield 2 it = _odd_oter() #初始序列 while True: n = next(it) yield n it = filter(_not_divisible(n),it)
练习:用filter筛选出回数(一个从左到右读和从右到左读都是一样的自然数)
算法一:
def is_palindrome(n):
s = str(n)
if len(s) == 1:
return 1
if len(s)%2 == 0:
return s[:len(s)//2] == s[len(s)//2:]
return s[:len(s)//2] == s[(len(s)//2)+1:]
# 测试:
output = filter(is_palindrome, range(1, 1000))
print('1~1000:', list(output))
if list(filter(is_palindrome, range(1, 200))) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191]:
print('测试成功!')
else:
print('测试失败!')
算法二:
def is_palindrome(n):
s = str(n)
l = len(s)+1
for i in range(l//2):
if s[i] != s[-i-1]
return False
return True
# 测试:
output = filter(is_palindrome, range(1, 1000))
print('1~1000:', list(output))
if list(filter(is_palindrome, range(1, 200))) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191]:
print('测试成功!')
else:
print('测试失败!')
sorted(iterable, key=None, reverse=False)参数:
练习
假设我们用一组tuple表示学生名字和成绩:使用sorted()对列表分别按名字和姓名排序
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
# 对名字进行排序,名字是字符串即通过Ascii的大小比较
def by_name(t):
return t[0]
L2 = sorted(L, key=by_name)
print(L2)
# 对成绩以倒序方式排序
def by_score(t):
return -t[1] # 这里添加"-"号的效果和在sorted函数中添加reverse=True效果一样
L3 = sorted(L, key=by_score)
print(L3)
https://www.liaoxuefeng.com/wiki/1016959663602400/1017434209254976
1、高阶函数除了可以接收函数作为参数外,还可以把函数作为结果值返回
2、在一个函数(外部函数)中又定义了一个函数(内部函数),内部函数可以引用外部函数的参数和局部变量,当外部函数返回内部函数时,相关参数和变量都保存在返回的内部函数中,这种程序结构称为”闭包“
3、注意:返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量(可以使用可变类型的数据例如list)
4、如果一定要引用循环遍历,方法就是在创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何修改,已绑定到函数参数的值不变
练习:利用闭包返回一个计数器函数,每次调用它返回递增整数
下面代码闭包的返回值不能使用整数类型,因为整数是不可变数据类型(修改值的话,内存地址也会发生改变)(笔记第3点),而是使用list(可变),这样修改list中的value,对应的id也不会发生改变
def createCounter():
n = [0]
def counter():
n[0] = n[0]+1
return n[0]
return counter
# 测试:
counterA = createCounter()
print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5
counterB = createCounter()
if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:
print('测试通过!')
else:
print('测试失败!')
匿名函数:没有函数名,只是一个函数对象
由于匿名函数是只是一个函数对象,没有函数名,所以不用担心函数名冲突问题,此外还可以将匿名函数赋值给一个变量,再利用变量来调用函数
匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果
lambda x: x*x # 参数可以有多个
相当于
def f(x):
return x * x
在代码运行期间动态增加功能的方式,称之为装饰器
本质上,decorator就是一个返回函数的高阶函数
通过functools.wrap()把原始函数的__name__等属性复制到wrapper()函数中,否则有些依赖函数签名的代码执行就会报错
不带参数的decorator
import functools
def log(func):
@functools.wraps(func) # 把原始函数的__name__等属性复制到wrapper()函数中,否则有些依赖函数签名的代码执行就会报错
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(*args, **kw)
return wrapper
return decorator
练习1:请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间:
import time, functools def metric(fn): @functools.wraps(fn) def wrapper(*args, **kw): start_time = time.time() tmp = fn(*args, **kw) end_time = time.time() print('%s executed in %s ms' % (fn.__name__, end_time - start_time)) return tmp return wrapper # 测试 @metric def fast(x, y): time.sleep(0.0012) return x + y; @metric def slow(x, y, z): time.sleep(0.1234) return x * y * z; f = fast(11, 22) s = slow(11, 22, 33) if f != 33: print('测试失败!') elif s != 7986: print('测试失败!')
练习2:请编写一个decorator,能在函数调用的前后打印出'begin call'
和'end call'
的日志,写出一个@log的decorator,使它即支持
@log
def f():
pass
又支持
@log('execute')
def f():
pass
import functools,time def log(text): # 注意这里的text,当不传入参数时,text相当于传入的函数名 if isinstance(text,str): def decorator(fn): @functools.wraps(fn) def wrapper(*args, **kw): print('begin call %s' % fn.__name__) tmp = fn(*args, **kw) print('end call %s' % fn.__name__) return tmp return wrapper return decorator @functools.wraps(text) #注意这里的text,当不传入参数时,text相当于传入的函数名 def wrapper(*args, **kw): print('begin call %s' % text.__name__) tmp = text(*args, **kw) print('end call %s' % text.__name__) return tmp return wrapper # 测试 @log def f(): print('f') f() print('------------------------') @log('im') def f1(): print('f1') f1()
当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单
def int2(x ,base=2):
return int(x, base)
print(int2('1000000'))
import functools
int2 = functools.partial(int,base=2) # partial()函数固定base参数
print(int2('10000000'))
import xxx
from xxx import xxx
类似_xxx和__xxx这样的函数或变量就是非公开的(private),不应该被直接引用(不应该不是不能)
private函数或变量的作用
def _private_1(name):
return 'Hello, %s' % name
def _private_2(name):
return 'Hi, %s' % name
def greeting(name):
if len(name) > 3:
return _private_1(name)
else:
return _private_2(name)
在模块里公开greeting()函数,而把内部逻辑用private函数隐藏起来了,这样,调用greeting()函数就不用关心内部的private函数细节,这也是一种非常有用的代码封装和抽象的方法,即:外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public
面向对象最重要的概念是类和实例,类是抽象的模板,而实例是根据类创建出来的一个个具体的"对象",各个实例拥有的数据都相互独立,互不影响;
方法就是于实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据
通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节
数据封装:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self): # 类的方法,将数据封装起来
print('%s: %s' % (self.name, self.score))
通过类的方法将数据封装起来,外部调用Student对象的实例的print_score方法很容易,但是不知道该方法内部的逻辑和数据。起到一个封装作用。
关于下划线:https://zhuanlan.zhihu.com/p/105783765?utm_source=com.miui.notes
在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的如咋逻辑
为了不让内部属性被外部访问,可以在属性的名称前添加__两条下划线,实例的变量如果以__开头,就变成了一个私有属性,只有内部可以访问,而外部不可以访问(实际上还是可以访问的,下面会说)
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
s = Student('tom',80)
print(s.__name) # 会报错
print(s._Student__name) #这种方法可以访问到私有属性
Python中,有些变量名是__xxx__的,这种是特殊变量,不是private,外部是可以直接访问的
有些是_xxx这种一个下划线的变量,外部是可以直接访问的,但是,按照约定俗成的规定,当看到这种变量时,意思是:“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问我”
私有属性“__name”在类中定义的时候,Python解释器会对外把__name变量改成_Classname__name,所以外部还是可以通过“ 实例名._Classname__name”的方式去访问私有属性
如果外部代码要获取私有属性的值和修改私有属性的值的话,我们可以在类中增加get方法和set方法(也可以用装饰器@property把方法变成属性用于调用),外部可以通过函数去调用或修改属性
class Student(object): def __init__(self, name, gender): self.name = name self.__gender = gender def get_gender(self): return self.__gender def set_gender(self, gender): if not isinstance(gender,str): raise 'TypeError' self.__gender = gender # 测试: bart = Student('Bart', 'male') if bart.get_gender() != 'male': print('测试失败!') else: bart.set_gender('female') if bart.get_gender() != 'female': print('测试失败!') else: print('测试成功!')
type()
使用type()判断对象类型,基本类型都可以用type()判断
判断函数使用types(),这个函数不是builtin的,需要import types
>>> import types
>>> def fn():
... pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True
isinstance()
对于class的继承关系来说,使用type()就很不方便。要判断class的类型,可以使用isinstance()函数
class Animal(object):
def run(self):
print('Animal is runing...')
class Dog(Animal):
def run(self):
print('little dog is running...')
a = Animal()
d = Dog()
print(isinstance(a,Animal)) # True
print(isinstance(d,Animal)) # True
instance()还可以判断一个变量是否是某些类型中的一种
isinstance([1, 2, 3], (list, tuple))
dir()
要获取一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list
dir('ABC') #获取str对象的所有属性和方法
# ['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
当使用len()函数去获取对象的长度时,其实内部是调用了__len__()这个特殊方法,所以我们也可以自己在class中重写这个方法(类似的其他内置函数道理也一样)
获取class内的数据,还有getattr()、setattr()、hasattr()函数
class MyObject(object):
def __init__(self):
self.x = 9
def power(self):
return self.x * self.x
obj = MyObject()
print(hasattr(obj,'x')) # True
print(hasattr(obj,'y')) # False
setattr(obj,'y',19)
print(hasattr(obj, 'y')) # True
print(getattr(obj, 'y')) # 19
getattr()试图获取不存在的属性时会报错,可以指定默认的返回值
getattr(obj,'z',404) #若obj对象中没有z属性,则返回404
正常情况下,我们可以给创建的class绑定属性或方法
绑定属性有两种,第一种是创建实例化对象后绑定,另一种是直接类名.属性名绑定类属性
绑定方法同样有两种情况
1、实例化对象后,给对象绑定方法,不过只有此实例化对象可以使用该方法,另一个实例却不可以用
>>> class Student(object): ... pass >>> def set_age(self, age): # 定义一个函数作为实例方法 ... self.age = age ... >>> from types import MethodType >>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法 >>> s.set_age(25) # 调用实例方法 >>> s.age # 测试结果 25 >>> s2 = Student() # 创建新的实例 >>> s2.set_age(25) # 尝试调用方法 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'set_age'
2、为了给所有实例都绑定方法,可以给class绑定方法:
>>> def set_score(self, score):
... self.score = score
...
>>> Student.set_score = set_score
使用__slots__限定实例的属性(类属性不限制),规定实例只可以绑定哪些属性
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
使用__slots__
要注意,__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的,不过子类中也可以定义__slots__,这样的话子类实例允许定义的属性就是自身的__slots__加上父类的__slots__
为了让外部使用类中的属性而不将真实属性名暴露出去,可以使用get()方法和set()或者使用@property装饰器
class Student(object):
def get_score(self):
return self._score
def set_score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
https://www.liaoxuefeng.com/wiki/1016959663602400/1017502939956896
继续是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能
定义类时,继承多个不同的类,从而实现多种功能,这种设计思想通常称为MixIn
只允许单一继承的语言(如Java)不能使用MixIn的设计
这里说的定制类,其实就是重写类里面自带的特殊方法,像一些__call__、__getitem__、__str__、__repr__、__iter__、__next__、__getattr__等等
__str__和__repr__
__str__是指打印对象的时候会调用,像print(obj)方法其实也是调用obj对象中的__str__方法
__repr__也是打印对象的时候会调用的一个方法
区别在于:__repr__可以应用在所有场景,__str__只是用一些特定的场景,是因为那些场景在repr和str之间会有优先调用的区别,在命令行模式下,解释器会优先调用repr
__iter__和__next__
一个类对象要想被用于for…in循环,首先这个对象是可迭代对象即类中有__iter__方法(collections.abc中的Iterable,使用isinstance来判断),确认是可迭代对象后,for循环会调用next()方法去获取值即类中也要有__next__方法
Fib类举例
class Fib(object): def __init__(self): self.a, self.b = 0, 1 def __iter__(self): # 实现iter方法来判断是否是iterable return self def __next__(self): # for...in 循环遍历时,其实也是调用next()方法 self.a, self.b = self.b, self.a + self.b # 计算下一个值 if self.a > 10000: # 退出循环的条件 raise StopIteration return self.a # 返回下一个值 f = Fib() for i in f: print(i)
__getitem__这个方法是获取对象的索引、分片操作
上文提到Fib类,虽然Fib实例能作用于for循环,但是不能当作list去进行slice操作
下文的n是指传入进去的索引,下文没有对step进行判断操作,只判断了切片的start和stop参数
class Fib2(object): def __getitem__(self, n): #n是指传入进去的索引 if isinstance(n, int): a, b = 1, 1 for _ in range(n): a, b = b, a+b return a if isinstance(n, slice): start = n.start stop = n.stop if start is None: start = 0 a, b = 1, 1 L = [] for x in range(stop): if x >= start: L.append(a) a, b = b, a+b return L f2 = Fib2() print(f2[0]) # n为0 print(f2[3:5]) # n为3:5 print(f2[:10]) # n为0:10
__getattr__
当类去调用属性的时候,只有没有找到属性时,才调用__getattr__,已有的属性,不会去__getattr__中查找
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
s = Student()
print(s.name)
# 'Michael'
print(s.score)
# 99
实现链式调用
class Chain(object):
def __init__(self, path= ''):
self._path = path
def __getattr__(self, path):
return Chain('%s.%s' % (self._path, path))
def __str__(self):
return self._path
__repr__ = __str__
print(Chain('www').baidu.com)
__call__
调用实例,可以使用callable()方法判断对象是否是Callable对象,是的话则可以被调用
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
return ('my name is %s' % (self.name))
s = Student('Michael')
print(s)
https://www.liaoxuefeng.com/wiki/1016959663602400/1017595944503424#0
from enum import Enum
Enum可以把一组相关常量定义在一个class中,且class不可变,而且成员可以直接比较
type()
1、查看类型
2、创建类
class Hello(object):
def hello(self, name='world'):
print('Hello %s' % name)
print(self.__class__.__name__)
H = Hello()
H.hello()
# 上面和下面一样
def fn(self, name='world'):
print('Hello %s' % name)
Hello = type('Hello', (object,), dict(hello=fn))
h = Hello()
h.hello()
要创建一个class对象,type()
函数依次传入3个参数:
fn
绑定到方法名hello
上。metaclass
https://www.liaoxuefeng.com/wiki/1016959663602400/1017592449371072
https://docs.python.org/3/library/exceptions.html#exception-hierarchy
try…except…finally
raise主动抛出异常
使用logging模块记录错误
import logging def foo(s): return 10 / int(s) def bar(s): return foo(s) * 2 def main(): try: bar('0') except Exception as e: logging.exception(e) main() print('END')
print()
assert
断言assert
assert n != 0, 'n is zero!'
如果 n != 0 为True,则执行下一条语句,否则返回AssertionError:n is zero!
logging模块(主流)
pdb(python debugger)命令行调试
python -m pdb err.py
pdb.set_trace()
import pdb
xxx
pdb.set_trace() # 运行到这里会自动暂停
xxx
IDE
https://www.liaoxuefeng.com/wiki/1016959663602400/1017604210683936
https://www.liaoxuefeng.com/wiki/1016959663602400/1017605739507840
知识点:磁盘上读写文件的功能都是由操作系统提供的,现代的操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(文件描述符),然后通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写数据)
file-like Object
像open()
函数返回的这种有个read()
方法的对象,在Python中统称为file-like Object。除了file外,还可以是内存的字节流,网络流,自定义流等等。file-like Object不要求从特定类继承,只要写个read()
方法就行。StringIO
就是在内存中创建的file-like Object,常用作临时缓冲。
open(‘file_path’,‘r/w/a/rb/wb/ab’,encoding=’’,errors=’’)函数
with语句
使用with语句后就不需要手动关闭文件对象了
with open('/path/to/file', 'r') as f:
print(f.read())
# with语句自动完成了 f.close()
https://www.liaoxuefeng.com/wiki/1016959663602400/1017609424203904
在命令行中
在Python程序中
练习:
编写一个程序,能在当前目录以及当前目录的所有子目录下查找文件名包含指定字符串的文件,并打印出相对路径。
递归方法
import os def find_str_file(path, strname): L = [] for f in os.listdir(path): relative_path = os.path.join(path,f) if os.path.isdir(f): try: find_str_file(relative_path,strname) except: print(relative_path,'拒绝访问') if strname.lower() in f.lower(): L.append(relative_path) else: return '未找到对应的文件' return L l = find_str_file('D:\Data\Desktop', 'P') print(l)
正常方法
import os
def find_str_file(path, strname):
L = []
for cur_path, alldir_path_list, allfile_path_list in os.walk(path):
for file_name in allfile_path_list:
if strname.lower() in file_name.lower():
L.append(os.path.join(cur_path, file_name))
else:
return '未找到对应的文件'
return L
l = find_str_file('.','12345646')
print(l)
https://www.liaoxuefeng.com/wiki/1016959663602400/1017624706151424
dumps()
和loads()
函数是定义得非常好得接口的典范。当我们使用时,只需要传入一个必须的参数。但是,当默认的序列化或反序列化机制不满足我们的要求时,我们又可以传入更多的参数来定制序列化或反序列化的规则,既做到了接口简单易用,又做到了充分的扩展性和灵活性。https://www.zhihu.com/question/346422354
https://www.cnblogs.com/resn/p/5591419.html
线程和进程只是程序的执行过程,而不是具体指某一个程序
程序:一个按指定格式存储的一系列指令的编码序列。即一组指令序列
操作系统:也是程序的一种,给程序员提供一个统一的访问界面,便于去管理其他程序
进程:把一个被载入内存、正在执行的程序叫做进程
线程:是操作系统中最小的执行单元,而进程由至少一个线程组成,一个程序的执行点,线程可以由操作系统实现,也可以自己写程序实现。
多线程:一个程序的多个执行点
线程和进程的区别
如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间
多进程和多线程的程序涉及到同步、数据共享的问题,编写起来更复杂
fork()
调用实现多进程
multiprocessing
模块Queue
、Pipes
等实现的import os
from multiprocessing import Process
def run_proc(name):
print('Run child process %s (%s)' % (name,os.getpid()))
if __name__ == "__main__":
print('Parent process %s' % os.getpid())
p = Process(target=run_proc, args=('test',))
print('Child process will start')
p.start()
p.join()
print('Child process end')
from multiprocessing import Pool import os,time,random def long_time_task(name): print('Run task %s (%s)' % (name, os.getpid())) start = time.time() time.sleep(random.random() * 3) end = time.time() print('Task %s run %0.2f seconds' % (name, (end-start))) if __name__ == "__main__": print('Parent process %s' % os.getpid()) p = Pool(4) for i in range(5): p.apply_async(long_time_task, args=(i,)) print('Waiting for all subprocessing done...') p.close() p.join() print('All subprocessing done')
from multiprocessing import Queue,Process import os,time,random def write(q): print('Process to write:%s' % os.getpid()) for i in ['A','B','C']: print('Put %s to queue' % i ) q.put(i) time.sleep(random.random()) def read(q): print('Process to read %s' % os.getpid()) while True: value = q.get(True) print('Get %s from queue' % value) if __name__ == "__main__": q = Queue() pw = Process(target=write, args=(q,)) pr = Process(target=read, args=(q,)) pw.start() pr.start() pw.join() pr.terminate()
_thread
和threading
,_thread
是低级模块,threading
是高级模块import time, threading def loop(): print('thread %s is running...' % threading.current_thread().name) n = 0 while n <5: n = n+1 print('thread %s >>> %s' % (threading.current_thread().name,n)) time.sleep(1) print('thread %s ended' % threading.current_thread().name) if __name__ == "__main__": print('thread %s start'% threading.current_thread().name) t = threading.Thread(target=loop, name='LoopThread') t.start() t.join() print('thread %s ended'% threading.current_thread().name)
多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而在多线程中,所有变量都有所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,从而把内容给改乱了。
为什么多个线程同时改一个变量会把变量内容给该乱了,因为Python是高级语言的原因,高级语言的一条语句在CPU执行时是若干条语句,例如
balance = balance + n
语句其实分为两步执行
1、计算balance + n
,存入临时变量中
2、将临时变量的值赋值给balance
1、x = balance + n
2、balance = x
改乱的例子:
import time, threading balance = 0 def change_it(n): # 先存后取,结果应该为0 global balance balance = balance + n balance = balance - n def run_thread(n): for _ in range(1000000): change_it(n) t1 = threading.Thread(target=run_thread, args=(5,)) t2 = threading.Thread(target=run_thread, args=(8,)) t1.start() t2.start() t1.join() t2.join() print(balance) # 结果balance不为0
正常过程应该是
初始值 balance = 0
t1: x1 = balance + 5 # x1 = 0 + 5 = 5
t1: balance = x1 # balance = 5
t1: x1 = balance - 5 # x1 = 5 - 5 = 0
t1: balance = x1 # balance = 0
t2: x2 = balance + 8 # x2 = 0 + 8 = 8
t2: balance = x2 # balance = 8
t2: x2 = balance - 8 # x2 = 8 - 8 = 0
t2: balance = x2 # balance = 0
结果 balance = 0
实际过程
初始值 balance = 0
t1: x1 = balance + 5 # x1 = 0 + 5 = 5
t2: x2 = balance + 8 # x2 = 0 + 8 = 8
t2: balance = x2 # balance = 8
t1: balance = x1 # balance = 5
t1: x1 = balance - 5 # x1 = 5 - 5 = 0
t1: balance = x1 # balance = 0
t2: x2 = balance - 8 # x2 = 0 - 8 = -8
t2: balance = x2 # balance = -8
结果 balance = -8
为了解决多线程间调用全局变量
的问题,需要引入锁
概念,这样多个线程可以有序执行,有序的去调用共享的变量,但是也正是由于锁的存在,会影响执行速度,而且每个线程都有一个锁,可能会形成不同线程持有不同的锁且试图获取对方持有的锁的情况,导致死锁出现
import threading, multiprocessing balance = 0 def change_it(n): # 先存后取,结果应该为0: global balance balance = balance + n balance = balance - n lock = threading.Lock() def run_thread(n): for i in range(1000000): lock.acquire() try: change_it(n) finally: lock.release() t1 = threading.Thread(target=run_thread, args=(5,)) t2 = threading.Thread(target=run_thread, args=(8,)) t1.start() t2.start() t1.join() t2.join() print(balance)
ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题,多个线程中使用全局变量的话就必须要加锁,而使用局部变量就不需要
为了解决多线程中变量共享的问题,可以使用局部变量,可以定义一个dict,将变量名作为key,值作为value,这样只需要在每个线程对应的函数中去调用里面的值,然后赋值给函数中的局部变量即可。而ThreadLocal对象相当于作为中间对象
import threading # 创建全局的ThreadLocal对象 local_school = threading.local() def process_student(): # 获取当前线程关联的student std = local_school.student print('Hello, %s (in %s)' % (std, threading.current_thread().name)) def process_thread(name): local_school.student = name process_student() t1 = threading.Thread(target=process_thread, args=('Alice',), name='Thread-t1') t2 = threading.Thread(target=process_thread, args=('Bob',), name='Thread-t2') t1.start() t2.start() t1.join() t2.join()
对于Python来说,每个进程都会有一把GIL锁,而每个进程中的多个线程都会使用这把GIL锁,所以Python的多线程其实使用不了多核CPU,还是相当于单核CPU按顺序执行多个任务
Python解释器上的GIL全局锁会给所有线程的执行代码都上了锁,所以多线程在Python中其实只能交替执行,并不可以真正利用到多核CPU,多线程程序不能并发执行,这是历史遗留问题,除非重写一个不带GIL的解释器
想要实现并发执行,也可以使用多进程实现,Python每个进程都会有一把GIL,互相之间独立,互不影响
Master-Worker
模式,Master负责分配调度任务,Worker负责执行任务,多任务环境下,通常是一个Master,多个Worker事件驱动模型
。在Thread和Process中,应当首选Process,因为Process更稳定,而且Process可以分布在多台机器上,而Thread最多只能分布到同一台机器的多个CPU上
Python 的multiprocessing模块中的managers子模块还支持把多进程分布到多台机器上,由于managers模块封装的很好,不必了解网络通信的细节,也可以很容易地编写分布式多进程程序
task_master.py
# task_master.py import random, time, queue from multiprocessing.managers import BaseManager # 发送任务的队列: task_queue = queue.Queue() # 接收结果的队列: result_queue = queue.Queue() def return_task_queue(): global task_queue return task_queue def return_result_queue(): global result_queue return result_queue # 从BaseManager继承的QueueManager: class QueueManager(BaseManager): pass if __name__ == "__main__": # 把两个Queue都注册到网络上, callable参数关联了Queue对象: QueueManager.register('get_task_queue', callable=return_task_queue) QueueManager.register('get_result_queue', callable=return_result_queue) # 绑定端口5000, 设置验证码'abc': manager = QueueManager(address=('127.0.0.1', 5001), authkey=b'abc') # 启动Queue: manager.start() # 获得通过网络访问的Queue对象: task = manager.get_task_queue() result = manager.get_result_queue() # 放几个任务进去: for i in range(10): n = random.randint(0, 10000) print('Put task %d...' % n) task.put(n) # 从result队列读取结果: print('Try get results...') for i in range(10): r = result.get(timeout=10) print('Result: %s' % r) # 关闭: manager.shutdown() print('master exit.')
另一台机器上(可以是本机不同的IDE运行)
# task_worker.py import time, sys, queue from multiprocessing.managers import BaseManager # 创建类似的QueueManager: class QueueManager(BaseManager): pass if __name__ == "__main__": # 由于这个QueueManager只从网络上获取Queue,所以注册时只提供名字: QueueManager.register('get_task_queue') QueueManager.register('get_result_queue') # 连接到服务器,也就是运行task_master.py的机器: server_addr = '127.0.0.1' print('Connect to server %s...' % server_addr) # 端口和验证码注意保持与task_master.py设置的完全一致: m = QueueManager(address=(server_addr, 5000), authkey=b'abc') # 从网络连接: m.connect() # 获取Queue的对象: task = m.get_task_queue() result = m.get_result_queue() # 从task队列取任务,并把结果写入result队列: for i in range(10): try: n = task.get(timeout=1) print('run task %d * %d...' % (n, n)) r = '%d * %d = %d' % (n, n, n*n) time.sleep(1) result.put(r) except Queue.Empty: print('task queue is empty.') # 处理结束: print('worker exit.')
Python的分布式进程接口简单,封装良好,适合把繁重任务分布到多台机器的环境下
注意Queue的作用是用来传递任务和接收结果的,每个任务的描述数据量要尽量小。比如发送一个处理日志文件的任务,就不要发送几百兆的日志文件本身,而是发送日志文件存放的完整路径,有Worker进程再去共享的磁盘上读取文件。
字符串是编程时涉及到的最多的一种数据结构,对字符串进行操作的需求几乎无处不在。
正则表达式是一种用来匹配字符串的强有力的武器,它的设计思想使用一种描述性的语言来给字符串定义一个规则,凡是符合规则的字符串,我们就认为它“匹配”,否则,该字符串就是不合法的
使用正则表达式步骤:1、创建一个用于匹配的正则表达式。2、用该正则表达式去匹配用户的输入来判断是否合法
re模块
re.match(正则表达式,待匹配字符串)
re.compile(正则表达式):编译一个正则表达式,然后用编译后生成的Regular Expression对象去匹配字符串
re.spilt**(正则表达式,待切割的字符串)**:这个模块比字符串的split方法强大多了
r = re.split(r'[\s\,\;]+', 'a,b;; c d')
print(r)
# ['a', 'b', 'c', 'd']
练习:
# 请尝试写一个验证Email地址的正则表达式。版本一应该可以验证出类似的Email: import re # someone@gmail.com # bill.gates@microsoft.com def is_valid_email(addr): re_email = re.compile(r'^[a-zA-Z](.*?)\@(gmail|microsoft)\.(\w+)$') if not re_email.match(addr): return False return True # 测试: assert is_valid_email('someone@gmail.com') assert is_valid_email('bill.gates@microsoft.com') assert not is_valid_email('bob#example.com') assert not is_valid_email('mr-bob@example.com') print('ok')
# 版本二可以提取出带名字的Email地址: # <Tom Paris> tom@voyager.org => Tom Paris # bob@example.com => bob import re def name_of_email(addr): # re_email = re.compile(r'<?([\w\s]+)>?\s*(\w*)@(\w+\.\w+)$') # result = re_email.match(addr) result = re.match(r'<?([\w\s]+)>?\s*(\w*)@(\w+\.\w+)$', addr) print(result.groups()) if not result: return False return result.group(1) # 测试: assert name_of_email('<Tom Paris> tom@voyager.org') == 'Tom Paris' assert name_of_email('tom@voyager.org') == 'tom' print('ok')
https://www.liaoxuefeng.com/wiki/1016959663602400/1017648783851616
datetime是Python处理日期和时间的标准库
datetime转换为timestamp:
timestamp转换为datetime:
timestamp转换为utc时区的datetime:
str转换为datetime:
datetime转换为str:
datetime加减:
本地时间转换为UTC时间:
时区转换:
datetime表示的时间需要时区信息才能确定一个特定的时间,否则只能视为本地时间。
如果要存储datetime,最佳方法是将其转换为timestamp再存储,因为timestamp的值与时区完全无关
练习
假设你获取了用户输入的日期和时间如2015-1-21 9:01:30,以及一个时区信息如UTC+5:00,均是str,请编写一个函数将其转换为timestamp:
import re from datetime import datetime, timezone, timedelta # def to_timestamp(dt_str, tz_str): # dt_obj = datetime.strptime(dt_str,'%Y-%m-%d %H:%M:%S') # tz_hours = int(re.split(r'[UTC\:]+',tz_str)[1]) # tz_utc = timezone(timedelta(hours=tz_hours)) # dt_obj_tz = dt_obj.replace(tzinfo=tz_utc) # return datetime.timestamp(dt_obj_tz) def to_timestamp(dt_str, tz_str): re_tz = re.match(r'^UTC([\-\+]\d{1,2})\:(\d{1,2})$', tz_str) dt = datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S').replace(tzinfo=timezone(timedelta(hours=int(re_tz.group(1)),minutes=int(re_tz.group(2))))) return dt.timestamp() # 测试 t1 = to_timestamp('2015-6-1 08:10:30', 'UTC+7:00') assert t1 == 1433121030.0, t1 t2 = to_timestamp('2015-5-31 16:10:30', 'UTC-09:00') assert t2 == 1433121030.0, t2 print('ok')
Base64是一种用64个字符来表示任意二进制数据的方法
Base64是一种通过查表的编码方法,不能用于加密,即使使用自定义得编码表也不行
Base64适用于小段内容的编码,比如数字证书签名、Cookie的内容
Base64是把3个字节变成4个字节,所以,Base64编码的长度永远是4的倍数,编码长度不够4的倍数时,会添加“=”号,但“=”用在URL、Cookie里面会造成歧义,所以很多Base64编码后会把“=”号去掉,但去掉“=”后的编码字节不够4的倍数时,需要编写函数去添加“=”,如下
练习:
请写一个能处理去掉=
的base64解码函数
import base64
def safe_base64_decode(s):
# print(s)
# print(type(s))
remainder = len(s) % 4
if remainder:
for _ in range(4-remainder):
s = s + b'='
return base64.b64decode(s)
assert b'abcd' == safe_base64_decode(b'YWJjZA=='), safe_base64_decode('YWJjZA==')
assert b'abcd' == safe_base64_decode(b'YWJjZA'), safe_base64_decode('YWJjZA')
print('ok')
Base64是一种任意二进制到文本字符串的编码方法,常用于在URL、Cookie、网页中传输少量二进制数据
https://www.liaoxuefeng.com/wiki/1016959663602400/1017685387246080
https://www.liaoxuefeng.com/wiki/1016959663602400/1017686752491744
Python 的hashlib提供了常见的摘要算法,如MD5、SHA1等等
练习
根据用户输入的登录名和口令模拟用户注册,计算更安全的MD5:
import random, hashlib db = {} # 用于存储用户名和口令 # md5加密 def get_md5(s): return hashlib.md5(s.encode('utf-8')).hexdigest() # 用户类 class User(object): def __init__(self, username, password): self.username = str(username) self.salt = ''.join([ chr(random.randint(30,200)) for i in range(20)]) self.password = get_md5(str(password) + self.salt) # 注册时加密 def register(username, password): # 实例化User对象 s = User(username, password) # 将对象存入字典db db[username] = s # 添加用户口令信息 register('bob', 'abc999') register('michael',123456) register('alice','alice2008') print(db) # 获取db字典中User对象中的password值 # db_dict = {user:password.password for user,password in db.items()} # print(db_dict) # 登录 def login(username, password): user = db[str(username)] return user.password == get_md5(str(password) + user.salt) # 测试: assert login('michael', '123456') assert login('bob', 'abc999') assert login('alice', 'alice2008') assert not login('michael', '1234567') assert not login('bob', '123456') assert not login('alice', 'Alice2008') print('ok')
hmac算法相当于封装了“哈希算法+salt”
将上一节的练习使用hmac做修改
import random, hmac db = {} # 用于存储用户名和口令 # hmac函数md5加密 def hmac_md5(key, s): # 使用hmac函数 return hmac.new(key.encode('utf-8'), str(s).encode('utf-8'),digestmod='MD5').hexdigest() # 用户类 class User(object): def __init__(self, username, password): self.username = str(username) self.key = ''.join([ chr(random.randint(30,200)) for i in range(20)]) self.password = hmac_md5(self.key, password) # 注册时加密 def register(username, password): # 实例化User对象 s = User(username, password) # 将对象存入字典db db[username] = s # 添加用户口令信息 register('bob', 'abc999') register('michael',123456) register('alice','alice2008') print(db) # 获取db字典中User对象中的password值 # db_dict = {user:password.password for user,password in db.items()} # print(db_dict) # 登录 def login(username, password): user = db[str(username)] return user.password == hmac_md5(user.key, password) # 测试: assert login('michael', '123456') assert login('bob', 'abc999') assert login('alice', 'alice2008') assert not login('michael', '1234567') assert not login('bob', '123456') assert not login('alice', 'Alice2008') print('ok')
itertools提供了非常有用的用于操作迭代对象的函数
itertools模块提供的全部是处理迭代功能的函数,它们的返回值不是list,而是Iterator,只有用for循环迭代的时候才真正计算
itertools.count(x,steps=n):会创建一个无限的迭代器,从x开始步长为n
itertools.repeat(x, y):无限的迭代器,创建y个x
itertools.cycle(x):无限迭代器,x为序列,无限重复序列中的值
itertools.takewhile(func, x):根据条件判断来截取出一个有限序列的迭代器,有点类似filter()但又不一样
itertools.chain(x,y):可以把一组迭代对象串联起来,形成一个更大的迭代器
itertools.groupby(x):把x中相邻的重复元素挑出来放在一起
练习
计算圆周率可以根据公式:
利用Python提供的itertools模块,我们来计算这个序列的前N项和:
import itertools def pi(N): ' 计算pi的值 ' # step 1: 创建一个奇数序列: 1, 3, 5, 7, 9, ... odd_iter = itertools.count(1,step=2) # step 2: 取该序列的前N项: 1, 3, 5, 7, 9, ..., 2*N-1. odd_n = itertools.takewhile(lambda x: x <= 2*N-1,odd_iter) # step 3: 添加正负符号并用4除: 4/1, -4/3, 4/5, -4/7, 4/9, ... tmp = map(lambda x: 4 / x if x % 4 ==1 else -4 / x, odd_n) # step 4: 求和: return sum(tmp) # 测试: print(pi(10)) print(pi(100)) print(pi(1000)) print(pi(10000)) assert 3.04 < pi(10) < 3.05 assert 3.13 < pi(100) < 3.14 assert 3.140 < pi(1000) < 3.141 assert 3.1414 < pi(10000) < 3.1415 print('ok')
contextlib模块中提供了contextmanager、closing等函数,方便我们对资源做上下文管理
读写文件资源时,读写完后要使用"文件对象.close()"方法关闭文件对象,还是挺麻烦的
@contextmanager
可以使用with…as语句,with语句调用的是fp(file point文件指针)对象中__enter__和__exit__方法,为了不写这两个方法,可以使用contextmanager装饰器
from contextlib import contextmanager class Query(object): def __init__(self, name): self.name = name def query(self): print('Query info about %s...' % self.name) @contextmanager def create_query(name): print('Begin') q = Query(name) yield q print('End') # @contextmanager这个decorator接受一个generator,用yield语句把with ... as var把变量输出出去,然后,with语句就可以正常地工作了: with create_query('Bob') as q: q.query()
closing()
如果一个对象没有实现上下文(enter和exit方法),它就不能用于with语句,这时候可以使用closing()函数将对象变成上下文对象
from contextlib import closing
from urllib.request import urlopen
with closing(urlopen('https://www.python.org')) as page:
for line in page:
print(line)
closing也是一个经过@contextmanager装饰的generator,这个generator编写起来其实非常简单
@contextmanager
def closing(thing):
try:
yield thing
finally:
thing.close()
练习:
# 定义一个把任意对象变成可以用with调用的对象的函数。并简单的打印调用过程中产生的错误。
# 定义一个把任意对象变成可以用with调用的对象的函数。并简单的打印调用过程中产生的错误。 from contextlib import contextmanager class Query(object): def __init__(self, name): self.name = name def query(self, q_name): print(f'Query: {q_name}') if self.name == q_name: print(f"Yes! it's {q_name}") else: raise KeyError(f"No! it isn't {q_name}") @contextmanager def catch_exc(obj): try: yield obj except Exception as e: print(e) with catch_exc(Query('Bob')) as q: q.query('Mechale')
urllib提供了一系列用于操作URL功能的模块
urllib提供的功能就是利用程序去执行各种HTTP请求。如果要模拟浏览器完成特定功能,需要把请求伪装成浏览器。伪装的方法是先监控浏览器发出的请求,再根据浏览器的请求头来伪装,User-Agent头就是用来标识浏览器的。
request模块
Get
from urllib import request
req = request.Request('http://www.douban.com/')
req.add_header('User-Agent', 'Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25')
with request.urlopen(req) as f:
date = f.read()
print('Status:', f.status, f.reason)
for k, v in f.getheaders():
print('%s: %s' % (k, v))
print('Data:',date.decode('utf-8'))
Post:相对于Get方式,多做一步表单传输操作
from urllib import request, parse print('Login to weibo.cn...') username = input('username:') password = input('Password:') login_data = parse.urlencode( [ ('username',username), ('password',password), ('entry','nweibo'), ('savestate','1'), ] ) req = request.Request('https://passport.weibo.cn/sso/login') req.add_header('Origin','https://passport.weibo.cn') req.add_header('Referer','https://passport.weibo.cn/signin/login') req.add_header('User-Agent','Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.162 Safari/537.36') with request.urlopen(req, data=login_data.encode('utf-8')) as f: print('Status',f.status, f.reason) for k, v in f.getheaders(): print('%s: %s' % (k, v)) print('Data:', f.read().decode('utf-8'))
handler:如果还需要更复杂的控制,比如通过一个Proxy去访问网站,我们需要利用ProxyHandler
来处理,示例代码如下:
from urllib import request
proxy_handler = urllib.request.ProxyHandler({'http': 'http://www.example.com:3128/'})
proxy_auth_handler = urllib.request.ProxyBasicAuthHandler()
proxy_auth_handler.add_password('realm', 'host', 'username', 'password')
opener = urllib.request.build_opener(proxy_handler, proxy_auth_handler)
with opener.open('http://www.example.com/login.html') as f:
pass
练习:利用urllib读取JSON,然后将JSON解析为Python对象:
import json
from urllib import request
def fetch_data(url):
with request.urlopen(str(url)) as f:
return json.loads(f.read().decode('utf-8'))
return None
# 测试
URL = 'https://yesno.wtf/api'
data = fetch_data(URL)
print(data)
assert data['answer']== 'yes' and (data['forced']== False)
print('ok')
DOM vs SAX
通常优先考虑SAX,因为DOM太占用内存
SAX事件有三个需要自己处理:start_element事件(读取开头标签时)、char_data事件(读取数据时)、end_element事件(读取结束标签时)
from xml.parsers.expat import ParserCreate class DefaultSaxHandler(object): def start_element(self, name, attrs): print('sax:start_element: %s, attrs: %s' % (name, str(attrs))) def end_element(self, name): print('sax:end_element: %s' % name) def char_data(self, text): print('sax:char_data: %s' % text) xml = r'''<?xml version="1.0"?> <ol> <li><a href="/python">Python</a></li> <li><a href="/ruby">Ruby</a></li> </ol> ''' handler = DefaultSaxHandler() parser = ParserCreate() parser.StartElementHandler = handler.start_element parser.EndElementHandler = handler.end_element parser.CharacterDataHandler = handler.char_data parser.Parse(xml)
使用字符串的方式生成XML
L = []
L.append(r'<?xml version="1.0"?>')
L.append(r'<root>')
L.append(encode('some & data'))
L.append(r'</root>')
return ''.join(L)
注意:如果要生成复杂的XML,建议使用JSON
编写一个搜索引擎步骤:1、用爬虫将网站页面爬取下来。2、解析该HTML页面
由于HTML语法没有XML那么严格,所以不能用标准的DOM或SAX方式去解析HTML。
使用HTMLParser来解析HTML。
from html.parser import HTMLParser from html.entities import name2codepoint class MyHTMLParser(HTMLParser): def handle_starttag(self, tag, attrs): print('starttag:%s' % tag) def handle_endtag(self, tag): print('endtag:%s' % tag) def handle_startendtag(self, tag, attrs): print('startendtag:%s ' % tag) def handle_data(self, data): print('data:',data) def handle_comment(self, data): print('comment:<!——', data, '——>') def handle_charref(self, name): print('charref:&#%s' % name) html = r'''<html> <head></head> <body> <!-- test html parser --> <p>Some <a href=\"#\">html</a> HTML tutorial...<br>END</p> </body></html>''' parser = MyHTMLParser() parser.feed(html)
feed()
方法可以调用多次,也就是不一定要一次性把HTML字符串都塞进去,可以一部分一部分塞进去。
特殊字符有两种,一种时英文表示的
,一种时数字表示的Ӓ
,这两种字符都可以通过Parser解析出来。
利用HTMLParser,可以把网页中的文本、图像等解析出来。
可以使用Anaconda管理工具,不用麻烦的去一个一个安装所需要的库
PIL(Python Imaging Library )官方文档:https://pillow.readthedocs.org/
PIL提供了操作图像的强大功能,可以通过简单的代码完成复杂的图像处理。
由于PIL仅支持到Python 2.7,加上年久失修,有一群志愿者在PIL的基础上创建了兼容的版本叫做Pillow
图片缩放与图片模糊滤镜
# #图片缩放 # from PIL import Image # # 打开一个图片,并获取对象 # im = Image.open(r'D:\Data\Desktop\cat.jpg') # # 获取图片对象的尺寸 # w, h = im.size # print('Original image size: %sx%s'%(w,h)) # # 缩放图片到50% # im.thumbnail((w//2,h//2)) # # print('Resize image to :%sx%s'%(w//2,h//2)) # # 将缩放后的图片用jpeg格式保存 # im.save('thumbnail.jpg','jpeg') # 给图片添加模糊滤镜 from PIL import Image, ImageFilter # 获取图片对象 im = Image.open(r'D:\Data\Desktop\cat.jpg') # 应用模糊滤镜 im2 = im.filter(ImageFilter.BLUR) # 保存图片 im2.save('blur.jpg','jpeg')
使用pillow生成验证码字母图片,逻辑如下:
from PIL import Image, ImageFont, ImageDraw,ImageFilter import random # 随机字母 def rndChar(): return chr(random.randint(65,90)) # 随机颜色1: def rndColor(): return (random.randint(64,255),random.randint(64,255),random.randint(64,255)) # 随机颜色2: def rndColor2(): return (random.randint(32,127),random.randint(32,127),random.randint(32,127)) # 指定图片长度 width = 60*4 height = 60 # 创建Image对象 image = Image.new('RGB',(width,height),(255,255,255)) # 创建Font对象 font = ImageFont.truetype(r'C:\Windows\Fonts\Arial.ttf',36) draw = ImageDraw.Draw(image) # 填充每个像素 for x in range(width): for y in range(height): draw.point((x,y),fill=rndColor()) # 填充文字 for t in range(4): draw.text((60*t+10,10),text=rndChar(),font=font,fill=rndColor2()) # 添加模糊功能 image = image.filter(ImageFilter.BLUR) image.save('code.jpg','jpeg')
requests和python内置的urllib模块都是用于访问网络资源的。
相对于python内置的urllib模块,requests模块用起来比较方便,而且还封装了很多实用的高级功能。
pip3 install requests
网络请求方式有:GET、POST、PUT、DELETE
GET请求的话, 需要注意传入的url是否正确,url是否携带参数,是否需要携带请求头信息
POST请求:需要传入data数据,data的类型为字典或json类型
参数:url、headers(请求头信息)、data(post请求数据)、files(保存文件)、json(将data数据序列化为json格式)、cookies、timeout、
对象属性/方法:status_code、text、content(bytes类型数据)、encoding(编码格式)、
参考文章:https://www.liaoxuefeng.com/wiki/1016959663602400/1183249464292448
chardet第三方库是用于检测bytes
对象数据的编码格式,虽然Python提供了Unicode表示的str
和bytes
两种数据类型,并且可以通过encode()
和decode()
方法转换,但是需要猜测编码类型,就比较麻烦。
pip3 install chardet
import chardet
res = chardet.detect(b'Hello, world!')
print(res)
# {'encoding': 'ascii', 'confidence': 1.0, 'language': ''}
confidence表示概率,1.0为100%
psutil = process and system utilities,去获取系统运行的状态的一个第三方工具
去获取cpu信息、内存信息、进程线程信息、磁盘使用信息、网络连接信息等
Linux:ps
、top
、free
Linux/Unix/OSX/Windows:psutil
pip3 install psutil
# 获取CPU信息 import psutil # cpu逻辑数量 print(psutil.cpu_count()) # cpu物理核心数 print(psutil.cpu_count(logical=False)) # 统计cpu的用户/系统/空间时间 print(psutil.cpu_times()) # 计算CPU使用率 # percpu指的是每个cpu,interval是指规定间隔时间去检测 # for x in range(10): # print(psutil.cpu_percent(interval=1,percpu=True)) # 获取物理内存,会显示总内存大小,可用内存,已用内存 print(psutil.virtual_memory()) # 获取交换内存 print(psutil.swap_memory()) # 获取磁盘信息:磁盘分区、磁盘使用率、磁盘IO信息 # print(psutil.disk_partitions()) # print(psutil.disk_usage('/')) # print(psutil.disk_io_counters()) # 获取网络信息:网络接口、网络连接信息 # print(psutil.net_io_counters()) # 网络读写字节/包的个数 # print(psutil.net_if_addrs()) # 网络接口信息 # print(psutil.net_if_stats()) # 网络接口状态 print('---------------------------------') # 获取当前网络连接信息 # print(psutil.net_connections()) # 获取进程信息 # print(psutil.pids()) # 所有进程ID p = psutil.Process(10332) # 获取指定进程ID对象 print(p.name()) # 进程名 print(p.exe()) # 进程exe路径 # print(p.cwd()) # 进程当前工作目录 print(p.cmdline()) # 进程启动的命令行 print(p.ppid()) # 父进程ID print(p.parent()) # 父进程 print(p.children()) # 子进程列表 print(p.status()) # 状态 print(p.username()) # 用户名 print(p.create_time()) # 显示的是时间戳 import datetime print(datetime.datetime.fromtimestamp(p.create_time())) # 将时间戳转为正常格式时间显示 # print(p.terminal()) # 进程的终端信息 print(p.cpu_times()) # 进程使用的CPU时间 print(p.memory_info()) #进程使用的内存 # print(p.open_files()) #进程打开的文件 # print(p.connections()) # 进程相关网路连接 print(p.num_threads()) # 进程的线程数 # print(p.threads()) # 进程的线程 # print(p.environ()) # 进程的环境变量 print(psutil.test()) # 模拟ps命令显示进程信息 # print(p.terminate()) # 结束进程
虚拟环境可以创建多个运行环境,可以安装不同版本的包。可以解决版本冲突问题。
虚拟环境管理工具有很多:pipenv、virtualenv、virtualenvwrapper
参考文章:
https://zhuanlan.zhihu.com/p/60647332
https://zhuanlan.zhihu.com/p/55781739
Python支持多种图形界面的第三方库:Tk、wxWidgets、Qt、GTK
Tk是一个图形库,支持多个操作系统,使用Tcl语言开发,Tk会调用操作系统提供给本地的GUI接口,完成最终的GUI。
Tkinter是Python内置的库,Tkinter封装了访问Tk的接口,所以我们直接去调用Tkinter提供的接口就可以了。
示例1:简单的GUI程序
Frame
是所有Widget的父容器,在GUI中每个Button、Label、输入框等都是一个Widget。pack()
方法把Widget加入到父容器中,并实现布局。pack()
是简单的布局,grid()
可以实现复杂的布局。from tkinter import * class Application(Frame): # 继承Frame def __init__(self,master=None): Frame.__init__(self, master) self.pack() self.createWidgets() def createWidgets(self): self.helloLabel = Label(self, text='Hello world!') self.helloLabel.pack() # 将helloLabel这个Widget添加到父容器Frame中 self.quitButton = Button(self, text='Quit', command=self.quit) self.quitButton.pack() app = Application() # 设置标题窗口 app.master.title('Hello World!') # 启动消息循环 app.mainloop()
示例2:加入一个文本框
# from tkinter import * # # class Application(Frame): # def __init__(self,master=None): # Frame.__init__(self, master) # self.pack() # self.createWidgets() # # def createWidgets(self): # self.helloLabel = Label(self, text='Hello world!') # self.helloLabel.pack() # self.quitButton = Button(self, text='Quit', command=self.quit) # self.quitButton.pack() # # app = Application() # app.master.title('Hello World!') # app.mainloop() from tkinter import * import tkinter.messagebox as messagebox class Application(Frame): def __init__(self,master=None): Frame.__init__(self,master) self.pack() self.createWidgets() def createWidgets(self): self.nameInput = Entry(self) self.nameInput.pack() self.alertButton = Button(self, text='Hello', command=self.hello) self.alertButton.pack() def hello(self): name = self.nameInput.get() or 'world' messagebox.showinfo('Message','Hello, %s' % name) # 弹出消息对话框 app = Application() # 设置窗口标题 app.master.title('Hello World!') # 主消息循环 app.mainloop()
注意:Python内置的Tkinter就可以满足大部分场景的GUI程序要求,如果是非常复杂的程序,建议使用操作系统原生支持的语言和库来编写。
1966年,Seymour Papert和Wally Feurzig发明了一种专门给儿童学习编程的语言----LOGO语言,它的特色就是通过编程指挥一个小海龟(turtle)在屏幕上绘图。
海龟绘图(Turtle Graphics)后来被移植到各种高级语言中,Python内置了turtle库,基本上100%赋值了原始的Turtle Graphics的所有功能。
示例1:绘制长方形
# 导入turtle包的所有内容: from turtle import * # 设置笔刷宽度 width(4) # 前进 forward(200) # 向右转90° right(90) #设置笔的颜色 pencolor('red') forward(100) right(90) pencolor('blue') forward(200) right(90) pencolor('green') forward(100) right(90) # 调用done()让窗口进去消息循环,等待被关系 done()
示例2:画五角星
from turtle import * def drawStar(x, y): pu() goto(x, y) pd() # set heading: 0 seth(0) for i in range(5): fd(40) rt(144) for x in range(0, 250, 50): drawStar(x, 0) done()
https://www.liaoxuefeng.com/wiki/1016959663602400/1249593505347328
网络通信:两个进程间的通信
Python进行网络编程:在Python程序本身这个进程内,连接别的服务器进程的通信端口进行通信。
Socket是网络编程的一个抽象概念。
通常我们用一个Socket表示"打开了一个网络连接",而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可。
现有计算机后有互联网,计算机之间想要连接起来,需要通过互联网,而互联网需要有一些规定(协议)对于计算机之间的连接。
互联网协议包含了上百种协议标准,但最重要的两个协议是TCP协议和IP协议,所以大家把互联网的协议简称为TCP/IP协议。
互联网上每个计算机唯一标识是IP地址,但如果一台计算机同时接入到两个或多个网络中,比如路由器,则会有两个或多个IP地址,所以IP地址对应的实际上是计算机的网络接口通常是网卡。
IP协议:负责将数据从一台计算机通过网络发送到另一台计算机。数据是一小块一小块以IP包的方式被发送出去,路由器负责将IP包转发出去。IP包特点是按快发送,途径多个路由,但不能保证到达,也不保证顺序到达。
TCP协议:建立在IP协议的基础上,TCP协议负责在两台计算机之间建立可连接,保证数据包按顺序
到达。握手建立连接、丢失重发。报文中包含传输数据、源IP地址、目标IP地址、源端口号、目标端口号。
注意:进程间通讯需要知道各自的IP地址和端口号,一个进程可能同时与多个计算机建立连接,因为它会申请多个端口号
1 是否面向连接(即是否可靠)
2 以什么方式传输数据
3 速度方面比较
TCP建立的是可靠连接,双方可以以流的形式发送数据。会建立三次握手流程,保证数据的到达。
TCP连接创建的是双向通道,双方都可以同时给对方发数据。但是谁先发谁后发,怎么协调,要根据具体的协议来决定。例如,HTTP协议规定客户端必须先发请求给服务器,服务器收到后才发数据给客户端。
客户端-服务端模式:
socket.socket()
,绑定bind()
一个IP和端口用于监听,开启一个监听listen()
的端口,等待客户端的连接accept()
,安排线程或进程给对每个客户端连接,接收recv()
客户端发过来的消息,并给客户端发送响应send()
。socket.socket()
,连接指定服务端的IP和端口connect()
,发送数据和接收数据send()
,recv()
。示例1:
import socket # AF_INET是IPv4协议,AF_INET6是IPv6,SOCK_STREAM面向流的TCP协议 # 创建一个socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 建立连接 # 80是web服务器的标准端口 s.connect(('www.sina.com.cn',80)) # 发送数据 s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n') buffer = [] while True: # 规定每次最多接收1k字节 d = s.recv(1024) if d: buffer.append(d) else: break data = b''.join(buffer) print(data) s.close() header, html = data.split(b'\r\n\r\n',1) print(header.decode('utf-8')) print(html.decode('utf-8')) with open('sina.html','wb') as f: f.write(html)
示例2:客户端和服务端
服务端:echo_server.py
# TCP服务端 import socket import threading import time s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 绑定一个IP和端口用于接收客户端的连接 s.bind(('127.0.0.1', 9999)) # 开始监听端口,指定等待连接的最大数量 s.listen(5) print('Waiting for connection...') def tcplink(sock, addr): print('Accept new connection from %s:%s...' % addr) sock.send(b'Welcome!') while True: data = sock.recv(1024) time.sleep(1) if not data or data.decode('utf-8') == 'exit': break sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8')) sock.close() print('Connection from %s:%s closed.' % addr) while True: # 接受一个新连接: sock, addr = s.accept() # 创建新线程来处理TCP连接: t = threading.Thread(target=tcplink, args=(sock, addr)) t.start()
客户端:echo_client.py
# TCP客户端
import socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('127.0.0.1',9999))
print(s.recv(1024).decode('utf-8'))
for data in [b'Michael',b'Tracy',b'Sarah']:
s.send(data)
s.send(b'exit')
s.close()
相对于TCP连接,UDP连接客户端不需要和服务端建立连接的过程,服务端也不需要去监听端口
这里省略掉多线程操作,想要多线程操作可以模仿TCP代码
示例:
# UDP服务端
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('127.0.0.1',9999))
while True:
# recvfrom接收data和addr
data, addr = s.recvfrom(1024)
s.sendto(b'Hello:%s'% data,addr)
# UDP客户端
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in [b'Mic',b'Tra',b'Halo']:
s.sendto(data,('127.0.0.1',9999))
print(s.recv(1024).decode('utf-8'))
s.close()
电子邮件的理解:
先从普通邮件讲起:给朋友写信
电子邮件:me@163.com
给friend@qq.com
发电子邮件
Outlook
或Foxmail
之类的软件写好邮件,填上对方地址,发送过去。这些软件被称为MUA:Mail User Agent 邮件用户代理电子邮件的旅程:
发件人 -> MUA -> MTA ->若干个MTA -> MTA -> MDA <- MUA <- 收件人
程序发送邮件和接收邮件:
发邮件时,MUA和MTA使用的协议就是SMTP:Simple Mail Transfer Protocol,后面的MTA到MTA也是SMTP协议
收邮件时,MUA和MDA使用的协议有两种:POP:Post Office Protocol,目前时版本3,俗称POP3;IMAP:Internet Message Access Protocol,目前是版本4,优点是不但能取邮件,还能直接操作MDA上存储的邮件,比如将邮件从收信箱移到垃圾箱,等等。
邮件客户端在发邮件时,需要将邮件先发送到MTA,这时需要配置SMTP服务器,假设自己是163.com,则需要先配置SMTP服务器对应163的地址:smtp.163.com
,为了证明你是163的用户,SMTP服务器需要你填写邮箱地址和邮箱口令,只有这样MUA才能正常地把Email通过SMTP协议发送给MTA。
类似的,从MDA收邮件时,MDA服务器也要求验证你的邮箱口令,确保不会有人冒充你收取你的邮件,所以Foxmail之类的邮件客户端会要求你填写POP3或IMAP服务器地址、邮箱地址和口令,这样,MUA才能顺利地通过POP或IMAP协议从MDA取到邮件。
https://www.liaoxuefeng.com/wiki/1016959663602400/1017790702398272#0
SMTP是发送邮件的协议,Python内置了对SMTP的支持,可以发送纯文本邮件、HTML邮件以及带附件的邮件
Python对SMTP的支持有smtplib
和email
两个模块,email
负责构造邮件,smtplib
负责发送邮件
示例1:纯文本
from email.mime.text import MIMEText
msg = MIMEText('hellp, send by Python...','plain','utf-8')
from_addr = input('From:')
password = input('Password:')
to_addr = input('To:')
smtp_server = input('SMTP server:')
import smtplib
server = smtplib.SMTP(smtp_server, 25) # SMTP协议默认端口25
server.set_debuglevel(1) # 打印出和SMTP服务器交互的所有信息
server.login(from_addr,password=password) # 登录SMTP服务器
server.sendmail(from_addr,[to_addr],msg.as_string()) # 发送邮件,因为正文是一个str,所以使用as_string()将MIMEText对象转变成str
server.quit() # 关闭服务
示例2:纯文本,添加主题、收件人名字、发件人名字
from email.mime.text import MIMEText from email.header import Header from email.utils import parseaddr,formataddr import smtplib # 格式化邮件地址 def _format_addr(s): name, addr = parseaddr(s) print(name,addr) return formataddr((Header(name, 'utf-8').encode(),addr)) from_addr = input('From:') password = input('Password:') to_addr = input('To:') smtp_server = input('SMTP server:') msg = MIMEText('hello, 我是本人,这封邮件是用来测试的','plain','utf-8') msg['From'] = _format_addr('Python爱好者 <%s>' % from_addr) # msg['To'] = _format_addr('管理员<%s>'% (to_addr)) msg['Subject'] = Header('来自benrende问候。。。', 'utf-8').encode() server = smtplib.SMTP(smtp_server, 25) # SMTP协议默认端口25 server.set_debuglevel(1) # 打印出和SMTP服务器交互的所有信息 server.login(from_addr,password=password) # 登录SMTP服务器 server.sendmail(from_addr,[to_addr],msg.as_string()) # 发送邮件,因为正文是一个str,所以使用as_string()将MIMEText对象转变成str server.quit() # 关闭服务
注意:我们编写了一个函数_format_addr()
来格式化一个邮件地址。注意不能简单地传入name <addr@example.com>
,因为如果包含中文,需要通过Header
对象进行编码。msg['To']
接收的是字符串而不是list,如果有多个邮件地址,用,
分隔即可,如果要发给多个邮箱则在sendmail()
方法中指定即可。
示例3:发送附件邮件,正文是纯文本或html
from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart, MIMEBase from email import encoders from email.header import Header from email.utils import parseaddr, formataddr import smtplib # 格式化邮件地址 def _format_addr(s): name, addr = parseaddr(s) print(name, addr) return formataddr((Header(name, 'utf-8').encode(), addr)) from_addr = input('From:') password = input('Password:') to_addr = input('To:') smtp_server = input('SMTP server:') # 邮件对象: msg = MIMEMultipart() msg['From'] = _format_addr('Python爱好者 <%s>' % from_addr) msg['To'] = _format_addr('管理员 <%s>' % to_addr) msg['Subject'] = Header('来自SMTP的问候……', 'utf-8').encode() # 邮件正文是MIMEText: # msg.attach(MIMEText('send with file...', 'plain', 'utf-8')) # 将附件嵌入到正文中 msg.attach(MIMEText('<html><body><h1>Hello</h1>' + '<p><img src="cid:0"></p>' + '</body></html>', 'html', 'utf-8')) # 添加附件就是加上一个MIMEBase,从本地读取一个图片: # with open('/Users/michael/Downloads/test.png', 'rb') as f: with open(r'D:\Data\Desktop\Snipaste_2020-08-31_15-21-04.png', 'rb') as f: # 设置附件的MIME和文件名,这里是png类型: mime = MIMEBase('image', 'png', filename='test.png') # 加上必要的头信息: mime.add_header('Content-Disposition', 'attachment', filename='test.png') mime.add_header('Content-ID', '<0>') mime.add_header('X-Attachment-Id', '0') # 把附件的内容读进来: mime.set_payload(f.read()) # 用Base64编码: encoders.encode_base64(mime) # 添加到MIMEMultipart: msg.attach(mime) server = smtplib.SMTP(smtp_server, 25) # SMTP协议默认端口25 server.set_debuglevel(1) # 打印出和SMTP服务器交互的所有信息 server.login(from_addr, password=password) # 登录SMTP服务器 server.sendmail(from_addr, [to_addr], msg.as_string()) # 发送邮件,因为正文是一个str,所以使用as_string()将MIMEText对象转变成str server.quit() # 关闭服务
示例4:同时支持HTML和Plain格式
msg = MIMEMultipart('alternative')
msg['From'] = ...
msg['To'] = ...
msg['Subject'] = ...
msg.attach(MIMEText('hello', 'plain', 'utf-8'))
msg.attach(MIMEText('<html><body><h1>Hello</h1></body></html>', 'html', 'utf-8'))
# 正常发送msg对象.
示例5:加密SMTP,不加密则是明文传输,发送邮件的整个过程可能会被窃听,这里以Gmail为例,不同的邮件服务商端口不同
smtp_server = 'smtp.gmail.com'
smtp_port = 587 # gmail的端口
server = smtplib.SMTP(smtp_server, smtp_port)
# server = smtplib.SMTP_SSL("smtp.qq.com", 465) # qq的端口
server.starttls()
# 剩下的代码和前面的一模一样:
server.set_debuglevel(1)
...
**总结:**使用Python的smtplib发送邮件,主要注意两点:
第1点,掌握各种邮件邮件类型的构造方法。
第2点,设置好邮件头就可以顺利发出。
Message
对象:邮件对象
MIMEText
对象:文本邮件对象
MIMEImage
对象:作为附件的图片对象
MIMEMultipart
对象:可以把多个对象组合起来,配合attach
方法
MIMEBase
对象:可以表示任何对象
MEssage
+-MIMEBase
+-MIMEMultipart
+-MIMENonMultipart
+-MIMEMessage
+-MIMEText
+-MIMEImage
https://docs.python.org/3/library/email.mime.html
收取邮件:编写一个MUA作为客户端,从MDA把邮件获取到用户的电脑或手机上
收取的文本还不是一个可阅读的文本,需要email
模块提供的各种类来解析原始文本,变成可阅读的邮件对象。
收取邮件分两步:
poplib
把邮件原始文本下载到本地。email
解析原始文本,还原为邮件对象。示例1:收取邮件
import poplib # 输入邮件地址, 口令和POP3服务器地址: email = input('Email: ') password = input('Password: ') pop3_server = input('POP3 server: ') # 连接到POP3服务器: server = poplib.POP3(pop3_server) # 可以打开或关闭调试信息: server.set_debuglevel(1) # 可选:打印POP3服务器的欢迎文字: print(server.getwelcome().decode('utf-8')) # 身份认证: server.user(email) server.pass_(password) # stat()返回邮件数量和占用空间: print('Messages: %s. Size: %s' % server.stat()) # list()返回所有邮件的编号: resp, mails, octets = server.list() # 可以查看返回的列表类似[b'1 82923', b'2 2184', ...] print(mails) # 获取最新一封邮件, 注意索引号从1开始: index = len(mails) resp, lines, octets = server.retr(index) # lines存储了邮件的原始文本的每一行, # 可以获得整个邮件的原始文本: msg_content = b'\r\n'.join(lines).decode('utf-8') # 稍后解析出邮件: msg = Parser().parsestr(msg_content) # 可以根据邮件索引号直接从服务器删除邮件: # server.dele(index) # 关闭连接: server.quit()
示例2:解析邮件
from email.parser import Parser from email.header import decode_header from email.utils import parseaddr import poplib # 输入邮件地址, 口令和POP3服务器地址: email = input('Email: ') password = input('Password: ') pop3_server = input('POP3 server: ') # 连接到POP3服务器: server = poplib.POP3(pop3_server) # 可以打开或关闭调试信息: # server.set_debuglevel(1) # 可选:打印POP3服务器的欢迎文字: # print(server.getwelcome().decode('utf-8')) # 身份认证: server.user(email) server.pass_(password) # stat()返回邮件数量和占用空间: # print('Messages: %s. Size: %s' % server.stat()) # list()返回所有邮件的编号: resp, mails, octets = server.list() # 可以查看返回的列表类似[b'1 82923', b'2 2184', ...] # print(mails) # 获取最新一封邮件, 注意索引号从1开始: index = len(mails) resp, lines, octets = server.retr(index) # lines存储了邮件的原始文本的每一行, # 可以获得整个邮件的原始文本: msg_content = b'\r\n'.join(lines).decode('utf-8') # 稍后解析出邮件: msg = Parser().parsestr(msg_content) # 这个Message对象本身可能是一个MIMEMultipart对象,即包含嵌套的其他MIMEBase对象,嵌套可能还不止一层。 # 所以我们要递归地打印出Message对象的层次结构: # indent用于缩进显示: def print_info(msg, indent=0): if indent == 0: for header in ['From', 'To', 'Subject']: value = msg.get(header, '') if value: if header == 'Subject': value = decode_str(value) else: hdr, addr = parseaddr(value) name = decode_str(hdr) value = u'%s <%s>' % (name, addr) print('%s%s: %s' % (' ' * indent, header, value)) if (msg.is_multipart()): parts = msg.get_payload() for n, part in enumerate(parts): print('%spart %s' % (' ' * indent, n)) print('%s--------------------' % (' ' * indent)) print_info(part, indent + 1) else: content_type = msg.get_content_type() if content_type == 'text/plain' or content_type == 'text/html': content = msg.get_payload(decode=True) charset = guess_charset(msg) if charset: content = content.decode(charset) print('%sText: %s' % (' ' * indent, content + '...')) else: print('%sAttachment: %s' % (' ' * indent, content_type)) # 邮件的Subject或者Email中包含的名字都是经过编码后的str,要正常显示,就必须decode: def decode_str(s): value, charset = decode_header(s)[0] if charset: value = value.decode(charset) return value # 文本邮件的内容也是str,还需要检测编码,否则,非UTF-8编码的邮件都无法正常显示: def guess_charset(msg): charset = msg.get_charset() if charset is None: content_type = msg.get('Content-Type', '').lower() pos = content_type.find('charset=') if pos >= 0: charset = content_type[pos + 8:].strip() return charset print_info(msg)
小结:用Python的poplib
模块收取邮件分两步:第一步是用POP3协议把邮件获取到本地,第二步是用email
模块把原始邮件解析为Message
对象,然后,用适当的形式把邮件内容展示给用户即可。
数据库存在的意义:
当程序运行时,数据都是在内存中的。当程序终止时,需要将需要的数据存储到磁盘文中。
如何定义数据的存储格式就是一个大问题。可以用文本文件保存,但是不能做到快速查询,只有把数据全部读到内存中才能去遍历获取其中的数据,而且有些数据太大,大小超过了内存的大小。
为了便于程序保存和读取数据,而且能直接通过条件快速查询到指定的数据,数据库出现了,数据库就是专门用于集中存储和查询的软件
关系型数据库(SQL)和非关系型数据库(NoSQL)区别:
关系型数据库类别:
数据库由于都差不多的,Python中的使用方法都是调用对应的库,连接数据库,使用SQL写,提交,关闭连接。亦或是使用SQLAlchemy,所以没怎么总结了。可以自己去看视频来具体学习
https://www.liaoxuefeng.com/wiki/1016959663602400/1017801397501728
web开发应用阶段:
HTTP:一种传输协议,作用在浏览器和服务器之间
HTML是用来定义网页的文本,HTTP是在网络上传输HTTP的协议,用于浏览器和服务器的通信。
一个HTTP包含Header和Body
HTTP GET请求仅请求资源,POST请求资源会附带用户数据
HTML简介:
HTML文档就是一系列的Tag组成,最外层的Tage是<html>
。规范的HTML也包括<head>...</head>
和<body>...</body>
(注意:不要和HTTP的Header、Body搞混了),由于HTML是富文档模型,所以,还有一系列的Tag用来表示链接、图片、表格、表单等等。
CSS简介:
CSS是Cascading Style Sheets(层叠样式表)的简称,CSS用来控制HTML里的所有元素如何展现。
JavaScript简介:
JavaScript虽然名称有个Java,但和Java一点关系都没有。JavaScript是为了让HTML具有交互性而作为脚本语言添加的,JavaScript既可以内嵌到HTML中,也可以外部链接到HTML中。
学习网站:https://www.w3school.com.cn/
具体讲解看这个:https://www.liaoxuefeng.com/wiki/1016959663602400/1017805733037760
Web应用的本质:
静态服务器:Web应用先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件读取静态HTML,返回。
**“动态”服务器:**如果要生成动态HTML用于返回的话,则静态服务器就帮不上忙了(因为只返回静态HTML),所以需要自己实现返回操作(接收HTTP请求、解析HTTP请求、发送HTTP响应)。刚好有对应的服务器软件可以帮助我们实现动态的HTML对应的HTTP操作。帮我们处理TCP链接,HTTP原始请求和响应格式。需要一个统一接口去完成
WSGI接口简单定义:要求Web开发者实现一个函数,就可以响应HTTP请求。
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'<h1>Hello, web!</h1>']
上面的application()
函数就是符合WSGI标准的一个HTTP处理函数(即Web项目后端需要完成的逻辑代码函数,接收HTTP请求信息,然后返回HTTP响应)
dict
对象application()
函数本身没有涉及到任何解析HTTP的部分,就是说底层代码我们不需要编写,只负责在更高层次上考虑如何响应请求就可以了。
但是这个application()
如何调用,两个参数environ
和start_response
也没法提供,返回的bytes
也没法发给浏览器。
所以这个application()
函数就必须由WSGI服务器来调用,有很多WSGI服务器,下面先使用Python内置的WSGI服务器来实现先
运行WSGI服务:
hello.py
负责编写application()
函数,wsgi_server.py
负责充当WSGI服务器去调用appliction()
,他们两个文件处于同一文件夹下
# hello.py def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) # 从environ中读取HTTP请求中的信息 body = '<h1> Hello %s! </h1>' % (environ['PATH_INFO'][1:] or 'web') return [body.encode('utf-8')] # wsgi_server.py # 导入python内置的wsgi服务函数 from wsgiref.simple_server import make_server # 导入自己的application处理HTTP请求函数 from hello import application # 创建一个服务器,IP地址为空,端口是8000,处理函数是application httpd = make_server('', 8000, application) # 开始监听HTTP请求 httpd.serve_forever()
# 启动WSGI服务器
python wsgi_server.py
# 去浏览器访问http://127.0.0.1:8000/michael
小结:
无论多么复杂的Web应用程序,入口都是一个WSGI处理函数。HTTP请求的所有输入信息都可以通过environ
获得,HTTP响应的输出都可以通过start_response()
加上函数返回值作为Body。
复杂的Web应用程序,光靠一个WSGI函数来处理还是太底层了,我们需要在WSGI之上再抽象出Web框架,进一步简化Web开发。
编写HTTP处理函数的流程:
Web框架就是帮我们完成了第一步。
用Flask编写Web App示例:
pip install flask
from flask import Flask from flask import request app = Flask(__name__) @app.route('/', methods=['GET', 'POST']) def home(): return '<h1>Home</h1>' @app.route('/signin', methods=['GET']) def signin_form(): return '''<form action="/signin" method="post"> <p><input name="username"></p> <p><input name="password" type="password"></p> <p><button type="submit">Sign In</button></p> </form>''' @app.route('/signin', methods=['POST']) def signin(): # 需要从request对象读取表单内容: if request.form['username']=='admin' and request.form['password']=='password': return '<h3>Hello, admin!</h3>' return '<h3>Bad username or password.</h3>' if __name__ == '__main__': app.run()
实际的Web App应该拿到用户名和口令后,会去数据库查询再对比,来判断用户是否登录成功。
处理Flask,常见的Python Web框架还有:
有了Web框架后,我们在编写Web应用时,注意力就从WSGI处理函数转移到URL+对应的处理函数,这样编写Web App就更加简单了。
在编写URL处理函数时,除了配置URL外,从HTTP请求拿到用户数据也是非常重要的。Web框架都提供了自己的API来实现这些功能。Flask通过request.form['name']
来获取表单的内容
https://www.liaoxuefeng.com/wiki/1016959663602400/1017806952856928
每个Web框架可能都有其对应的模板与模板语法,Flask支持的是jinja2。
常见的模板:
jinja2:用{% ... %}
表示指令,{{ xx }}
表示变量
Mako:用<% ... %>
和${xxx}
Cheetah:<% ... %>
和${ xxx }
Django:Django是一站式框架,{% ... %}
和{{ xxx }}
有了模板,我们就分离了Python代码和HTML代码。HTML代码全部放到模板里,写起来更有效率。
https://blog.csdn.net/xiaoduu/article/details/108313286
https://www.liaoxuefeng.com/wiki/1016959663602400/1017959540289152
CPU的速度远远快于磁盘、网络等IO。
同步IO:但在一个线程内的话,不实现异步的情况下,一旦遇到了IO操作,如读写文件、发送网络数据等,就需要去等待IO操作完成,此时线程会被挂起,CPU不能去执行线程内的其他操作。这种情况被称为同步IO。
多进程或多线程解决并发:这时候可以配置多线程或多进程来解决这个IO操作造成的阻塞问题,当一个线程被阻塞了,操作系统就可以切换到另一个线程中去,然后CPU继续执行。虽然多线程和多进程的模型解决了并发问题。但是系统不能无上限的增加线程,而且切换线程的开销也很大,即耗时。一旦线程数量过多,时间都花在切换上面了,真正运行代码的时间就少了,CPU利用率就降低,导致资源浪费。
异步IO:在一个线程内实现异步IO,异步IO需要一个消息循环,将异步任务都添加到消息循环中去,消息循环会一直处在监听状态(“读取消息-处理消息”),当其中一个任务遇到IO操作时,程序会去运行消息循环中的其他异步任务,这样就可以有效的利用CPU。
相对于同步IO模型,异步IO模型主线程不会休息,而是在消息循环中继续处理其他消息。在异步IO模型下,一个线程就可以同时处理多个IO请求,并且没有切换线程的操作。对于大多数IO密集型的应用程序,使用异步IO将大大提升系统的多任务处理能力。
协程(Coroutine):微线程,在一个线程中,两个“子程序”之间协作完成任务,所以称为“协程”。
子程序:有称为函数,在所有语言中都是层级调用。比如A调用B,B调用C,需要等C执行完返回结果,然后B才执行,等B执行完,A才执行,最后返回A执行完的结果。
子程序总是一个入口,一次返回,调用顺序是确定的。
协程:协程的调用和子程序不同,协程看上去也是子程序,但执行过程中,在子程序内部可以中断,转而去执行别的子程序,在适当的时候再返回来接着执行当前子程序。注意:在一个子程序中中断去执行其他子程序,而不是调用其他子程序来执行。
协程 vs 多线程:
协程利用多核CPU:多进程+协程
传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。
改用协程,无锁流程:
def consumer(): r = '' while True: n = yield r # 第三步:拿到消息,并处理 if not n: return print('[CONSUMER] Consuming %s...' % n) r = '200 OK' # 第四步:返回结果 def produce(c): c.send(None) # 第一步:启动生成器 n = 0 while n < 5: n = n + 1 print('[PRODUCER] Producing %s...' % n) r = c.send(n) # 第二步:切换到consumer执行 print('[PRODUCER] Consumer return: %s' % r) c.close() c = consumer() produce(c)
https://www.liaoxuefeng.com/wiki/1016959663602400/1017970488768640
asyncio
实现了TCP、UDP、SSL等协议
asyncio
:Python3.4引入的标准库,内置了对异步IO的支持。
asyncio
的编程模型就是一个消息循环。我们从asyncio
模块中直接获取一个Eventloop
的引用,然后把需要执行的协程扔到Eventloop
中执行,就是先了异步IO。
asyncio
提供的@asyncio.coroutine
可以把一个generator标记为coroutine类型,然后在coroutine内部用yield form
调用另一个coroutine实现异步操作。
async与await是针对coroutine的新语法
async
代替@asyncio.coroutine
await
代替yield from
asyncio
实现了TCP、UDP、SSL等协议,aiohttp
则是基于asyncio
实现的HTTP框架。
pip install aiohttp
编写一个HTTP服务器,分别处理以下URL:
/
-首页返回b<h1>Index</h1>
/hello/{name}
- 根据URL参数返回文本hello,%s!
代码如下:
import asyncio from aiohttp import web async def index(request): await asyncio.sleep(0.5) return web.Response(body=b'<h1>Index</h1>') async def hello(request): await asyncio.sleep(0.5) text = '<h1>hello, %s!</h1>' % request.match_info['name'] return web.Response(body=text.encode('utf-8')) async def init(loop): app = web.Application(loop=loop) app.router.add_route('GET', '/', index) app.router.add_route('GET', '/hello/{name}', hello) srv = await loop.create_server(app.make_handler(), '127.0.0.1', 8000)# 创建TCP服务 print('Server started at http://127.0.0.1:8000...') return srv loop = asyncio.get_event_loop() loop.run_until_complete(init(loop)) loop.run_forever()
注意:其中web.Application
的loop
参数已经弃用了,make_handler()
也已经弃用了,这里只做简单汇总知识点。
https://www.liaoxuefeng.com/wiki/1016959663602400/1346182154551329
MicroPython是Python 的一个精简版本,它是为了运行在单片机这样的性能有限的微控制器上,最小体积仅256K,运行时仅需16K内存。
运用MicroPython做嵌入式
实战篇的话廖雪峰老师的教程太老了,有兴趣可以去看aiohttp 3.0的项目,下面有连接
https://aodabo.tech/blog/001546714438970511a8089adc94c909312e2554aa4eabd000
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。