【Python】Python 语言学习 笔记一

基础

(一) 数据类型和变量

整数

python可以处理任意大整数,负整数

num1 = 123
num2 = -123

浮点数

小数,可能存在四舍五入的误差

floatNum = 123.123

字符串

以单引号'或双引号"括起来的任意文本
转义字符
\n表示换行,\t表示制表符,字符\本身也要转义,所以\表示的字符就是\

print('my name is \'luna\'')

如果有很多字符需要转义,简化允许使用 r' ',内部的字符串默认不转义

str1 = r'\\\t\\' 

如果字符串有很多换行,\n多了不好阅读,简化可使用''' '''表示多行内容,多行默认不转义r''' '''

print(''' l1
...l2
...l3''')

布尔值

布尔值只有 TrueFalse(注意大小写)

a = True
b = False
print(a and b)
print(a or b)
print(not a)

空值

空值用None表示,不能理解为0,None为一个特殊的空值

变量

大小写英文、数字和_的组合,且不能以数字开头
同一个变量可以反复赋值,且可以是不同类型

a = 1
t_01 = 't01'
Check = True 

常量

不能变得变量,π为常量

PI = 3.14

整数除法有两种

#整数恰好整除,结果也是浮点数
print(9/3)
print(10/3)  
# 两个整数的除法仍然是整数
print(10//3) 

(二)字符串和编码

字符编码

ASCII
大小写英文字母、数字和一些符号
Unicode
Unicode把所有语言都统一到一套编码里,解决了乱码问题。(两个字节表示一个字符)
UTF-8
UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。

字符串

Python 3版本中,字符串是以Unicode编码。

print('中文 和 English')

对于单个字符编码,提供了ord()函数获取字符的整数表示,chr()函数吧编码转换成对应的字符:

print(ord('A'))
print(ord('我'))
print(ord(66))

Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes。
计算字符字节数:len()

# -*- coding: utf-8 -*-
str_a = 'abc'
bytes_a = b'abc'
print(len(str_a))
print(len(bytes_a))

格式化

字符串内部,%s表示用字符串替换,%d表示用整数替换,有几个%?占位符,后面就跟几个变量或者值,顺序要对应好。如果只有一个%?,括号可以省略。

a1 = 10
b1 = 20
print('a1+b1=%d'%(a1+b1))
a2 = 1.2
b2 = 2.1
print("a2+b2=%f"%(a2+b2))
s1 = 'hello'
s2 = 'python'
print('%s,I\'m %s' %(s1,s2))

# format格式化
name = 'luna'
age = 17
grade = 98
print('哇,{0}今年{1}岁,她的英语成绩是{2}%'.format(name,age,grade))

(三)列表list、元组tuple

list是一种有序的集合,可以随时添加和删除其中的元素。

students = ['a','b','c']
print(students)
print(len(students))

# 访问第一个元素 
print(students[0])
# 访问最后一个元素
print(students[-1])
# 以此类推 获取倒数第二个
print(students[-2])

# 添加元素
students.append('d')
# 指定位置插入元素、
students.insert(1,'K')
print(students)

# 删除列表末尾的元素
students.pop()
print(students)
# 指定位置删除元素
students.pop(1)
print(students)

# 替换指定位置的元素
students[1] = 'c'
print(students)

有序列表叫元组:tupletuplelist非常类似,但是tuple一旦初始化就不能修改。定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来。

# 元组
tups = ('bob','mok','lvf')
print(tups[0])
# 空元组
tup1 = ()
# 只有一个元素
tup2 = (1,)

(四)条件判断

如果if语句判断是True,就把缩进的两行print语句执行了,否则,什么也不做。
if添加一个else语句,意思是,如果if判断是False,不要执行if的内容,去把else执行了

# if ...
age = 20
if age >= 18:
    print('你的年龄是:',age)
    print('成年')

# if ... else
if age >= 18:
    print('成年')
else:
    print('未成年')

# if...elif...else
if age >= 18:
    print('成年')
elif age < 18:
    print('未成年')
else:
    print('错误')

age1 = int(input('age:'))
if age1 >= 18:
    print('成年')
else:
    print('未成年')

(五)循环

循环有两种,for...in循环、while循环
for...in循环,依次把listtuple中的每个元素迭代出来,for x in ...循环就是把每个元素代入变量x,然后执行缩进块的语句。

name = ['as','you','me']
for n in name:
    print(n)

# Python提供一个range()函数,可以生成一个整数序列
sum = 0
for x in list(range(101)):
    sum = sum + x
print(sum)

while循环,只要条件满足,就不断循环,条件不满足时退出循环

sum = 0
n = 99
while n > 0:
    sum = sum + n
    n = n - 2
print(sum)

sum = 0
active = True
while active:
    for x in range(11):
        sum = sum + x
    print(sum)
    active = False

break语句退出循环

# 循环打印 当等于12时 提前结束
for x in list(range(20)):
    if x > 12:
        break
    print(x)
print('END')

continue语句,跳过当前的这次循环,直接开始下一次循环。

# 循环打印1-100的偶数
for x in list(range(101)):
    if x != 0 and x%2 == 0:
       print(x)
    else:
        continue

(六) 字典dict 、set(键值对集合)

字典dict

字典dict:使用键-值(key-value)存储,具有极快的查找速度。
dict内部存放的顺序和key放入的顺序是没有关系,dict的key必须是不可变对象
dict特点(用空间来换取时间):
1、查找和插入的速度极快,不会随着key的增加而变慢
2、需要占用大量的内存,内存浪费多。

list特点:
1、查找和插入的时间随着元素的增加而增加;
2、占用空间小,浪费内存很少

people = {'luna':95,'koko':90,'tio':80}
# 如果key不存在 返回false
if 'koko' in people:
    print(people)

# 通过get()判断key是否存在
if people.get('luna') != None:
    print(people.get('luna'))

# 删除key
if people.get('luna') != None:
    people.pop('luna')
    print(people)
    
# 添加
people['momo'] = 65
print(people)

set

一组key的集合,不存储value。由于key不能重复,所以在set中,没有重复的key

# key不重复 无序
sets = set([1,2,3,4,5,3,2])
print(sets)
#添加元素
sets.add(6)
sets.add(5)
print(sets)
# 删除元素
sets.remove(1)
print(sets)
# 无序和无重复元素的集合,可做交集、并集等操作
s1 = set([1,2,3])
s2 = set([2,4,5])
print(s1&s2) # 交集
print(s1|s2) # 并集

函数

(一)调用函数

Python内置了许多有用的函数,可直接调用,可查看官方文档:
Python官方函数文档
可在交互式命令行通过命令[help(abs)]查看[abs]函数的帮助信息。

# 调用函数
# 求绝对值
num1 = -10
print(abs(num1))
# 求最大、小值
nums = [1,2,3,4,5,32,21,23,12]
print(max(nums),min(nums))

(1)数据类型转换:
内置的常用函数包括了数据类型转换函数:

>>> int('123')
123
>>> str(123)
'123'
>>> float('12.21')
12.21

(二)定义函数

(1)定义函数:
使用【def】语句,依次函数名、括号、参数和冒号,在缩进块中写函数体,返回值用【return】返回

# 定义函数返回绝对值
def my_abs(x):
    if x >= 0:
        return x
    else:
        return -x

print(my_abs(-10))

(2)空函数:
定义一个什么事也不做的空函数

def noneFun():
	pass

(3)参数检查:
调用函数时,如果参数个数不对,Python解释器会自动检查出来,并抛出TypeError

# 参数类型做检查
def my_abs_check(x):
    if not isinstance(x,(int,float)):
        raise TypeError('错误类型')
    if x >= 0:
        return x
    else:
        return -x
print(my_abs_check('-1'))

TypeError: 错误类型

(4)返回多个值:
函数可以同时返回多个值,但其实就是一个tuple
定义一个函数quadratic(a, b, c),接收3个参数,返回一元二次方程 ax^2+bx+c=0的两个解

# 一元二次方程的求根
import math
def quadratic(a,b,c):
    d = b ** 2 - 4 * a * c
    if a == 0 or d < 0:
        raise TypeError('无解')
    else:
        x1 = (-b + math.sqrt(d)) / (2 * a)
        x2 = (-b - math.sqrt(d)) / (2 * a)
        return x1, x2

print(quadratic(2,3,1))

(三)函数的参数

(1)位置参数:
power(x, n)函数有两个参数:x和n,这两个参数都是位置参数

# x^2
def power1(x):
    return x * x
# x^n
def power2(x, n):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s

print(power2(2,3))

(2)默认函数:
调用power(5)时,相当于调用power(5, 2)

# 默认参数
def power(x, n=2):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s

print(power(2))

默认参数降低了函数调用的难度,而一旦需要更复杂的调用时,又可以传递更多的参数来实现
默认参数必须指向不变对象

(3)可变参数:
可变参数就是传入的参数个数是可变的,定义可变参数和定义一个list或tuple参数相比,仅在参数前面加了一个*号。
例子:给定一组数字a,b,c……,请计算a2 + b2 + c2 + ……:

# 可变参数
def calc(*numbers):
    sum = 0
    for b in numbers:
        sum = sum + b * b
    return sum

print(calc(1,2,3,4))

# *nums表示把nums这个list的所有元素作为可变参数传进去
nums = [2,4,6]
print(calc(*nums))

(4)关键字参数:
关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。可以扩展函数的功能。

# 关键字参数
def person(name,age,**kw):
    print('name:',name,'age:',age,'other:',kw)

person('luna',14,city='anshun')
person('alice',12,gender='M',job='teacher')

infos = {'city':'guiyang','job':'teacher'}
person('momo',21,**infos)

【**infos】 表示把infos 这个dict的所有key-value用关键字参数传入到函数的【**kw】参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra。

(5)命名关键字参数:
限制关键字参数的名字,就可以用命名关键字参数,命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数。

def person1(name,age,*,city,job):
	print(name,age,city,job)

person1('luna',11,gender='M',city='anshun',job='IT')

(6)参数组合:
Python中定义函数,可用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可组合使用。
参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
任意函数,都可以通过类似func(*args, **kw)的形式调用它。

【*args】是可变参数,args接收的是一个tuple;
【**kw】是关键字参数,kw接收的是一个dict。

def all_fun(a,b,c=0,*args,**kw):
    print('a=',a,'b=',b,'c=',c,'args=',args,'kw=',kw)
def all_fun1(a,b,c=0,*,d,**kw):
    print('a=',a,'b=',b,'c=',c,'d=',d,'kw=',kw)

args = (1,2,3)
kw = {'d':99,'x':'%'}
all_fun(*args,**kw)
all_fun1(*args,**kw)

可变参数既可以直接传入:func(1, 2, 3),又可以先组装listtuple,再通过*args传入:func(*(1, 2, 3));
关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})

(四)递归函数

函数在内部调用自身本身,这个函数就是递归函数。
计算阶乘:

def fact(n):
    if n == 1:
        return 1
    return n * fact(n - 1)

print(fact(3))

高级特性

Python中,代码不是越多越好,而是越少越好。代码不是越复杂越好,而是越简单越好。

(一)切片

取一个list或tuple的部分元素

# --------------列表----------------
List = ['aa','bb','cc','dd']
# 取前2个元素
print(List[0:2]) # 从索引0开始取,直到索引为2为止,不包括索引2
# 如果第一个元素索引为0
print(List[:2])

# 从索引1开始,取2个元素
print(List[1:3])

# 倒数切片 倒数第一个元素的索引是-1
print(List[-1:])

# 0-100的数列
l = list(range(101))
# 取出前十个元素
print(l[0:10])
# 取出后十个元素
print(l[-10:])
# 前11-21的元素
print(l[10:21])
# 前10个数,每隔2个取一个
print(l[:10:2])
# 所有数,每隔9个取1个
print(l[::9])
# 复制list
print(l[:])

# --------------元组-----------------
tup = tuple(range(20))
# 元组前10个元素 结果仍是元组
print(tup[:10])

# 字符串也可以用切片操作
str = 'abcdefg'
print(str[:3])

# ----------去除字符串前后的空格-----------
def my_trim(str):
    while str!='' and str[0] ==' ':
        str = str[1:]
    while str!='' and str[-1] ==' ':
        str = str[:-1]
    return str
# 测试
if my_trim('python ') != 'python':
    print('失败')
elif my_trim(' python') != 'python':
    print('失败')
elif my_trim(' python ') != 'python':
    print('失败')
elif my_trim('') != '':
    print('失败')
else:
    print('成功')

(二) 迭代

给定一个list或tuple,通过for循环来遍历这个list或tuple,这种遍历称为迭代(Iteration),Python中,迭代是通过for … in来完成的。

# 迭代
# 迭代字典
d = {'a':1,'b':2,'c':3}
# 迭代key
for key in d:
    print(key)
# 迭代value
for value in d.values():
    print(value)
# 同时迭代
for k,v in d.items():
    print(k,v)

# 下标循环
for i,v in enumerate(['a','b','c']):
    print(i,v)

# 迭代查找一个list中最小和最大值,并返回一个tuple
def fdMinMax(l):
    if l == []:
        return (None, None)
    maxNum = l[0]
    minNum = l[0]
    for i in l:
        if i > maxNum:
            maxNum = i
        if i < minNum:
            minNum = i
    return (maxNum, minNum)
    
result = fdMinMax([7,1,3,5,8])
print(result)

(三) 列表生成式

# 生成[1x1, 2x2, 3x3, ..., 10x10]
# 一般方法
L = []
for i in range(1, 11):
    L.append(i * i)
print(L)

# 列表生成式
l = [x * x for x in range(1, 11)]
print(l)

# 奇数平方和
l = [x * x for x in range(1, 11) if x % 2 != 0]
print(l)

# 两层循环
l = [x + y for x in 'abc' for y in '123']
print(l)

# 列出当前目录所有文件和目录名
import os

l = [d for d in os.listdir('.')]
print(l)

# 把一个list中所有的字符串变成小写
list = ['Hello', 'Python', 'You']
l = [s.lower() for s in list]
print(l)

# if...else
# 列表生成式,跟在for后面的if是一个筛选条件,不能带else
# 在一个列表生成式中,for前面的if ... else是表达式,而for后面的if是过滤条件,不能带else。
l = [x if x%2 != 0 else -x for x in range(1,11)]
print(l)

# isinstance函数可以判断一个变量是不是字符串
L1 = ['Hello', 'World', 18, 'Apple', None]
l = [s.lower() for s in L1 if isinstance(s,str)==True]
print(l)

(四) 生成器

Python中,这种一边循环一边计算的机制,称为生成器:generator
创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator,通过next()函数获得generator的下一个返回值(通过for循环来迭代它)。

g = (x * x for x in range(10))
for n in g:
    print(n)

# 斐波拉契数列(Fibonacci):除第一个和第二个数外,任意一个数都可由前两个数相加得到
def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'

f = fib(10)
print(f)

# 把fib函数变成generator,只需要把print(b)改为yield b就可
def fib1(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'

f1 = fib1(10)
print(f1)

如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator,是在for循环的过程中不断计算出下一个元素,并在适当的条件结束for循环。对于函数改成的generator来说,遇到return语句或者执行到函数体最后一行语句,就是结束generator的指令,for循环随之结束。

  • 普通函数调用直接返回结果
  • generator函数的“调用”实际返回一个generator对象
# 杨辉三角
def triangles():
    L = [1]
    while True:
        yield L
        L = [1] + [L[i] + L[i + 1] for i in range(len(L) - 1)] + [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循环的数据类型有以下几种:

  • 一类是集合数据类型,如list、tuple、dict、set、str等;
  • 一类是generator,包括生成器和带yield的generator function。
    这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。可以使用isinstance()判断一个对象是否是Iterable对象

生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator。把list、dict、str等Iterable变成Iterator可以使用iter()函数。for循环本质上就是通过不断调用next()函数实现。

函数式编程

函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数。

(一)高阶函数

把函数作为参数传入,这样的函数称为高阶函数。

  • 变量可指向函数
    函数本身可以赋值给变量,即:变量可指向函数
f = abs
print(f(-10))
  • 函数名也是变量
    函数名其实是指向函数的变量

-传入函数
函数的参数可以接收变量,一个函数可以接收另一个函数作为参数,这种函数称为高阶函数。

def sum(a,b,f):
	return f(a)+f(b)

1、map/reduce

map()和reduce()函数。map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

  • map
# map
def f(x):
    return x * x

r = map(f,[1,2,3,4,5])
print(list(r))

map()传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator,Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单f(x)=x2,还可以计算任意复杂的函数。

x = [1,2,3,4,5,6,7]
print(list(map(str,x)))
  • reduce
    reduce把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算。
# reduce
# 对序列求和
def add(x, y):
    return x + y


sum = reduce(add, [1, 2, 3, 4, 5])
print(sum)


# 把序列[1, 2, 3, 4, 5]变换成整数12345
def fn(x, y):
    return x * 10 + y


num = reduce(fn, [1, 2, 3, 4, 5])
print(num)

2、filter

过滤序列,和map()类似,filter()也接收一个函数和一个序列。filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list。

# filter
# list中。删掉偶数,保留奇数
def is_qi(x):
    return x % 2 == 1


l = list(filter(is_qi, [1, 2, 3, 4, 5, 6, 7, 8]))
print(l)

# 序列中空字符串删掉
def not_empty(s):
    return s and s.strip()
s = list(filter(not_empty,['a','','b',None,'c',' ']))
print(s)

3、sorted

sorted()函数就可以对list进行排序,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序。

# 排序算法
# sorted
l = sorted([3, 1, 2, 43, 12])
print(l)

# 按绝对值大小排序
abs = sorted([21, 2, 32, -23, -12], key=abs)
print(abs)

# 忽略大小写,按照字母序排序
st = sorted(['bob', 'Asd', 'Coo', 'dss'], key=str.lower)
print(st)

# 反向排序  reverse=True
s = sorted(['bob', 'Asd', 'Coo', 'dss', 'Ds'], key=str.lower, reverse=True)
print(s)

# 按名字排序
L = [('Bob', 75, 2), ('Adam', 92, 3), ('Bart', 66, 1), ('Lisa', 88, 4)]


# 按名字排序
def sorted_byName(t):
    t = t[0]
    return t


# 按成绩排序
def sorted_byScore(t):
    t = t[1]
    return t


# 按学号排序
def sorted_byNo(t):
    t = t[2]
    return t


L1 = sorted(L, key=sorted_byName)
print(L1)
L2 = sorted(L1, key=sorted_byScore)
print(L2)
L3 = sorted(L2, key=sorted_byNo)
print(L3)

(二)返回函数

把函数作为结果值返回。

# 可变参数求和
def c_sum(*args):
    a = 0
    for i in args:
        a = a + i
    return a


print(c_sum(1, 2, 3, 4))


# 返回求和函数
def return_sumFun(*args):
    def sum():
        a = 0
        for i in args:
            a = a + i
        return a

    return sum


f1 = return_sumFun(1, 2, 3, 4, 5)
f2 = return_sumFun(1, 2, 3, 4, 5)
print(f1 == f2)

print(f1)
print(f1())

闭包:当一个函数返回了一个函数后,其内部的局部变量还被新函数引用

# 利用闭包返回一个计数器函数,每次调用它返回递增整数
def createCounter():
    count = [0]
    def counter():
        count[0] += 1
        return count[0]
    return counter

countFun = createCounter()
print(countFun(),countFun(),countFun())

(三)匿名函数

在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。
【匿名函数】 : lambda x: x * x
关键字lambda表示匿名函数,冒号前面的x表示函数参数,只能有一个表达式,不用写return。匿名函数也是一个函数对象,可以把匿名函数赋值给一个变量,再利用变量来调用该函数。

# lambda
# 计算f(x)=x*x
def f(x):
    return x * x

n = f(2)
print(n)

# 匿名函数
n = lambda x: x * x
print(n(2))

# 匿名函数作为返回值
def bt(x, y):
    return lambda: x * x + y * y

f = bt(1, 3)
print(f())

l = list(filter(lambda n: n % 2 == 1, range(1, 20)))
print(l)

(四) 装饰器

在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator),decorator就是一个返回函数的高阶函数。

def log(func):
    def wrapper(*args, **kw):
        print('call %s():' %func.__name__)
        return func(*args, **kw)
    return wrapper

# @log放到now()函数的定义处,相当于执行了语句:now = log(now)
@log
def now():
    print('2020:2:24')

print(now())

(五) 偏函数

functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。

  • int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换。
# int
print(int('12345'))

# int()   base参数 默认值10 N进制转换
print(int('100', base=8))


# 转二进制字符串
def int2(x, base=2):
    return int(x, base)

print(int2('1000'))

  • functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2。
# 偏函数
import functools

int2 = functools.partial(int, base=2)
print(int2('1000'))

functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

模块

模块

为了编写可维护的代码,把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在Python中,一个.py文件就称之为一个模块(Module)。

  • 【好处】
    提高了代码的可维护性复用性、避免函数名和变量名冲突
    一个a.py的文件就是一个名字叫a的模块,一个b.py的文件就是一个名字叫b的模块。如果a、b模块冲突,可以通过包(package)来组织模块,避免冲突。
mypkg
├─ __init__.py
├─ a.py
└─ b.py

每一个包目录下必须有__init__.py的文件,如果没有,Python就把这个目录当成普通目录,而不是一个包。init.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是mypkg。

  • 【注意】
    1.模块名要遵循Python变量命名规范,不要使用中文、特殊字符;
    2.模块名不要和系统模块名冲突,最好先查看系统是否已存在该模块。

(一)使用模块

1、import、from … import、from … import*、import…as…语句

使用 Python 模块,只需在执行 import 语句:

import module1[, module2[,... moduleN]

from … import 语句:from 语句让你从模块中导入一个指定的部分到当前命名空间中

from modname import name1[, name2[, ... nameN]]

from … import * 语句:把一个模块的所有内容全都导入到当前的命名空间

from modname import *

import…as…语句:模块名替换新的名字

import modname as newmodname

2、__name__属性

一个模块被另一个程序第一次引入时,其主程序将运行。如果我们想在模块被引入时,模块中的某一程序块不执行,我们可以用__name__属性来使该程序块仅在该模块自身运行时执行。

# @/usr/bin/env python3
# -*- coding:utf-8 -*-
# Filename:使用模块.py

if __name__=='__main__':
    print('程序自身运行')
else:
    print('我来自别的模块')

每个模块都有一个__name__属性,当其值是’main’时,表明该模块自身在运行,否则是被引入。

面向对象

面向对象编程

面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。

(一)类和实例

面向对象最重要的概念就是类(Class)和实例(Instance),class后面接着类名,类名首字母大写,所有类都会继承object类。

# 定义类对象
class Student():
    '初始化属性'

    def __init__(self, name, score):
        self.name = name
        self.score = score

	# 数据封装(数据和逻辑被“封装”起) 类方法
    def print_score(self):
        print('%s:%s' % (self.name, self.score))

	def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >= 60:
            return 'B'
        else:
            return 'C'

# 创建实例
a_stu = Student('luna', 98);
a_stu.print_score()
print(a_stu.get_grade())

__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

(二)访问限制

要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。

# 定义对象
class Student():
	# 初始化属性
    def __init__(self, name, score):
        self.__name = name
        self.__score = score

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))
	
	# 获取属性
	def get_name(self):
		return self.__name
	
	def get_score(self):
		return self.__score
	
	# 修改属性
	def set_score(self):
		self.__score = score

外部无法访问 实例.__name和实例.__score了。外部如果想获取name和score,可增加get_name()方法。

变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__、__score__这样的变量名。

(三)继承和多态

定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类。

# 父类
class Animal():
    def run(self):
        print('动物正在跑。。。')

# 子类(继承父类)
class Dog(Animal):
    # 覆盖父类方法 重写父类方法 (多态)
    def run(self):
        print('狗正在跑。。。')

    def eat(self):
        print('狗正在吃。。。')

class Cat(Animal):
    def run(self):
        print('猫正在跑。。。')

    def eat(self):
        print('猫正在吃。。。')

# 实例化
dog = Dog()
dog.run()

cat = Cat()
cat.run()
cat.eat()

子类获得了父类的全部功能。由于Animial实现了run()方法,因此,Dog和Cat作为它的子类,自动拥有了run()方法。当子类和父类都存在相同的run()方法时,子类的run()覆盖(重写)了父类的run(),在代码运行的时候,总是会调用子类的run()。这就是继承好处之一:多态。

# dog不仅仅是Dog,还是Animal
print(isinstance(dog,Dog)) # True
print(isinstance(dog,Animal)) # True
  • 多态:a,b,c类里都有run这个鸭子方法,a,b,c无论是什么类型都是鸭子,可以运行
  • 继承:a自身有run方法,d,c会从继承父类里调用run方法,可以运行
  • 只要在父类里定义了鸭子方法,鸭子是父类和它的子类至于实际调用时子类是用自身的方法还是父类的方法根据子类自己决定
# 父类
class Animal():
    def run(self):
        print('动物正在跑。。。')

# 子类(继承父类)
class Dog(Animal):
    # 覆盖父类方法 重写父类方法 (多态)
    def run(self):
        print('狗正在跑。。。')

    def eat(self):
        print('狗正在吃。。。')

class Cat(Animal):
    def run(self):
        print('猫正在跑。。。')

    def eat(self):
        print('猫正在吃。。。')

def Run_twice(animal):
    animal.run()
    animal.run()

# 实例化
a = Dog()
b = Cat()
c = Animal()

Run_twice(a)
Run_twice(b)
Run_twice(c)

[鸭子类型]:
把类Animal当做鸭子,把调用run函数当做鸭子的走路姿势,只要这个类可以调用run函数,就把它看做鸭子也就是说走路姿势像鸭子,你就是鸭子。

(四)获取对象信息

当拿到一个对象的引用时,如何知道这个对象是什么类型、有哪些方法呢?

1、使用type()

判断对象类型使用type()函数,基本类型都可以用type()判断,使用type()函数,可以获取任何数据的类型。如果要判断一个数据是不是函数,可以使用types模块中定义的常量,如:types.FunctionType、types.LambdaType。

# type判断对象
print(type(111))
print(type('123'))
print(type(None))

# 结果
<class 'int'>
<class 'str'>
<class 'NoneType'>

如果一个变量指向函数或者类,也可以用type()判断
判断一个对象是否是函数:

import types

def fn():
    pass

print(type(fn) == types.FunctionType)
print(type(abs) == types.BuiltinFunctionType)
print(type(lambda x: x) == types.LambdaType)
print(type((x for x in range(10))) == types.GeneratorType)

2、使用isinstance()

对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数,isinstance函数判断的是一个对象是否是该类型或位于该类型的父类继承链上。isinstance还可以判断一个变量是否是某些类型的一种,用元组写多种类型。
继承关系:object -> Animal -> Dog -> Husky

# isinstance()
class Animal():
    pass

class Dog(Animal):
    pass

class Husky(Dog):
    pass

a = Animal()
b = Dog()
c = Husky()

print(isinstance(c, Husky))  # True
print(isinstance(c, Dog))  # True
print(isinstance(c, Animal))  # True

能用type()判断的基本类型也可以用isinstance()判断,优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”。

3、使用dir()

1.如果要获得一个对象全部的属性和方法,可以使用dir()函数。它返回一个包含 字符串的list。
2.类似“xxx”的属性和方法在Python中都是有特殊用途的,比如len()函数获取对象的长度。但实际上,在len函数内部它会去自动调用对象的__len__()方法,所以,你可以在类中自己设定一个__len__()方法,让len返回你想要返回的长度。

4、操作一个对象状态

getattr() 获取、setattr() 设置 和hasattr() 有没有 方法,可以直接操作一个对象的状态。

hasattr(obj,'x') #有属性‘x’吗?
setattr(obj,'y',18) #设置一个属性‘y’,值为18.
getattr(obj,'y') #获取属性y
getattr(obj,'z',404) #获取属性z,如果不存在,就返回默认值404.

(五)实例属性和类属性

Python是动态语言,根据类创建的实例可以任意绑定属性,给实例绑定属性的方法是通过实例变量,或者通过self变量:
编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。

class Student():
    def __init__(self):
        self.name = name


s = Student('Tom')
s.score = 90
  • 实例属性属于各个实例所有,互不干扰;
  • 类属性属于类所有,所有实例共享一个属性;
  • 不要对实例属性和类属性使用相同的名字,否则将产生难以发现的错误。

面向对象高级编程

(一)使用__slots__

当定义了一个class,创建了一个class的实例后,可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。
正常情况

from types import MethodType

# 定义class
class Student():
	pass
	
def set_age(self,age):
		self.age = age
# 给实例绑定属性
s = Student()
s.name = 'luna'
print(s.name)

# 给实例绑定方法
s.set_age = MethodType(set_age,s)
# 调用实例方法
s.set_age(25)
print(s.age)

# 给class绑定方法
def set_score(self,score):
	self.score = score

Student.set_score = set_score # 调用
# Student.set_score=MethodType(set_score,Student) 给类绑定方法
s.set_score(100)
print(s.score)

通常情况下,上面的set_score方法可以直接定义在class中,但动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。

【使用__slots__】
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

class Student():
	__slots__ = ('name','age') # 用元组定义允许绑定的属性名称

s = Student() # 创建实例
s.name = 'luna' # 绑定属性
s.age = 13
s.score = 90

由于’score’没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

(二) 使用@property

Python内置的@property装饰器就是负责把一个方法变成属性调用。

class Student():

    @property # 只读属性
    def score(self):
        return self._score

    @score.setter  # 可读写属性
    def score(self,value):
        if not isinstance(value,int):
            raise ValueError('必须是整数类型')
        if value < 0 or value > 100:
            raise ValueError('必须是0-100之间')
        self._score = value

s = Student()
s.score = 60
print(s.score)

s.score = 101
print(s.score)

(三)多重继承

继承是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能。

# 通过多重继承,一个子类就可以同时获得多个父类的所有功能
class Animal():
    pass

# 大类
class Mammal(Animal):
    pass

class Bird(Animal):
    pass

# 功能类
class Runnable():
    def run(self):
        print('running...')

class Flyable():
    def fly(self):
        print('flying...')

# 各种动物
class Dog(Mammal,Runnable):
    pass
class Parrot(Bird,Flyable):
    pass

MixIn
MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系,不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。

(四)定制类

__str__:返回用户看到的字符串

# __str__
class Student():
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return 'Student Object(name:%s)' % self.name


print(Student('luna'))

__iter__:一个类想被用于for … in循环,类似list或tuple那样,就必须实现一个__iter__()方法,返回一个迭代对象,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

# __str__
class Student():
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return 'Student Object(name:%s)' % self.name


print(Student('luna'))


# __iter__
class Fib():
    def __init__(self):
        self.a, self.b = 0, 1

    def __iter__(self):
        return self

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        if self.a > 1000:
            raise StopIteration()
        return self.a

    def __getitem__(self, n):
        if isinstance(n, int):  # n是序列
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice):  # n是切片
            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


for n in Fib():
    print(n)

f = Fib()
print(f[3])

# 切片
print(f[0:3])

如果把对象看成dict,__getitem__()的参数也可能是一个可以作key的object,例如str。与之对应的是__setitem__()方法,把对象视作list或dict来对集合赋值。最后,还有一个__delitem__()方法,用于删除某个元素。

__getattr__:正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错
调用不存在的属性时,比如score,Python解释器会试图调用__getattr__(self, 'score')来尝试获得属性,这样,我们就有机会返回score的值。

# __getattr__
class Animal():
    def __init__(self,name):
        self.name = name

    def __getattr__(self, item):
        if item == 'score':
            return '0' # 也可以返回函数

a = Animal('koko')
print(a.name)
print(a.score)

只有在没有找到属性的情况下,才调用__getattr__,已有的属性,比如name,不会在__getattr__中查找。

完全动态的__getattr__,写出一个链式调用:

class Chain():
    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().status.user.timeline.list)

__call__:任何类,只需要定义一个__call__()方法,就可以直接对实例进行调用

class Chain():
    def __init__(self,path=''):
        self._path = path

    def __getattr__(self, path):
        return Chain('%s/%s'%(self._path,path))

    def __call__(self, path):
        return  Chain('%s/%s'%(self._path,path))

    def __str__(self):
        return self._path

    __repr__ = __str__

print(Chain().status.user.timeline.list)
print(Chain().users('miko').repos)

分解 Chain().users('miko').repos

# Chain().users('michael').repos 分解
urls = Chain()    # 初始化一个实例
urls = urls.users    # 查找实例的一个属性
urls = urls('michael)    # 调用一个函数
urls = urls.repos    # 还是实例的属性

1、第一步

urls = Chain()

初始化一个实例,此时urls等于,因为定义了默认值path=''

2、第二步4

urls = urls.users

查找urls的属性users,没找到定义的属性,那就调用__getattr__方法,返回了一个函数调用:

def __getattr__(self, users):
    return Chain('%s/%s' % (self.__path, users))

这一步调用了Chain(),而且把要查找的属性users作为参数传递了进去,也就是Chain(users),那么根据Chain()的逻辑,最后返回的是:/users,然后跟上一步的结果拼接,最终返回:/users
3、第三步

urls = urls('michael')

对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。
你把对象看成函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限。 原来是为了让实例化对象和函数一样可以被使用。
然后调用urls = urls('michael'),那么最终返回:/users/michael
4、第四步

urls = u.repos

和第二步没什么区别,所以urls最为:/users/michael/repos;

(五)使用枚举类

枚举类型可以看作是一种标签或是一系列常量的集合,通常用于表示某些特定的有限集合,例如星期、月份、状态等。
一般就通过字典或类来实现:

使用 Enum

# 使用枚举
from enum import Enum


class Color(Enum):
    RED = 1
    GREEN = 2
    YELLOW = 3


# 枚举成员有值(默认可重复)、枚举类型不可实例化,不可更改
print(Color.GREEN)
print(repr(Color.GREEN))
print(type(Color.GREEN))
print(isinstance(Color.GREEN, Color))
Color.GREEN
<Color.GREEN: 2>
<enum 'Color'>
True、

定义枚举

1、定义枚举时,成员名不能重复
2、成员值允许相同,第二个成员的名称被视作第一个成员的别名
3、若要不能定义相同的成员值,可以通过 unique 装饰

# 定义枚举
class Code(Enum):
    GA = 1
    GB = 2
    GG = 3
    GX = 2 # 成员值允许相同
print(Code.GA.value)

@unique #能定义相同的成员值
class Code2(Enum):
    GA = 1
    GB = 2
    GG = 3
print(Code2.GB.value)

枚举取值

通过成员名来获取成员也可以通过成员值来获取成员:

# 枚举取值
# 获取成员
print(Code2['GA'])  # Code2.GA 通过成员名来获取成员
print(Code2(1))  # Code2.GA  通过成员值来获取成员
# 成员属性和值
c = Code2.GA
print(c.name)  # GA
print(c.value)  # 1
# 支持迭代的方式遍历成员,按定义的顺序,如果有值重复的成员,只获取重复的第一个成员
for cd in Code:
    print(cd)
# 特殊属性 __members__ 是一个将名称映射到成员的有序字典,也可以通过它来完成遍历
for code in Code.__members__.items():
    print(code)
('GA', <Code.GA: 1>)
('GB', <Code.GB: 2>)
('GG', <Code.GG: 3>)
('GX', <Code.GB: 2>)

枚举比较

枚举的成员可以通过 is 同一性比较或通过 == 等值比较,枚举成员不能进行大小比较:

# 枚举比较
print(Code.GA is Code.GA)
print(Code.GA is not Code.GB)
print(Code.GA ==Code.GA)
print(Code.GA != Code.GB)

扩展枚举 IntEnum

IntEnum 是 Enum 的扩展,不同类型的整数枚举也可以相互比较

from enum import IntEnum
class A(IntEnum):
    a = 1
    b = 2

class B(IntEnum):
    a = 1
    b = 1

print(A.a == 1)
print(A.a < 3)
print(A.a == b.A)
print(A.a < B.b)

错误、调试和测试

(一)错误处理

在程序运行的过程中,如果发生了错误,可以事先约定返回一个错误代码,这样,就可以知道是否有错,以及出错的原因。所以高级语言通常都内置了一套try...except...finally...的错误处理机制:

try

# try
try:
    print("try...")
    n = 20 / 0
    print("result:", n)
except ZeroDivisionError as e:
    print("except:", e)
finally:
    print("finally...")
print('END')
try...
except: division by zero
finally...
END

当某些代码可能会出错时,就可用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。Python的错误其实也是class,所有的错误类型都继承自BaseException,所以在使用except时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”。

使用try...except捕获错误还有一个巨大的好处,就是可以跨越多层调用,比如函数main()调用f()f()调用b(),结果b()出错了,这时,只要main()捕获到了,就可以处理。不需要在每个可能出错的地方去捕获错误,只要在合适的层次去捕获错误就可以。

def f(s):
    return 10 / int(s)

def b(s):
    return f(s) * 2

def main():
    try:
        b('0')
    except Exception as e:
        print('Error:',e)
    finally:
        print('finally...')

main()

调用栈

如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。出错的时候,一定要分析错误的调用栈信息,才能定位错误的位置。

记录错误

如果不捕获错误,自然可以让Python解释器来打印出错误堆栈,但程序也被结束了。既然我们能捕获错误,就可以把错误堆栈打印出来,然后分析错误原因,同时,让程序继续执行下去。
Python内置的logging模块可以非常容易地记录错误信息:

# err_logging.py
import logging
def f(s):
    return 10 / int(s)

def b(s):
    return f(s) * 2

def main():
    try:
        b('0')
    except Exception as e:
        logging.exception(e)

main()
print('END')
ERROR:root:division by zero
Traceback (most recent call last):
  File "demo.py", line 40, in main
    b('0')
  File "demo.py", line 36, in b
    return f(s) * 2
  File "demo.py", line 33, in f
    return 10 / int(s)
ZeroDivisionError: division by zero
END

通过配置,logging可以把错误记录到日志文件里。

抛出错误

因为错误是class,捕获一个错误就是捕获到该class的一个实例。如果要抛出错误,首先根据需要,可以定义一个错误的class,选择好继承关系,然后,用raise语句抛出一个错误的实例。

class FoErr(ValueError):
    pass

def fo(s):
    n = int(s)
    if n == 0:
        raise FoErr('invalid value:%s' % s)
    return 10 / n

fo(0)
Traceback (most recent call last):
  File "demo.py", line 62, in <module>
    fo(0)
  File "demo.py", line 58, in fo
    raise FoErr('invalid value:%s' % s)
__main__.FoErr: invalid value:0

(二)调试

程序有Bug,需要一整套调试程序的手段来修复Bug。

  • 第一种:print()把可能出问题的变量打出来
def foo(s):
    n = int(s)
    print('n = %d' % n)
    return 10 / n

def main():
    foo('0')

main()

执行后在输出中查找打印的变量值:

n = 0
Traceback (most recent call last):
	...
ZeroDivisionError: division by zero

不建议使用print()

  • 第二种:断言 assert
    assert的意思是,表达式n != 0应该是True,否则,根据程序运行的逻辑,后面的代码肯定会出错。
    如果断言失败,assert语句本身就会抛出AssertionError
def foo(s):
    n = int(s)
    assert n != 0, 'n = 0'
    return 10 / n

def main():
    foo('0')

main()
Traceback (most recent call last):
  ...
AssertionError: n = 0

启动Python解释器时可以用-O参数来关闭assert,关闭后,所有的assert语句当成pass来看。

  • 第三种:logging
    logging不会抛出错误,而且可以输出到文件
import logging

logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info('n=%d' % n)
print(10 / n)
INFO:root:n=0
Traceback (most recent call last):
  File "err.py", line 31, in <module>
    print(10 / n)
ZeroDivisionError: division by zero

logging的好处,允许你指定记录信息的级别,有debug,info,warning,error等几个级别。可以放心地输出不同级别的信息,不用删除,最后统一控制输出哪个级别的信息。通过简单的配置,一条语句可以同时输出到不同的地方,比如console和文件。

  • 第四种:IDE设置断点

单元测试

单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
unittest模块:提供一整套的测试框架单元测试框架

  • 测试脚手架
    test fixture 表示为了开展一项或多项测试所需要进行的准备工作,以及所有相关的清理操作。举个例子,这可能包含创建临时或代理的数据库、目录,再或者启动一个服务器进程。

  • 测试用例
    一个测试用例是一个独立的测试单元。它检查输入特定的数据时的响应。 unittest 提供一个基类: TestCase ,用于新建测试用例。

  • 测试套件
    test suite 是一系列的测试用例,或测试套件,或两者皆有。它用于归档需要一起执行的测试。

  • 测试运行器(test runner)
    test runner 是一个用于执行和输出测试结果的组件。这个运行器可能使用图形接口、文本接口,或返回一个特定的值表示运行测试的结果。

1、TestCase类:
在这里插入图片描述
多函数多用例测试:

import unittest  # 导入测试模块


def showMsg(msg):
    return '%s' % msg


def do_divdie(a, b):
    return a / b


def showTrue(flag):
    return flag


class TestSomeFunc(unittest.TestCase):  # 自定义测试类 继承unittest.TestCase
    def testrun(self):  # 自定义测试方法
        self.assertEqual('MC', showMsg('MC'))
        self.assertNotEqual('OK', showMsg('NO'))
        self.assertTrue(do_divdie(1, 2))  # 测试结果值是否为True
        self.assertIs(showTrue(False), False)  # 测试预期值与函数结果是否一致
        self.assertIs(int(do_divdie(1, 2)), 1)  # 测试预期值与函数结果是否一致


if __name__ == '__main__':
    unittest.main()  # 调用mian方法自动执行测试用例

测试结果:

F																# 出错信息提醒
======================================================================
FAIL: testrun (__main__.TestSomeFunc)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "单元测试.py", line 22, in testrun		# 22行代码,执行结果和预期不一致
    self.assertIs(int(do_divdie(1, 2)), 1)
AssertionError: 0 is not 1							#结果0不符合预期值1

----------------------------------------------------------------------
Ran 1 test in 0.000s									# 测试所用时间

FAILED (failures=1)									#一项测试失败

文档测试

doctest对所编写的代码当做文档字符串进行读取,然后通过相应的测试规则,对所编写的代码进行自动测试。doctest不但可以用来测试,还可以直接作为示例代码。通过某些文档生成工具,就可以自动把包含doctest的注释提取出来。用户看文档的时候,同时也看到了doctest

  • 1、嵌入代码测试
class Dict(dict):
    '''
    Simple dict but also support access as x.y style.

    >>> d1 = Dict()
    >>> d1['x'] = 100
    >>> d1.x
    100
    >>> d1.y = 200
    >>> d1['y']
    200
    >>> d2 = Dict(a=1, b=2, c='3')
    >>> d2.c
    '3'
    >>> d2['empty']
    Traceback (most recent call last):
        ...
    KeyError: 'empty'
    >>> d2.empty
    Traceback (most recent call last):
        ...
    AttributeError: 'Dict' object has no attribute 'empty'
    '''
    def __init__(self, **kw):
        super(Dict, self).__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Dict' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

if __name__=='__main__':
    import doctest
    doctest.testmod()

什么输出也没有。说明编写的doctest运行都是正确的。将数字200加上' ',就会报错:

**********************************************************************
File "文档测试.py", line 10, in __main__.Dict
Failed example:
    d1['y']
Expected:
    200
Got:
    '200'
**********************************************************************
1 items had failures:
   1 of   9 in __main__.Dict
***Test Failed*** 1 failures.

IO编程

IO在计算机中指Input/Output,也就是输入和输出。由于程序和运行时数据是在内存中驻留,由CPU这个超快的计算核心来执行,涉及到数据交换的地方,通常是磁盘、网络等,就需要IO接口。

IO编程中,Stream(流)是一个很重要的概念,可以把流想象成一个水管,数据就是水管里的水,但是只能单向流动。Input Stream就是数据从外面(磁盘、网络)流进内存,Output Stream就是数据从内存流到外面去。

  • 存在问题:输入和接收速度不匹配
  • 同步IO:CPU等着,也就是程序暂停执行后续代码,等100M的数据在10秒后写入磁盘,再接着往下执行(好了叫我)。
  • 异步IO:CPU不等待,只是告诉磁盘,慢慢写,不着急,接着干别的事去了,于是,后续代码可以立刻接着执行(好了没好了没)

同步和异步的区别就在于是否等待IO执行的结果

文件读写

读文件

要以读文件的模式打开一个文件对象,使用Python内置的open()函数,传入文件名和标示符,标示符'r'表示读:

f = open('D:/code/PycharmProjects/PythonStudy/venv/Include/高级特性/迭代器.py','r')
print(f.read())
f.close()

如果文件不存在,open()函数就会抛出一个IOError的错误,并且给出错误码和详细的信息告诉你文件不存在,文件打开成功,接下来,调用read()方法可以一次读取文件的全部内容。最后一步是调用close()法关闭文件,文件使用完毕后必须关闭,因为文件对象会占用操作系统的资源,并且操作系统同一时间能打开的文件数量也是有限的。
由于文件读写时都有可能产生IOError,一旦出错,后面的f.close()就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,我们可以使用try ... finally来实现。

try:
    f = open('D:/code/PycharmProjects/PythonStudy/venv/Include/高级特性/迭代器.py', 'r')
    print(f.read())
finally:
    if f:
        f.close()

这么写太繁琐,所以,Python引入了with语句来自动帮我们调用close()方法:

# with
with open('D:/code/PycharmProjects/PythonStudy/venv/Include/高级特性/迭代器.py') as f:
    print(f.read())

和前面的try ... finally是一样的,但是代码更佳简洁,并且不必调用f.close()方法。
调用read()会一次性读取文件的全部内容,如果文件有10G,内存就爆了,所以,要保险起见,可以反复调用read(size)方法,每次最多读取size个字节的内容。另外,调用readline()可以每次读取一行内容,调用readlines()一次读取所有内容并按行返回list。因此,要根据需要决定怎么调用。如果文件很小,read()一次性读取最方便;如果不能确定文件大小,反复调用read(size)比较保险;如果是配置文件,调用readlines()最方便:

with open('D:/code/PycharmProjects/PythonStudy/venv/Include/高级特性/迭代器.py') as f:
    # readlines()
    for line in f.readlines():
        print(line.strip())

  • file-like:在Python中统称为file-like Object。除了file外,还可以是内存的字节流,网络流,自定义流等等。file-like Object不要求从特定类继承,只要写个read()方法就行。StringIO就是在内存中创建的file-like Object,常用作临时缓冲。
  • 二进制文件:读取二进制文件,比如图片、视频等等,用'rb'模式打开文件:
f = open('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程/wallpaper_5242054.jpg','rb')
f.read()
  • 字符编码:要读取非UTF-8编码的文本文件,需要给open()函数传入encoding参数,例如,读取GBK编码的文件:
f = open('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程/gbk.txt', 'r', encoding='gbk')
f.read()

遇到有些编码不规范的文件,可能会遇到UnicodeDecodeError,因为在文本文件中可能夹杂了一些非法编码的字符。遇到这种情况,open()函数还接收一个errors参数,表示如果遇到编码错误后如何处理。最简单的方式是直接忽略:

f = open('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程/gbk.txt', 'r', encoding='gbk',errors='ignore')

写文件

写文件和读文件是一样的,唯一区别是调用open()函数时,传入标识符'w''wb'表示写文本文件或写二进制文件:

f = open('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程/io.txt', 'w')
f.write('hello world!!!')
f.close()

可以反复调用write()来写入文件,但是务必要调用f.close()来关闭文件。当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。只有调用close()方法时,操作系统才保证把没有写入的数据全部写入磁盘。忘记调用close()的后果是数据可能只写了一部分到磁盘,剩下的丢失了。所以,还是用with语句来得保险:

with open('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程/io.txt', 'w') as f:
    f.write('hi python!!!')

要写入特定编码的文本文件,请给open()函数传入encoding参数,将字符串自动转换成指定编码。'w’模式写入文件时,如果文件已存在,会直接覆盖,传入'a'以追加(append)模式写入。
在这里插入图片描述

(二)StringIO和BytesIO

StringIO

StringIO:内存中读写str

from io import StringIO

# 内存写入
f = StringIO()
print(f.write('hello'))
print(f.write(' '))
print(f.write('world!'))
print(f.getvalue())  # getvalue()方法用于获得写入后的str

读取StringIO,可以用一个str初始化StringIO,然后,像读文件一样读取:

# 内存读取
f = StringIO('Hello Python!')
while True:
    s = f.readline()
    if s == '':
        break
    print(s.strip())

BytesIO

StringIO操作的只能是str,如果要操作二进制数据,就需要使用BytesIO

from io import BytesIO

f = BytesIO()
f.write('中文'.encode('utf-8')) # 不是str,是经过UTF-8编码的bytes
print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87'
# 读取
f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
print(f.read())

(三)操作文件和目录

Python内置的os模块也可以直接调用操作系统提供的接口函数。如果是posix,说明系统是LinuxUnixMac OS X,如果是nt,就是Windows系统。os模块的某些函数是跟操作系统相关的

import os

print(os.name)  # 操作系统类型
print(os.uname())  # 详细系统信息 windows不提供

环境变量

在操作系统中定义的环境变量,全部保存在os.environ这个变量中,可以直接查看:

print(os.environ)
environ({'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\Administrator\\AppData\\Roaming', 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', 'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files', 'COMMONPROGRAMW6432': 'C:\\Program Files\\Common Files', 'COMPUTERNAME': 'LENOVO-PC', 'COMSPEC': 'C:\\Windows\\system32\\cmd.exe', 'FP_NO_HOST_CHECK': 'NO', 'HOMEDRIVE': 'C:', 'HOMEPATH': '\\Users\\Administrator', 'IDEA_INITIAL_DIRECTORY': 'D:\\PyCharm 2019.2.3\\bin', 'JAVA_HOME': 'D:\\Program Files\\Java\\jdk-9.0.4', 'LOCALAPPDATA': 'C:\\Users\\Administrator\\AppData\\Local', 'LOGONSERVER': '\\\\LENOVO-PC', 'NODE_PATH': 'D:\\Program Files\\nodejs\\node_global\\node_modules', 'NUMBER_OF_PROCESSORS': '6', 'OS': 'Windows_NT', 'PATH': ...)

获取某个环境变量的值,可以调用os.environ.get('key'):

print(os.environ.get('PATH'))
D:\code\PycharmProjects\PythonStudy\venv\Scripts;D:\app\Administrator\product\11.2.0\dbhome_1\bin;C:\ProgramData\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;D:\Python37;C:\Program Files\MySQL\MySQL Server 5.7\bin;D:\Program Files\Java\jdk-9.0.4\bin;D:\Program Files (x86)\Microsoft SQL Server\100\Tools\Binn\;D:\Program Files\Microsoft SQL Server\100\Tools\Binn\;D:\Program Files\Microsoft SQL Server\100\DTS\Binn\;C:\Program Files\Bandizip\;D:\Program Files\nodejs\node_global;C:\Users\Administrator\AppData\Roaming\npm\node_modules\yarn\bin;D:\Program Files (x86)\Yarn\bin

操作文件和目录

操作文件和目录的函数一部分放在os模块中,一部分放在os.path模块中。

# 创建和删除目录
# 查看当前目录的绝对路径
print(os.path.abspath('.'))
# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来:
print(os.path.join('D:\code\PycharmProjects\PythonStudy\venv\Include\IO编程', 'testDir'))
# 创建一个目录:
os.mkdir('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程/testDir')
# 删除一个目录
os.rmdir('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程/testDir')

把两个路径合成一个时,不要直接拼字符串,而要通过os.path.join()函数,这样可以正确处理不同操作系统的路径分隔符。在Linux/Unix/Mac下,os.path.join()返回这样的字符串:part-1/part-2Windows下会返回这样的字符串:part-1\part-2。拆分路径时,也不要直接去拆字符串,而要通过os.path.split()函数,这样可以把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名。os.path.splitext()可以直接让你得到文件扩展名。合并、拆分路径的函数并不要求目录和文件要真实存在,它们只对字符串进行操作。

# 拆分(后一部分总是最后级别的目录或文件名)
print(os.path.split('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程/io.txt'))
# 获取文件扩展名
print(os.path.splitext('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程/io.txt'))

shutil模块提供了copyfile()的函数,你还可以在shutil模块中找到很多实用函数,它们可以看做是os模块的补充。
Python的特性来过滤文件:

# Python的特性来过滤文件 列出所有.py文件
l = [x for x in os.listdir('D:/code/PycharmProjects/PythonStudy/venv/Include/IO编程') if
     os.path.isfile(x) and os.path.splitext(x)[1] == '.py']
print(l)

(四)序列化

程序运行的过程中,所有的变量都是在内存中。定义一个dict:

d = dict(name = 'Tom',age = 19 ,score = 90)

可以随时修改变量,但是一旦程序结束,变量所占用的内存就被操作系统全部回收。如果没有把修改后的变量存储到磁盘上,下次重新运行程序,变量又被初始化修改前的变量。把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling,序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling

Python提供了pickle模块来实现序列化

import pickle

d = dict(name='tom', age=18, score=90)
# 序列化
p = pickle.dumps(d)
print(p)

b'\x80\x04\x95$\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x04name\x94\x8c\x03tom\x94\x8c\x03age\x94K\x12\x8c\x05score\x94KZu.'

pickle.dumps()方法把任意对象序列化成一个bytes,然后就可以把这个bytes写入文件。或者用另一个方法pickle.dump()直接把对象序列化后写入一个file-like Object

# dump
f = open('D:/PycharmProjects/untitled/venv/Include/IO编程/dump.txt', 'wb')
pickle.dump(d,f)
f.close()

当要把对象从磁盘读到内存时,可以先把内容读到一个bytes,然后用pickle.loads()方法反序列化出对象,也可以直接用pickle.load()方法从一个file-like Object中直接反序列化出对象:

# 读取
f = open('D:/PycharmProjects/untitled/venv/Include/IO编程/dump.txt', 'rb')
d = pickle.load(f)
f.close()
print(d)

JSON

要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式。JSON表示出来就是一个字符串,可以被所有语言读取,也可以方便地存储到磁盘或者通过网络传输。JSON表示的对象就是标准的JavaScript语言的对象,JSONPython内置的数据类型对应如下:

JSON类型Python类型
{}dict
[]list
“String”str
1234.56int\float
true/falseTrue\False
nullNone
import json

d = dict(name='tom', age=20, score=90)
s = json.dumps(d)
print(s)

dumps()方法返回一个str,内容就是标准的JSON。类似的,dump()方法可以直接把JSON写入一个file-like Object。要把JSON反序列化为Python对象,用loads()或者对应的load()方法,前者把JSON的字符串反序列化,后者从file-like Object中读取字符串并反序列化:

json_str = '{"age":20,"score":99,"name":"Tom"}'
str = json.loads(json_str)
print(str)

JSON标准规定JSON编码是UTF-8,所以我们总是能正确地在Python的strJSON的字符串之间转。

JSON进价

Python的dict对象可以直接序列化为JSON{},不过,很多时候,我们更喜欢用class表示对象,比如定义Student类,然后序列化。默认情况下,dumps()方法不知道如何将Student实例变为一个JSON{}对象。可选参数default就是把任意一个对象变成一个可序列为JSON的对象,我们只需要为Student专门写一个转换函数,再把函数传进去即可。

import json

class Student():
    def __init__(self, name, age, score):
        self.name = name
        self.age = age
        self.score = score


def studentDict(std):
    return {
        'name': std.name,
        'age': std.age,
        'score': std.score
    }


s = Student('Tom', 14, 89)
print(json.dumps(s, default=studentDict))

# 任意class的实例变为dict
print(json.dumps(s, default=lambda obj: obj.__dict__))

通常class实例都有一个__dict__属性,它就是一个dict,用来存储实例变量。

进程和线程

Python既支持多进程,又支持多线程。线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。

进程

一个程序的执行实例就是一个进程。每一个进程提供执行程序所需的所有资源。(进程本质上是资源的集合)每一个进程启动时都会最先产生一个线程,即主线程。然后主线程会再创建其他的子线程。

线程

操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一个线程是一个execution context(执行上下文),即一个cpu执行时所需要的一串指令。

进程:打开word
线程:在word里打字、拼写、打印

进程与线程区别

1.同一个进程中的线程共享同一内存空间,但是进程之间是独立的。
2.同一个进程中的所有线程的数据是共享的(进程通讯),进程之间的数据是独立的。
3.对主线程的修改可能会影响其他线程的行为,但是父进程的修改(除了删除以外)不会影响其他子进程。
4.线程是一个上下文的执行指令,而进程则是与运算相关的一簇资源。
5.同一个进程的线程之间可以直接通信,但是进程之间的交流需要借助中间代理来实现。
6.创建新的线程很容易,但是创建新的进程需要对父进程做一次复制。
7.一个线程可以操作同一进程的其他线程,但是进程只能操作其子进程。
8.线程启动速度快,进程启动速度慢(但是两者运行速度没有可比性)。

多任务的实现有3种方式:

  • 多进程模式;
  • 多线程模式;
  • 多进程+多线程模式。

(一)多进程

Python程序实现多进程(multiprocessing),先了解操作系统的相关知识:

Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊。普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID

Python的os模块封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松创建子进程:

import os

print('Process (%s) start...' % os.getpid())
# Only works on Unix/Linux/Mac:
pid = os.fork()
if pid == 0:
    print('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
else:
    print('I (%s) just created a child process (%s).' % (os.getpid(), pid))
Process (876) start...
I (876) just created a child process (877).
I am child process (877) and my parent is 876.

Windows没有fork调用,上面的代码在Windows上无法运行。有了fork调用,一个进程在接到新任务时就可以复制出一个子进程来处理新任务,常见的Apache服务器就是由父进程监听端口,每当有新的http请求时,就fork出子进程来处理新的http请求。

1 、multiprocessing

multiprocessing模块就是跨平台版本的多进程模块,提供了一个Process类来代表一个进程对象:

from multiprocessing import Process
import os


# 子进程需要执行的代码
def run_proc(name):
    print('Run子进程 %s(%s)...' % (name, os.getpid()))


if __name__ == '__main__':
    print('父进程 %s.' % os.getpid())
    p = Process(target=run_proc, args=('test',))
    print('子进程将启动.')
    p.start()
    p.join()
    print('子进程结束.')

创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动,这样创建进程比fork()还要简单。join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。

2、Pool

如果要启动大量的子进程,可以用进程池的方式批量创建子进程:

from multiprocessing import Pool
import os, time, random


def long_time_task(name):
    print('任务启动%s(%s)...' % (name, os.getpid()))
    start = time.time()
    time.sleep(random.random() * 3)
    end = time.time()
    print('任务 %s 启动 %0.2f 秒.' % (name, (end - start)))


if __name__ == '__main__':
    print('父进程%s.' % os.getpid())
    p = Pool(4)
    for i in range(5):
        p.apply_async(long_time_task, args=(i,))
    print('等待所有进程执行完毕...')
    p.close() # 调用close()之后就不能继续添加新的Process
    p.join() # 调用join()方法会等待所有子进程执行完毕 调用join()之前必须先调用close()
    print('所有进程执行完毕.')

父进程7248.
等待所有进程执行完毕...
任务启动0(6012)...
任务启动1(5324)...
任务启动2(8176)...
任务启动3(6556)...
任务 1 启动 0.50 秒.
任务启动4(5324)...
任务 3 启动 1.67 秒.
任务 0 启动 2.43 秒.
任务 4 启动 1.99 秒.
任务 2 启动 2.57 秒.
所有进程执行完毕.

任务 0、1、2、3立刻执行,而任务4等前四个任务完成后才执行,任务 4要等待前面某个任务完成后才执行,Pool(4)默认是4,最多执行4个进程,Pool的默认大小是CPU的核数。

3 、子进程

很多时候,子进程并不是自身,而是一个外部进程。创建了子进程后,还需控制子进程的输入和输出。subprocess模块可以让我们非常方便地启动一个子进程,然后控制其输入和输出。

import subprocess

print('$ nslookup www.python.org')
r = subprocess.call(['nslookup', 'www.python.org'])
print('Exit code:', r)

$ nslookup www.python.org
Server:		192.168.19.4
Address:	192.168.19.4#53

Non-authoritative answer:
www.python.org	canonical name = python.map.fastly.net.
Name:	python.map.fastly.net
Address: 199.27.79.223

Exit code: 0

如果子进程还需要输入,则可以通过communicate()方法输入:

import subprocess

print('$ nslookup')
p = subprocess.Popen(['nslookup'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, err = p.communicate(b'set q=mx\npython.org\nexit\n')
print(output.decode('utf-8'))
print('Exit code:', p.returncode)
$ nslookup
Server:		192.168.19.4
Address:	192.168.19.4#53

Non-authoritative answer:g/h'
g
python.org	mail exchanger = 50 mail.python.org.

Authoritative answers can be found from:
mail.python.org	internet address = 82.94.164.166
mail.python.org	has AAAA address 2001:888:2000:d::a6


Exit code: 0

4 、进程间通信

进程之间肯定是需要通信的,操作系统提供了很多机制来实现进程间的通信。Python的multiprocessing模块包装了底层的机制,提供了QueuePipes等多种方式来交换数据。
Queue为例,在父进程中创建两个子进程,一个往Queue里写数据,一个从Queue里读数据:

from multiprocessing import Process, Queue
import os, time, random


# 写数据进程执行代码
def write(q):
    print('进程 写:%s' % os.getpid())
    for value in ['a', 'b', 'c']:
        print('添加 %s 到 Queue...' % value)
        q.put(value)
        time.sleep(random.random())


# 读数据进程执行的代码
def read(q):
    print('进程 读: %s' % os.getpid())
    while True:
        value = q.get(True)
        print('读取 %s 从 Queue...' % value)


if __name__ == '__main__':
    # 父进程创建Queue,并传给子进程
    q = Queue()
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    # 启动子进程pw
    pw.start()
    # 启动子进程pr
    pr.start()
    # 等待结束
    pw.join()
    # pr进程死循环,强制终止
    pr.terminate()

进程 写:5576
添加 a 到 Queue...
进程 读: 7232
读取 a 从 Queue...
添加 b 到 Queue...
读取 b 从 Queue...
添加 c 到 Queue...
读取 c 从 Queue...

Unix/Linux下,multiprocessing模块封装了fork()调用,不需要关注fork()的细节。Windows没有fork调用,因此
multiprocessing需要模拟出fork的效果,父进程所有Python对象都必须通过pickle序列化再传到子进程去。

  • Unix/Linux下,可以使用fork()调用实现多进程。
  • 实现跨平台的多进程,使用multiprocessing模块。
  • 进程间通信是通过QueuePipes等实现的。

(二)多线程

多任务可以由多进程完成,也可以由一个进程内的多线程完成。线程是操作系统直接支持的执行单元,因此,高级语言通常都内置多线程的支持,Python也不例外,并且Python的线程是真正的Posix Thread,而不是模拟出来的线程。Python的标准库提供了两个模块:_threadthreading_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行:

线程常用方法

方法注释
start()线程准备就绪,等待CPU调度
setName()为线程设置名称
getName()获取线程名称
setDaemon(True)设置为守护线程
join()逐个执行每个线程,执行完毕后继续往下执行
run()线程被cpu调度后自动执行线程对象的run方法,如果想自定义线程类,直接重写run方法就行了

1、普通创建

import threading, time


def run(n):
    print('任务', n)
    time.sleep(1)
    print('2s')
    time.sleep(1)
    print('1s')
    time.sleep(1)
    print('0s')
    time.sleep(1)


t1 = threading.Thread(target=run, args=('t1',))
t2 = threading.Thread(target=run, args=('t2',))
t1.start()
t2.start()

任务 t1
任务 t2
2s
2s
1s
1s
0s
0s

2、继承threading.Thread来自定义线程类(重构Thread类中的run方法)

import threading, time


class MyThread(threading.Thread):
    def __init__(self, n):
        super(MyThread, self).__init__()  # 重构run函数
        self.n = n

    def run(self):
        print('任务', self.n)
        time.sleep(1)
        print('2s')
        time.sleep(1)
        print('1s')
        time.sleep(1)
        print('0s')
        time.sleep(1)


if __name__ == '__main__':
    t1 = MyThread('t1')
    t2 = MyThread('t2')

    t1.start()
    t2.start()

任务 t2
2s
2s
1s1s

0s
0s

3、多线程创建

import time, threading


# 新线程执行的代码
def loop():
    print('线程 %s 正在启动...' % threading.current_thread().name)
    n = 0
    while n < 6:
        n = n + 1
        print('线程 %s >>> %s' % (threading.current_thread().name, n))
        time.sleep(1)
    print('线程 %s 结束.' % threading.current_thread().name)


print('线程 %s 正在启动...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('线程 %s 结束.' % threading.current_thread().name)

线程 MainThread 正在启动...
线程 LoopThread 正在启动...
线程 LoopThread >>> 1
线程 LoopThread >>> 2
线程 LoopThread >>> 3
线程 LoopThread >>> 4
线程 LoopThread >>> 5
线程 LoopThread >>> 6
线程 LoopThread 结束.
线程 MainThread 结束.

任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,Python的threading模块有个current_thread()函数,它永远返回当前线程的实例。主线程实例的名字叫MainThread,子线程的名字在创建时指定,我们用LoopThread命名子线程。名字仅仅在打印时用来显示,完全没有其他意义,如果不起名字Python就自动给线程命名为Thread-1,Thread-2……

4、计算子线程执行的时间

sleep的时候是不会占用cpu,在sleep的时候操作系统会把线程暂时挂起。

import threading, time


def run(n):
    print('任务', n, threading.current_thread())  # 输入当前线程
    time.sleep(1)
    print('3s')
    time.sleep(1)
    print('2s')
    time.sleep(1)
    print('1s')


start_time = time.time()
thread_obj = []  # 存放子线程实例

for i in range(3):
    t = threading.Thread(target=run, args=('t-%s' % i,))
    t.start()
    thread_obj.append(t)

for t in thread_obj:
    t.join()        #为每个子线程添加join之后,主线程就会等这些子线程执行完之后再执行。

print('cost:', time.time() - start_time)  # 主线程
print(threading.current_thread())  # 输出当前线程

任务 t-0 <Thread(Thread-1, started 8124)>
任务 t-1 <Thread(Thread-2, started 8944)>
任务 t-2 <Thread(Thread-3, started 948)>
3s
3s
3s
2s
2s
2s
1s
1s
1s
cost: 3.000999927520752
<_MainThread(MainThread, started 8756)>

5、统计当前活跃的线程数

主线程比子线程快很多,当主线程执行active_count()时,其他子线程都还没执行完毕,因此利用主线程统计的活跃的线程数num = sub_num(子线程数量)+1(主线程本身):

import threading, time


def run(n):
    print('任务', n)
    time.sleep(0.5)  # 子线程停0.5s


for i in range(3):
    t = threading.Thread(target=run, args=('t-%s' % i,))
    t.start()

time.sleep(1)  # 主线程 停1s
print(threading.active_count())  # 输出活跃线程数

任务 t-0
任务 t-1
任务 t-2
1

6、守护进程

使用setDaemon(True)把所有的子线程都变成了主线程的守护线程,因此当主进程结束后,子线程也会随之结束。所以当主线程结束后,整个程序就退出了。

import threading, time


def run(n):
    print('任务', n)
    time.sleep(1)
    print('3')
    time.sleep(1)
    print('2')
    time.sleep(1)
    print('1')


for i in range(3):
    t = threading.Thread(target=run, args=('t-%s' % i,))
    t.setDaemon(True)  # 子进程设置为守护线程,必须在start()之前设置
    t.start()

time.sleep(0.5)  # 主线程停0.5s
print(threading.active_count())  # 输出活跃的线程数

任务 t-0
任务 t-1
任务 t-2
4

Lock(互斥锁)

线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁,即同一时刻允许一个线程执行操作。线程锁用于锁定资源,你可以定义多个锁, 像下面的代码, 当你需要独占某一资源时,任何一个锁都可以锁这个资源,就好比你用不同的锁都可以把相同的一个门锁住是一个道理。

由于线程之间是进行随机调度,如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,我们也称此为“线程不安全”。

多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。通过threading.Lock()来实现锁。

import time, threading

# 银行存款:
balance = 0
lock = threading.Lock() #实例化一个锁对象


def change_it(n):
    # 先存后取,结果应该为0:
    global balance
    balance = balance + n
    balance = balance - n


def run_thread(n):
    for i in range(100000):
        # 获取锁:
        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)

当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。获得锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。所以我们用try...finally来确保锁一定会被释放。
锁的好处就是确保了某段关键代码只能由一个线程从头到尾完整地执行,坏处当然也很多,首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了。其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。

事件(Event类)

线程的事件用于主线程控制其他线程的执行,事件是一个简单的线程同步对象,其主要提供以下几个方法:

方法注释
clear将flag设置为“False”
set将flag设置为“True”
is_set判断是否设置了flag
wait会一直监听flag,如果没有检测到flag就一直处于阻塞状态

事件处理的机制:全局定义了一个Flag,当flag值为False,那么event.wait()就会阻塞,当flag值为True,那么event.wait()便不再阻塞。

#利用Event类模拟红绿灯
import threading
import time

event = threading.Event()


def lighter():
    count = 0
    event.set()     #初始值为绿灯
    while True:
        if 5 < count <=10 :
            event.clear()  # 红灯,清除标志位
            print("\33[41;1m  红灯 \033[0m")
        elif count > 10:
            event.set()  # 绿灯,设置标志位
            count = 0
        else:
            print("\33[42;1m 绿灯 \033[0m")

        time.sleep(1)
        count += 1

def car(name):
    while True:
        if event.is_set():      #判断是否设置了标志位
            print("[%s] running..."%name)
            time.sleep(1)
        else:
            print("[%s] 红灯,停车..."%name)
            event.wait()
            print("[%s] 绿灯,通行..."%name)

light = threading.Thread(target=lighter,)
light.start()

car = threading.Thread(target=car,args=("悍马",))
car.start()

定时器(Timer类)

from threading import Timer


def hello():
    print('hello.world')

t = Timer(1, hello)  # 1s后执行hello函数
t.start()

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):
    # 绑定ThreadLocal的student
    local_school.student = name
    process_student()


t1 = threading.Thread(target=process_thread, args=('alice',), name='线程-A')
t2 = threading.Thread(target=process_thread, args=('bob',), name='线程-B')
t1.start()
t2.start()
t1.join()
t2.join()

Hello,alice(in 线程-A)
Hello,bob(in 线程-B)

全局变量local_school就是一个ThreadLocal对象,每个Thread对它都可以读写student属性,但互不影响。可以把local_school看成全局变量,但每个属性如local_school.student都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal内部会处理。全局变量local_school是一个dict,不但可以用local_school.student,还可以绑定其他变量,如local_school.teacher等。ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。

正则表达式

正则表达式是一种用来匹配字符串的。它的设计思想是用一种描述性的语言来给字符串定义一个规则,凡符合规则的字符串,就认为它“匹配”了,否则,该字符串就是不合法的。在正则表达式中,如果直接给出字符,就是精确匹配。
正则表达式
Python正则表达式

基础

【匹配数字和字母】\d可以匹配一个数字,\w可以匹配一个字母或数字

  • 00\d 可以匹配 007,但是无法匹配00A
  • \d\d\d可以匹配010
  • \w\w\d 可以匹配py3

【匹配任意字符】.

  • py.可以匹配pyfpyupy9等等。

【匹配变长的字符】:在正则表达式中,用*表示任意个字符(包括0个),用+表示至少一个字符,用?示0个或1个字符,用{n}表示n个字符,用{n,m}表示n-m个字符

\d{4}\s+\d{3,8}

1 、\d{4}表示匹配4个数字,例如'0851'
2、\s可以匹配一个空格(也包括Tab等空白符),所以\s+表示至少有一个空格,例如匹配' '' '等;
3、\d{3,8}表示3-8个数字,例如'1234567'

特殊字符需要用\转义:-字符,转义\-

进阶

精确地匹配,可以用[]表示范围

  • [0-9a-zA-z\_] :匹配一个数字、字母或者下划线
  • [0-9a-zA-z\_]+:匹配至少由一个数字、字母或者下划线组成的字符串,如a2000_Zpy1000等等
  • [a-zA-Z\_][0-9a-zA-Z\_]*:匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串
  • [a-zA-Z\_][0-9a-zA-Z\_]{0,19}:精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)

A|B可以匹配A或B,所以(P|p)ython可以匹配'Python'或者'python'
^表示行的开头,^\d表示必须以数字开头。
$表示行的结束,\d$表示必须以数字结束

re模块

在Python中使用正则表达式了。Python提供re模块,包含所有正则表达式的功能。由于Python的字符串本身也用\转义,所以要特别注意。
[re.match]
函数语法:re.match(pattern, string, flags=0)

参数描述
pattern匹配的正则表达式
string要匹配的字符串
flags标志位,用于控制正则表达式的匹配方式,如:是否区分大小写,多行匹配等等(正则表达式修饰符 - 可选标志)
  • 正则表达式修饰符 - 可选标志:
    正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。多个标志可以通过按位 OR(|)它们来指定。如re.I | re.M被设置成IM标志:
修饰符描述
re.I使匹配对大小写不敏感
re.L做本地化识别(locale-aware)匹配
re.M多行匹配,影响 ^ 和 $
re.S使 . 匹配包括换行在内的所有字符
re.U根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B.
re.X该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。
import re

num_a = r'^\d{4}\-\d{3,8}$'

# match()方法判断是否匹配,如果匹配成功,返回一个Match对象,否则返回None
print(re.match(num_a, '0853-123345'))  # <re.Match object; span=(0, 11), match='0853-123345'>
print(re.match(num_a, '0853 123345'))  # None

# 判断方法
re_test = r'^\d{4}\-\d{3,8}$'
if re.match(re_test, '0853-123345'):
    print('匹配正确')
else:
    print('匹配失败')

[re.search]
函数语法:re.search(pattern, string, flags=0)

print(re.search('www', 'www.python.org').span())
'''
(0,3)
'''
line = 'Cats are smarter than pigs'
sObj = re.search(r'^(.*) are (.*?) .*$', line, re.M | re.I)

if sObj:
    print('sObj.group():%s' % sObj.group())
    print('sObj.group(1):%s' % sObj.group(1))
    print('sObj.group(2):%s' % sObj.group(2))
else:
    print('Not found')
sObj.group():Cats are smarter than dogs
sObj.group(1):Cats
sObj.group(2):smarter

re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;而re.search匹配整个字符串,直到找到一个匹配。

[re.sub]
re.sub用于替换字符串中的匹配项:re.sub(pattern, repl, string, count=0, flags=0)

  • pattern : 正则中的模式字符串。
  • repl : 替换的字符串,也可为一个函数。
  • string : 要被查找替换的原始字符串。
  • count : 模式匹配后替换的最大次数,默认 0 表示替换所有的匹配。
phone = '0853-121-211 # 一个电话号码'

# 删除注释
num = re.sub(r'#.*$','',phone)
print(num)

# 删除‘-’字符
num = re.sub(r'\D','',phone)
print(num)
0853-121-211 
0853121211

切分字符串

用正则表达式切分字符串比用固定的字符更灵活:

# 切割字符串
str_a = 'a b    x'
str_b = 'a,b,c  ;f,d'

# 正常的切分代码(无法识别连续的空格)
print(str_a.split(' '))  # ['a', 'b', '', '', '', 'x']

# 正则表达式切分代码
print(re.split(r'\s+', str_a))  # ['a', 'b', 'x']
print(re.split(r'[\s\,\;]+', str_b))  # ['a', 'b', 'c', 'f', 'd']

分组

除了简单地判断是否匹配之外,正则表达式还有提取子串的强大功能。用()表示的就是要提取的分组(Group):

匹配对象方法描述
group(num=0)匹配的整个表达式的字符串,group() 可以一次输入多个组号,在这种情况下它将返回一个包含那些组所对应值的元组。
groups()返回一个包含所有小组字符串的元组,从 1 到 所含的小组号。
# 分组
g_re = r'^(\d{4})-(\d{3,8})$'
m = re.match(g_re, '0853-123456')
print(m)
print(m.group(0))
print(m.group(1))
print(m.group(2))
<re.Match object; span=(0, 11), match='0853-123456'>
0853-123456
0853
123456

group(0)永远是原始字符串,group(1)group(2)……表示第1、2、……个子串。

贪婪匹配

正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。

# 贪婪匹配
s_re = r'(\d+)(0*)$'
s = re.match(s_re,'10320000').groups()
print(s)
('10320000', '')

\d+采用贪婪匹配,直接把后面的0全部匹配了,结果0*只能匹配空字符串了,\d+采用非贪婪匹配(也就是尽可能少匹配),才能把后面的0匹配出来,加个?就可以让\d+采用非贪婪匹配。

# 贪婪匹配
s_re = r'(\d+?)(0*)$'
s = re.match(s_re,'10320000').groups()
print(s)
('1032', '0000')

常用模块

datetime-日期和时间模块

Python处理日期和时间的标准库
获取当前日期和时间,获取指定日期和时间:

from datetime import datetime

# 获取当前日期和时间
now_date = datetime.now()
print(now_date)
print(type(now_date))

# 获取指定日期和时间
d_time = datetime(2020,3,6,12,18)
print(d_time)
2020-03-06 14:17:14.286800
<class 'datetime.datetime'>
2020-03-06 12:18:00

datetime\timestamp互转

在计算机中,时间实际上是用数字表示的。我们把1970年1月1日 00:00:00 UTC+00:00时区的时刻称为epoch time,记为 0(1970年以前的时间timestamp为负数),当前时间就是相对于epoch time的秒数,称为timestamp。Python的timestamp是一个浮点数。如果有小数位,小数位表示毫秒数。

d_time = datetime(2020,3,6,12,18)
print(d_time.timestamp())
'''
1583468280.0
'''

t = 1583462280.0
print(datetime.fromtimestamp(t))
'''
2020-03-06 10:38:00
'''

str/datetime互转

# str-datetime
obj_time = datetime.strptime('2020-09-11 14:36:01', '%Y-%m-%d %H:%M:%S')
print(obj_time)
print(type(obj_time))

now_time = datetime.now()
str_time = now_time.strftime('%Y-%m-%d %H:%M:%S')
print(str_time)
'''
2020-09-11 14:36:01
<class 'datetime.datetime'>
2020-03-06 14:48:42
'''

datetime加减

对日期和时间进行加减实际上就是把datetime后或往前计算,得到新的datetime。加减可以直接用+和-运算符。

from datetime import datetime, timedelta
now_a = datetime.now()
print(now_a)
print(now_a+timedelta(days=1))
print(now_a+timedelta(days=1,hours=10,minutes=20))
2020-03-09 09:31:52.615500
2020-03-10 09:31:52.615500
2020-03-10 19:51:52.615500

本地时间转换为UTC时间

本地时间是指系统设定时区的时间,例如北京时间是UTC+8:00时区的时间,而UTC时间指UTC+0:00时区的时间。
一个datetime类型有一个时区属性tzinfo,但是默认为None,所以无法区分这个datetime到底是哪个时区,除非强行给datetime设置一个时区。·

from datetime import datetime, timedelta, timezone
t_utc8 = timezone(timedelta(hours=8))  # 时区UTC+8:00
now_b = datetime.now()
print(now_b)
dt = now_b.replace(tzinfo=t_utc8)
print(dt)

2020-03-09 09:31:52.615500
2020-03-09 09:31:52.615500+08:00

时区转换

先通过utcnow()拿到当前的UTC时间,再转换为任意时区的时间:

from datetime import datetime, timedelta, timezone
# 获取UTC时间,强制设置时区为UTC+0:00
utc_dt = datetime.utcnow().replace(tzinfo=timezone.utc)
print(utc_dt)
# 转换时区为北京时间
bj_dt = utc_dt.astimezone(timezone(timedelta(hours=8)))
print(bj_dt)
# 转换时区为东京时间
dj_dt = utc_dt.astimezone(timezone(timedelta(hours=9)))
print(dj_dt)
2020-03-09 01:47:44.125500+00:00
2020-03-09 09:47:44.125500+08:00
2020-03-09 10:47:44.125500+09:00
from datetime import datetime, timedelta, timezone
import re


# 获取了用户输入的日期和时间如2020-3-18 9:01:30,以及一个时区信息如UTC+5:00,均是str,请编写一个函数将其转换为timestamp
def to_timestamp(dt_str, tz_str):
    # 获取时间
    r_time = datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S')
    # 获取时区
    utc = re.match(r'^UTC([\+\-])(\d{1,2})\:00$', tz_str)
    if utc[1] == '+':
        t_utc = int(utc[2])
    else:
        t_utc = -int(utc[2])

    # 强制设置时区
    utc_n = r_time.replace(tzinfo=timezone(timedelta(hours=t_utc))).astimezone(timezone.utc)

    # 返回时间戳
    return utc_n.timestamp()


print(to_timestamp('2020-3-18 9:01:30', 'UTC+5:00'))


collections-集合模块

collections是Python内建的一个集合模块,提供了许多有用的集合类。

namedtuple

用来创建一个自定义的tuple对象,并且规定了tuple元素的个数,并可以用属性而不是索引来引用tuple的某个元素。

from collections import namedtuple

Pit = namedtuple('Pit',['x','y','z'])
p = Pit(1,2,3)
print(p)
print(p.x,p.y,p.z)

deque

使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低。deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈:

from collections import deque

q = deque(['a', 'b', 'c'])
q.append('d')
q.appendleft('x')
print(q)

defaultdict

使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict

from collections import  defaultdict

dd = defaultdict(lambda :'N/A')
dd['key1'] = 'abc'
print(dd['key1'])
print(dd['key2'])

Counter

Counter是一个简单的计数器,统计字符出现的个数:

from collections import Counter

c = Counter('dasdasdf asda')
print(c)

c = Counter()
c.update('asffwerr fdsfs')
print(c)
Counter({'d': 4, 'a': 4, 's': 3, 'f': 1, ' ': 1})
Counter({'f': 4, 's': 3, 'r': 2, 'a': 1, 'w': 1, 'e': 1, ' ': 1, 'd': 1})

base64

Python内置的base64可以直接进行base64的编解码,Base64是一种任意二进制到文本字符串的编码方法,常用于在URL、Cookie、网页中传输少量二进制数据

import base64

# 编码
s = base64.b64encode(b'binary\x00string')
print(s)
# 解码
b = base64.b64decode(b'YmluYXJ5AHN0cmluZw==')
print(b)

标准的Base64编码后可能出现字符+/,在URL中就不能直接作为参数,所以又有一种"url safe"的base64编码,其实就是把字符+/分别变成-_:

d = base64.b64encode(b'i\xb7\x1d\xfb\xef\xff')
print(d)
f = base64.urlsafe_b64encode(b'i\xb7\x1d\xfb\xef\xff')
print(f)
rf =  base64.urlsafe_b64decode('abcd--__')
print(rf)

Base64是一种通过查表的编码方法,不能用于加密,即使使用自定义的编码表也不行。
Base64适用于小段内容的编码,比如数字证书签名、Cookie的内容等。
由于=字符也可能出现在Base64编码中,但=用在URLCookie里面会造成歧义,所以,很多Base64编码后会把=去掉,Base64编码的长度永远是4的倍数,因此,需要加上=Base64字符串的长度变为4的倍数,就可以正常解码

struct

Python提供了一个struct模块来解决bytes和其他二进制数据类型的转换。structpack函数把任意数据类型变成bytes

import struct

i = struct.pack('>I',10240099)
print(i)
'''
b'\x00\x9c@c'
'''

>I的意思是:>表示字节顺序是big-endian,也就是网络序,I表示4字节无符号整数。后面的参数个数要和处理指令一致。

unpackbytes变成相应的数据类型:

import struct

s = struct.unpack('>IH',b'\xf0\xf0\xf0\xf0\x80\x80')
print(s)

'''
(4042322160, 32896)
'''

I4字节无符号整数 H2字节无符号整数

hashlib

hashlib提供了常见的摘要算法,如MD5SHA1等等。摘要算法就是通过摘要函数f()对任意长度的数据data计算出固定长度的摘要digest,目的是为了发现原始数据是否被人篡改过。

摘要算法MD5:

import hashlib

md5 = hashlib.md5()
md5.update('how to use md5 in python hashlib?'.encode('utf-8'))
print(md5.hexdigest())

'''
d26a53750bc40b38b65a520292f69306
'''

摘要算法是SHA1:

import hashlib

sha1 = hashlib.sha1()
sha1.update('hhhhhhsdadw'.encode('utf-8'))
sha1.update('dawrqr qrqr?'.encode('utf-8'))
print(sha1.hexdigest())

'''
f14475f43f4cb8d5f86b1f0ce89d9ec7369ab600
'''

练习1:

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import hashlib

db = {
    'michael': 'e10adc3949ba59abbe56e057f20f883e',
    'bob': '878ef96e86145580c38c87f0410ad153',
    'alice': '99b1c2188db85afee403b1536010c2c9'
}

def login(user, password):
    return db[user] == hashlib.md5(password.encode('utf-8')).hexdigest()

# 测试
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')

练习2:

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import hashlib, random


def get_md5(pwd):
    return hashlib.md5(pwd.encode('utf-8')).hexdigest()


class User():
    def __init__(self, uname, pwd):
        self.uname = uname
        self.salt = ''.join([chr(random.randint(48, 122)) for i in range(20)])
        self.pwd = get_md5(pwd + self.salt)


db = {
    'michael': User('michael', '123456'),
    'bob': User('bob', 'abc999'),
    'alice': User('alice', 'alice2008')
}


def login(uname, pwd):
    user = db[uname]
    return user.pwd == get_md5(pwd + 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

Hmac算法:Keyed-Hashing for Message Authentication。它通过一个标准算法,在计算哈希的过程中,把key混入计算过程中。Python自带的hmac模块实现了标准的Hmac算法。

import hmac

message = b'Hello,Python!'
key = b'secret'
h = hmac.new(key, message, digestmod='MD5')
print(h.hexdigest())
'''
e18f0514cec1694534724ea2eff6d401
'''

使用hmac和普通hash算法非常类似。hmac输出的长度和原始哈希算法的长度一致。需要注意传入的keymessage都是bytes类型,str类型需要首先编码为bytes

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import hmac, random


def hmac_md5(key, s):
    return hmac.new(key.encode('utf-8'), s.encode('utf-8'), 'MD5').hexdigest()


class User():
    def __init__(self, uname, pwd):
        self.uname = uname
        self.key = ''.join([chr(random.randint(48, 122)) for i in range(20)])
        self.pwd = hmac_md5(self.key, pwd)


db = {
    'michael': User('michael', '123456'),
    'bob': User('bob', 'abc999'),
    'alice': User('alice', 'alice2008')
}


def login(uname, pwd):
    user = db[uname]
    return user.pwd == hmac_md5(user.key, pwd)


# 测试:
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提供了非常有用的用于操作迭代对象的函数
count()会创建一个无限的迭代器
repeat()负责把一个元素无限重复下去,第二个参数可以限定重复次数
takewhile()等函数根据条件判断来截取出一个有限的序列

import itertools

# count()会创建一个无限的迭代器
'''
ns = itertools.count(1)
for n in ns:
    print(n)
'''

# cycle()会把传入的一个序列无限重复下去
'''
cs = itertools.cycle('ABC')
for c in cs:
    print(c)
'''

# repeat()负责把一个元素无限重复下去,不提供第二个参数就可以限定重复次数
rs = itertools.repeat('A', 4)
for r in rs:
    print(r)

# takewhile()函数根据条件判断来截取出一个有限的序列
ns = itertools.count(1)
n = itertools.takewhile(lambda x: x <= 10, ns)
print(list(n))

import itertools

# count()会创建一个无限的迭代器
'''
ns = itertools.count(1)
for n in ns:
    print(n)
'''

# cycle()会把传入的一个序列无限重复下去
'''
cs = itertools.cycle('ABC')
for c in cs:
    print(c)
'''

# repeat()负责把一个元素无限重复下去,不提供第二个参数就可以限定重复次数
rs = itertools.repeat('A', 4)
for r in rs:
    print(r)
'''
A
A
A
A
'''

# takewhile()函数根据条件判断来截取出一个有限的序列
ns = itertools.count(1)
n = itertools.takewhile(lambda x: x <= 10, ns)
print(list(n))
'''
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
'''

# chain()可以把一组迭代对象串联起来,形成一个更大的迭代器
for c in itertools.chain('abc', 'xyz'):
    print(c)
'''
a
b
c
x
y
z
'''

# groupby()把迭代器中相邻的重复元素挑出来放在一起
for key, group in itertools.groupby('aaabbbcccaaassAaBbb',lambda c: c.upper()):
    print(key, list(group))
'''
A ['a', 'a', 'a']
B ['b', 'b', 'b']
C ['c', 'c', 'c']
A ['a', 'a', 'a']
S ['s', 's']
A ['A', 'a']
B ['B', 'b', 'b']
'''

练习:

# -*- coding: utf-8 -*-
import itertools
from functools import reduce


def pi(N):
    '计算pi值'
    # 1、创建奇数序列
    odd = itertools.count(1, 2)

    # 2、取前N项
    lodd = itertools.takewhile(lambda x: x <= 2 * N - 1, odd)

    # 3、添加正负号 并用4除
    nums = itertools.cycle((4, -4))
    s = [nums.__next__() / i for i in lodd]

    # 4、求和
    sum = reduce(lambda x, y: x + y, s)

    return sum


# 测试:
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')

itertools模块提供的全部是处理迭代功能的函数,它们的返回值不是list,而是Iterator,只有用for循环迭代的时候才真正计算

contextlib

Python的with语句允许我们非常方便地使用资源,而不必担心资源没有关闭。@contextmanager这个decorator接受一个generator,用yield语句把with ... as var把变量输出出去,然后with语句就可以正常地工作。@contextmanager让我们通过编写generator来简化上下文管理。

from contextlib import contextmanager


class Query():
    def __init__(self, name):
        self.name = name

    def query(self):
        print('Query info about %s...' % self.name)


@contextmanager
def create_query(name):
    print('开始')
    q = Query(name)
    yield q
    print('结束')


with create_query('bob') as q:
    q.query()

'''
开始
Query info about bob...
结束
'''

在某段代码执行前后自动执行特定代码,也可以用@contextmanager实现

@contextmanager
def tag(name):
    print('<%s>' % name)
    yield
    print('</%s>' % name)


with tag('h1'):
    print('hello')
    print('python')
'''
<h1>
hello
python
</h1>   
'''

执行顺序是:

  • with语句首先执行yield前的语句,因此打印出<h1>
  • yield调用会执行with语句内部的所有语句,因此打印出hello和world;
  • 最后执行yield之后的语句,打印出</h1>

如果一个对象没有实现上下文,就不能把它用于with语句。可以用closing()来把该对象变为上下文对象。例如,用with语句使用urlopen()

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()

urllib

urllib提供了一系列用于操作URL的功能

  • get
    urllibrequest模块可以非常方便地抓取URL内容,也就是发送一个GET请求到指定的页面,然后返回HTTP的响应:
    例如,对知乎的一个http://news-at.zhihu.com/api/4/news/latest进行抓取,并返回响应
from urllib import request

with request.urlopen('http://news-at.zhihu.com/api/4/news/latest') as f:
    datas = f.read()
    print('状态:', f.status, f.reason)
    for k, v in f.getheaders():
        print('%s:%s' % (k, v))
    print('数据:', datas.decode('utf-8'))

状态: 200 OK
Server:Tengine
Content-Type:application/json; charset=UTF-8
Content-Length:2682
Connection:close
Date:Tue, 10 Mar 2020 07:12:49 GMT
Set-Cookie:tgw_l7_route=116a747939468d99065d12a386ab1c5f; Expires=Tue, 10-Mar-2020 07:27:49 GMT; Path=/
Vary:Accept-Encoding
Etag:"1a79ee3d3f912b41a4e4b189da6c2e88edd0df1a"
X-Backend:zhihu-daily-web--1-e2c01739-645cb8d756-kpkh5
X-Backend-Response:0.014
X-SecNG-Response:0.026000022888184
Set-Cookie:_xsrf=BmhAxiCPgLLtxu8ltktSmOfGN9p8OsMX; path=/; domain=zhihu.com; expires=Sat, 27-Aug-22 07:12:49 GMT
x-lb-timing:0.025
x-idc-id:1
Via:cache33.l2cn1828[280,0], vcache11.cn2450[285,0]
Timing-Allow-Origin:*
EagleId:75bb159f15838243689427639e
数据: {"date":"20200310","stories":[{"image_hue":"0x4d3b36","title":"贪腐、失控、倒逼工作室,顶流团队真能管好粉圈吗?","url":"https:\/\/daily.zhihu.com\/story\/9721372","hint":"娱理工作室 · 3 分钟阅读","ga_prefix":"031011","images":["https:\/\/pic2.zhimg.com\/v2-5c7068cccefbbfdd00b27d2667f26e95.jpg"],"type":0,"id":9721372},{"image_hue":"0x5c928e","title":"如果你是天美的运营官,你会如何优化《王者荣耀》?","url":"https:\/\/daily.zhihu.com\/story\/9721362","hint":"Joker · 4 分钟阅读","ga_prefix":"031009","images":["https:\/\/pic4.zhimg.com\/v2-d49cfd2d89adef0cc587f6602c2f11b3.jpg"],"type":0,"id":9721362},....}]}

模拟浏览器发送GET请求,就需要使用Request对象,通过往Request对象添加HTTP头,我们就可以把请求伪装成浏览器。例如,模拟iPhone 6去请求baidu首页

from urllib import request

req = request.Request('https://www.baidu.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:
    print('Status:', f.status, f.reason)
    for k, v in f.getheaders():
        print('%s:%s' % (k, v))
    print('Data:', f.read().decode('utf-8'))

  • Post
    如果要以POST发送一个请求,只需要把参数databytes形式传入。
    我们模拟一个微博登录,先读取登录的邮箱和口令,然后按照weibo.cn的登录页的格式以username=xxx&password=xxx的编码传入:
from urllib import request, parse

print('Login to weibo.cn...')
email = input('Email: ')
passwd = input('Password: ')
login_data = parse.urlencode([
    ('username', email),
    ('password', passwd),
    ('entry', 'mweibo'),
    ('client_id', ''),
    ('savestate', '1'),
    ('ec', ''),
    ('pagerefer', 'https://passport.weibo.cn/signin/welcome?entry=mweibo&r=http%3A%2F%2Fm.weibo.cn%2F')
])

req = request.Request('https://passport.weibo.cn/sso/login')
req.add_header('Origin', 'https://passport.weibo.cn')
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')
req.add_header('Referer', 'https://passport.weibo.cn/signin/login?entry=mweibo&res=wel&wm=3349&r=http%3A%2F%2Fm.weibo.cn%2F')

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'))

登录成功:

Status: 200 OK
Server: nginx/1.2.0
...
Set-Cookie: SSOLoginState=1432620126; path=/; domain=weibo.cn
...
Data: {"retcode":20000000,"msg":"","data":{...,"uid":"1658384301"}}

登录失败:

...
Data: {"retcode":50011015,"msg":"\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef","data":{"username":"example@python.org","errline":536}}

HTMLParser

from html.parser import HTMLParser
# from html.entities import name2codepoint
from urllib import request
import re

class MyHTMLParser(HTMLParser):
    def __init__(self):
        super(MyHTMLParser,self).__init__()
        self.__parsedata='' # 设置一个空的标志位

    def handle_starttag(self, tag, attrs):
        if ('class', 'event-title') in attrs:
            self.__parsedata = 'name'  # 通过属性判断如果该标签是我们要找的标签,设置标志位
        if tag == 'time':
            self.__parsedata = 'time'
        if ('class', 'say-no-more') in attrs:
            self.__parsedata = 'year'
        if ('class', 'event-location') in attrs:
            self.__parsedata = 'location'

    def handle_endtag(self, tag):
        self.__parsedata = ''# 在HTML 标签结束时,把标志位清空

    def handle_data(self, data):
        if self.__parsedata == 'name': 
            print('会议名称:%s' % data) # 通过标志位判断,输出打印标签内容

        if self.__parsedata == 'time':
            print('会议时间:%s' % data)

        if self.__parsedata == 'year':
            if re.match(r'\s\d{4}', data): # 因为后面还有两组 say-no-more 后面的data却不是年份信息,所以用正则检测一下
                print('会议年份:%s' % data.strip())

        if self.__parsedata == 'location':
            print('会议地点:%s' % data)
            print('----------------------------------')

parser = MyHTMLParser()

URL = 'https://www.python.org/events/python-events/'

with request.urlopen(URL, timeout=15) as f:  # 打开网页并取到数据
    data = f.read()
parser.feed(data.decode('utf-8'))

常用第三方模块

Pillow

Pycharm 安装:打开Terminal、python -m pip install pip python -m pip install Pillow
官网Pillow

from PIL import Image, ImageFilter

im = Image.open('D:\code\PycharmProjects\PythonStudy\966760155050624.jpg')
# 获得尺寸
w, h = im.size
print('长-宽:%s-%s' % (w, h))
# 缩放50%
im.thumbnail((w // 2, h // 2))
print('长-宽:%s-%s' % (w // 2, h // 2))
# 缩放保存
im.save('demo.jpg', 'jpeg')

# 模糊滤镜
im2 = im.filter(ImageFilter.BLUR)
im2.save('demo2.jpg', 'jpeg')

PIL的ImageDraw提供了一系列绘图方法,可以直接绘图。比如要生成字母验证码图片:

from PIL import Image, ImageDraw, ImageFont, ImageFilter
import random


# 随机字母
def rdChar():
    return chr(random.randint(65, 90))


# 随机颜色2
def rdColor():
    return (random.randint(64, 255), random.randint(64, 255), random.randint(64, 255))


# 随机颜色2
def rdColor2():
    return (random.randint(32, 127), random.randint(32, 127), random.randint(32, 127))


# 250 * 65
width = 50 * 5
height = 65
image = Image.new('RGB', (width, height), (255, 255, 255))
# Font对象
font = ImageFont.truetype('C:/Windows/Fonts/Arial.ttf', 36)
# Draw对象
draw = ImageDraw.Draw(image)
# 填充每个像素
for x in range(width):
    for y in range(height):
        draw.point((x, y), fill=rdColor())

# 输出文字
for t in range(5):
    draw.text((60 * t + 9, 9), rdChar(), font=font, fill=rdColor2())

# 模糊
image = image.filter(ImageFilter.BLUR)
image.save('code.jpg', 'jpeg')

requests

requests。它是一个Python第三方库,处理URL资源特别方便。
通过GET访问一个页面:

import requests

r = requests.get('https://new.qq.com/omn/20200310/20200310A0RYZA00.html')
print(r.status_code)
# print(r.text)

# 带参数URL,传入dict做params参数
r1 = requests.get('https://www.douban.com/search', params={'q': 'python', 'cat': '1001'})
print(r1.url)

# requests自动检测编码,可以使用encoding属性查看
print(r1.encoding)

无论响应是文本还是二进制内容,我们都可以用content属性获得bytes对象

print(r.content)

可以直接获取JSON,需要传入HTTP Header时,我们传入一个dict作为headers参数。

r2 = requests.get('https://www.douban.com/', headers={'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit'})
print(r2.text)

发送POST请求,只需要把get()方法变成post(),然后传入data参数作为POST请求的数据:

r3 = requests.post('https://accounts.douban.com/login',
                   data={'form_email': 'abc@example.com', 'form_password': '123456'})

params = {'key': 'value'}
r = requests.post(url, json=params) # 内部自动序列化为JSON

上传文件,requests把它简化成files参数

upload_file = {'file': open('xxx.xls', 'rb')}
r = requests.post(url, files=upload_file)

post()方法替换为put()delete()等,就可以以PUTDELETE方式请求资源,requestsCookie做了特殊处理,不必解析Cookie就可以轻松获取指定的Cookie

c = {'token': '123456', 'status': 'working'}
r = requests.get(url, cookies=c)

chardet

检测编码,简单易用。
拿到一个bytes时,就可以对其检测编码。用chardet检测编码,只需要一行代码:

import chardet

c = chardet.detect(b'Hello,Python')
print(c)
'''
{'encoding': 'ascii', 'confidence': 1.0, 'language': ''}
'''

datas = '天王盖地虎,小鸡炖蘑菇'.encode('gbk')
d = chardet.detect(datas)
print(d)

'''
{'encoding': 'GB2312', 'confidence': 0.7407407407407407, 'language': 'Chinese'}
'''

datas = '天王盖地虎,小鸡炖蘑菇'.encode('utf-8')
d = chardet.detect(datas)
print(d)
'''
{'encoding': 'utf-8', 'confidence': 0.99, 'language': ''}
'''

datas = '主要ニュース'.encode('euc-jp')
d = chardet.detect(datas)
print(d)

'''
{'encoding': 'EUC-JP', 'confidence': 0.99, 'language': 'Japanese'}
'''

psutil

Python中获取系统信息,跨平台使用,支持Linux/UNIX/OSX/Windows等psutil

  • 获取CPU信息
import psutil

# cpu数量
c_num = psutil.cpu_count()
print(c_num)
# cpu物理核心
c_hx = psutil.cpu_count(logical=False)
print(c_hx)
# 统计CPU的用户/系统/空闲时间
c_time = psutil.cpu_times()
print(c_time)
# 类似top命令的CPU使用率,每秒刷新一次,累计10次
for x in range(10):
    p = psutil.cpu_percent(interval=1, percpu=True)
    print(p)
6
6
scputimes(user=4970.815864, system=1706.5417392999952, idle=59512.0570851, interrupt=77.3920961, dpc=37.752241999999995)
[4.7, 9.4, 15.6, 7.8, 15.6, 17.2]
[7.7, 15.6, 0.0, 6.2, 10.9, 6.3]
[9.4, 9.4, 4.7, 6.3, 7.8, 9.4]
[9.1, 4.6, 3.1, 6.2, 10.8, 9.2]
[7.8, 9.4, 6.2, 9.4, 12.5, 9.4]
[9.4, 10.9, 0.0, 3.1, 17.2, 21.9]
[9.4, 9.4, 1.6, 6.3, 25.0, 17.2]
[6.2, 14.1, 3.1, 6.3, 12.5, 9.4]
[10.8, 9.4, 6.3, 4.7, 9.4, 14.1]
[20.9, 7.8, 10.9, 3.1, 14.1, 14.1]
  • 获取内存信息
    使用psutil获取物理内存和交换内存信息,分别使用:
import psutil

p = psutil.virtual_memory()
print(p)
s = psutil.swap_memory()
print(s)

svmem(total=8467001344, available=4706975744, percent=44.4, used=3760025600, free=4706975744)
sswap(total=16932057088, used=6075719680, free=10856337408, percent=35.9, sin=0, sout=0)
  • 获取磁盘信息
#  磁盘分区信息
ds = psutil.disk_partitions()
print(ds)
# 磁盘使用情况
dsu = psutil.disk_usage('/')
print(dsu)
#  磁盘IO
dsio = psutil.disk_io_counters()
print(dsio)
[sdiskpart(device='C:\\', mountpoint='C:\\', fstype='NTFS', opts='rw,fixed'), sdiskpart(device='D:\\', mountpoint='D:\\', fstype='NTFS', opts='rw,fixed'), sdiskpart(device='E:\\', mountpoint='E:\\', fstype='NTFS', opts='rw,fixed'), sdiskpart(device='F:\\', mountpoint='F:\\', fstype='', opts='cdrom')]
sdiskusage(total=503314378752, used=118400143360, free=384914235392, percent=23.5)
sdiskio(read_count=276350, write_count=246640, read_bytes=7565211648, write_bytes=8022806528, read_time=3129, write_time=589)

  • 获取网络接口
# 获取网络信息
n = psutil.net_io_counters()  # 获取网络读写字节/包个数
print(n)
ntf = psutil.net_if_addrs()  # 获取网络接口信息
print(ntf)
ntfs = psutil.net_if_stats()  # 获取网络接口状态
print(ntfs)
nifo = psutil.net_connections()  # 获取当前网络连接信息
print(nifo)
snetio(bytes_sent=25643627, bytes_recv=292889108, packets_sent=214689, packets_recv=255583, errin=0, errout=0, dropin=0, dropout=0)

{'本地连接': [snicaddr(fa...), snicaddr(...), snicaddr(...), snicaddr(...), snicaddr(...)]}

{'本地连接': snicstats(...), 'Loopback Pseudo-Interface 1': snicstats(...), 'isatap.DHCP HOST': snicstats...)}

[sconn(fd=-1, family=<AddressFamily.AF_INET6: 23>, ......... pid=2784)]

  • 获取进程信息
# 获取进程信息
print(psutil.pids())  # 获取所有进程ID
p = psutil.Process(408)  # 获取指定线程
print(p.name(), p.exe(), p.cwd(), p.cmdline(), p.ppid(), p.parent(), p.children(), p.status(), 				   	 p.username(),p.create_time(), p.cpu_times, p.memory_info(), p.open_files(), p.connections(), p.num_threads(), p.threads(), p.environ())
>>> p.name() # 进程名称
>>> p.exe() # 进程exe路径
>>> p.cwd() # 进程工作目录
>>> p.cmdline() # 进程启动的命令行
>>> p.ppid() # 父进程ID
>>> p.parent() # 父进程
>>> p.children() # 子进程列表
>>> p.status() # 进程状态
>>> p.username() # 进程用户名
>>> p.create_time() # 进程创建时间
>>> p.terminal() # 进程终端
>>> p.cpu_times() # 进程使用的CPU时间
>>> p.memory_info() # 进程使用的内存
>>> p.open_files() # 进程打开的文件
>>> p.connections() # 进程相关网络连接
>>> p.num_threads() # 进程的线程数量
>>> p.threads() # 所有线程信息
>>> p.environ() # 进程环境变量
>>>p.terminate() # 结束进程

图形界面

turtle

turtle
海龟绘图:

from turtle import *

# 设置笔刷宽度
width(4)

# 前进
forward(200)
# 右转60°
right(120)

# 笔刷颜色
pencolor('red')
forward(200)
right(120)

pencolor('green')
forward(200)
right(120)

# 调用done()使得窗口等待被关闭,否则立刻关闭
done()

绘制五角星:

from turtle import *


def drawStar(x, y):
    pencolor('red')
    pu()
    goto(x, y)
    pd()
    seth(0)
    for i in range(5):
        fd(40) # 向前移动
        rt(144) # 按角度单位右转


for x in range(0, 350, 50):
    drawStar(x, 0)

done()

PyQt5

安装:pip3 install --index-url https://pypi.douban.com/simple PyQt5
PyQt5教程

待补充…

网络编程

TCP

大多数连接都是可靠的TCP连接。创建TCP连接时,主动发起连接的叫客户端,被动响应连接的叫服务器。

  • Server
# -*- coding: utf-8 -*-
#server.py
import socket,threading,time
#创建一个基于IPv4和TCP协议的Socket:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 监听端口:
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)

#服务器程序通过一个永久循环来接受来自客户端的连接,
#accept()会等待并返回一个客户端的连接:

while True:
    # 接受一个新连接:
    sock, addr = s.accept()
    # 创建新线程来处理TCP连接:
    t = threading.Thread(target=tcplink, args=(sock, addr))
    t.start()
  • Client
# -*- coding: utf-8 -*-
#client.py
#要测试这个服务器程序,我们还需要编写一个客户端程序:
# 导入socket库:
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)
    print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()

UDP

TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据。相对TCP,UDP则是面向无连接的协议。使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。

  • udp_server
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# 导入socket库:
import socket

# 创建一个socket,socket.SOCK_DGRAM指定了这个Socket的类型是UDP:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 监听端口:
s.bind(('127.0.0.1', 9999))
#  绑定端口和TCP一样,但是不需要调用listen()方法,而是直接接收来自任何客户端的数据
print('Bind UDP on 9999....')
# recvfrom()方法返回数据和客户端的地址与端口
# 服务器收到数据后,直接调用sendto()就可以把数据用UDP发给客户端
while True:
	 # 接收数据:
    data, addr = s.recvfrom(1024)
    print('Received from %s:%s. ' % addr)
    s.sendto(b'Hello, %s!' % data, addr)
  • udp_client
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
客户端使用UDP时,首先仍然创建基于UDP的Socket,然后,不需要调用connect(),直接通过sendto()给服务器发数据
'''
# 导入socket库:
import socket

# 创建一个socket:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

for data in [b'kzc', b'qwe', b'asd']:
	# 发送数据:
    s.sendto(data, ('127.0.0.1', 9999))
     # 接收数据:
    print(s.recv(1024).decode('utf-8'))

s.close()

电子邮件

SMTP发送邮件

SMTP是发送邮件的协议,Python内置对SMTP的支持,可以发送纯文本邮件、HTML邮件以及带附件的邮件。Python对SMTP支持有smtplibemail两个模块,email负责构造邮件,smtplib负责发送邮件。

import smtplib

smtpObj = smtplib.SMTP( [host [, port [, local_hostname]]] )
  • host - 这是运行SMTP服务器的主机。可以指定主机的IP地址或域名。这是一个可选参数。
  • port- 如果提供主机参数,则需要指定SMTP服务器正在侦听的端口。通常这个端口默认值是:25
  • local_hostname -如果SMTP服务器在本地计算机上运行,那么可以只指定localhost选项。

发送纯文本文件

  • 给单人发送邮件
#! /usr/bin/env python
# coding=utf-8

from email.mime.text import MIMEText
from email.header import Header
from smtplib import SMTP_SSL

# qq邮箱smtp服务器
host_server = 'smtp.qq.com'
# sender_qq为发件人的qq号码
sender_qq = '...@qq.com'
# pwd为qq邮箱的授权码
pwd = 'nz....hf'  ## nz....hf
# 发件人的邮箱
sender_qq_mail = '..@qq.com'
# 收件人邮箱
receiver = '183...@163.com'

# 邮件的正文内容
mail_content = '你好,这是使用python登录qq邮箱发邮件的测试'
# 邮件标题
mail_title = 'Eureka的邮件'

# ssl登录
smtp = SMTP_SSL(host_server)
# set_debuglevel()是用来调试的。参数值为1表示开启调试模式,参数值为0关闭调试模式
smtp.set_debuglevel(1)
smtp.ehlo(host_server)
smtp.login(sender_qq, pwd)

msg = MIMEText(mail_content, "plain", 'utf-8')
msg["Subject"] = Header(mail_title, 'utf-8')
msg["From"] = sender_qq_mail
msg["To"] = receiver
smtp.sendmail(sender_qq_mail, receiver, msg.as_string())
smtp.quit()

  • 群发邮件
#! /usr/bin/env python
#coding=utf-8

from email.mime.text import MIMEText
from email.header import Header
from smtplib import SMTP_SSL


#qq邮箱smtp服务器
host_server = 'smtp.qq.com'
#sender_qq为发件人的qq号码
sender_qq = '...@qq.com'
#pwd为qq邮箱的授权码
pwd = 'nzi...bchf'
#发件人的邮箱
sender_qq_mail = '...@qq.com'
#收件人邮箱(读取脚本)
with open('D:/code/PycharmProjects/PythonStudy/venv/Include/电子邮件/email.txt') as f:
    for email in f.readlines():
        emails = email.strip(',')

receivers = list(emails.split(','))

#邮件的正文内容
mail_content = 'Hi,使用Python登录qq邮箱群发邮件的测试'
#邮件标题
mail_title = '^_^的邮件'


#ssl登录
smtp = SMTP_SSL(host_server)
#set_debuglevel()是用来调试的。参数值为1表示开启调试模式,参数值为0关闭调试模式
smtp.set_debuglevel(1)
smtp.ehlo(host_server)
smtp.login(sender_qq, pwd)

msg = MIMEText(mail_content, "plain", 'utf-8')
msg["Subject"] = Header(mail_title, 'utf-8')
msg["From"] = sender_qq_mail
msg["To"] = Header("receivers test", 'utf-8') ## 接收者的别名
smtp.sendmail(sender_qq_mail, receivers, msg.as_string())
smtp.quit()
  • 发送HTML格式的邮件
#! /usr/bin/env python
# coding=utf-8

from email.mime.text import MIMEText
from email.header import Header
from smtplib import SMTP_SSL

# qq邮箱smtp服务器
host_server = 'smtp.qq.com'
# sender_qq为发件人的qq号码
sender_qq = '...@qq.com'
# pwd为qq邮箱的授权码
pwd = 'nz...hf'
# 发件人的邮箱
sender_qq_mail = '...@qq.com'
# 收件人邮箱
with open('D:/code/PycharmProjects/PythonStudy/venv/Include/电子邮件/email.txt') as f:
    for email in f.readlines():
        emails = email.strip(',')

receiver = list(emails.split(','))

# 邮件的正文内容
mail_content = "你好,<p>这是使用python登录qq邮箱发送HTML格式邮件的测试:</p><p><a href='http://www.baidu.com'>baidu</a></p><img src='https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583990072584&di=cfc04c054a9f33fb000fd4f207c70ff9&imgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20190715%2F0c2b2d67886c4d72a90fee16e3fc8659.jpeg'></p>"
# 邮件标题
mail_title = 'Python测试HTML邮件'

# ssl登录
smtp = SMTP_SSL(host_server)
# set_debuglevel()是用来调试的。参数值为1表示开启调试模式,参数值为0关闭调试模式
smtp.set_debuglevel(1)
smtp.ehlo(host_server)
smtp.login(sender_qq, pwd)

msg = MIMEText(mail_content, "html", 'utf-8')
msg["Subject"] = Header(mail_title, 'utf-8') # 发送者别名
msg["From"] = sender_qq_mail
msg["To"] = Header("receiver test", 'utf-8')  # 接收者的别名

smtp.sendmail(sender_qq_mail, receiver, msg.as_string())
smtp.quit()

  • 发送带附件的邮件
    发送带附件的邮件,首先要创建MIMEMultipart()实例,然后构造附件,如果有多个附件,可依次构造,最后使用smtplib.smtp发送:
#! /usr/bin/env python
# coding=utf-8
import smtplib
from email.mime.text import MIMEText
from email.header import Header
from smtplib import SMTP_SSL

from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.header import Header

# qq邮箱smtp服务器
host_server = 'smtp.qq.com'
# sender_qq为发件人的qq号码
sender_qq = '...@qq.com'
# pwd为qq邮箱的授权码
pwd = 'nz...chf'
# 发件人的邮箱
sender_qq_mail = '...@qq.com'
# 收件人邮箱
with open('D:/code/PycharmProjects/PythonStudy/venv/Include/电子邮件/email.txt') as f:
    for email in f.readlines():
        emails = email.strip(',')

receiver = list(emails.split(','))

# 邮件的正文内容
mail_content = "你好,<p>这是使用python登录qq邮箱发送HTML格式邮件的测试</p><img src='https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1583990072584&di=cfc04c054a9f33fb000fd4f207c70ff9&imgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20190715%2F0c2b2d67886c4d72a90fee16e3fc8659.jpeg'></p>"
# 邮件标题
mail_title = 'Python测试HTML邮件'

# 邮件正文内容
msg = MIMEMultipart()
msg["Subject"] = Header(mail_title, 'utf-8')
msg["From"] = sender_qq_mail
msg["To"] = Header("receiver test", 'utf-8')  ## 接收者的别名
msg.attach(MIMEText(mail_content, 'html', 'utf-8'))

# 构造附件1,传送当前目录下的 email.txt 文件
att1 = MIMEText(open('email.txt', 'rb').read(), 'base64', 'utf-8')
att1["Content-Type"] = 'application/octet-stream'
# 这里的filename可以任意写,写什么名字,邮件中显示什么名字
att1["Content-Disposition"] = 'attachment; filename="email.txt"'
msg.attach(att1)

# 构造附件2,传送当前目录下的 demo.txt 文件
att2 = MIMEText(open('demo.txt', 'rb').read(), 'base64', 'utf-8')
att2["Content-Type"] = 'application/octet-stream'
att2["Content-Disposition"] = 'attachment; filename="demo.txt"'
msg.attach(att2)

# ssl登录
smtp = SMTP_SSL(host_server)
# set_debuglevel()是用来调试的。参数值为1表示开启调试模式,参数值为0关闭调试模式
smtp.set_debuglevel(1)
smtp.ehlo(host_server)
smtp.login(sender_qq, pwd)

smtp.sendmail(sender_qq_mail, receiver, msg.as_string())
smtp.quit()

POP3收取邮件

Python内置一个poplib模块,实现了POP3协议,可以直接用来收邮件

  • 收取邮件分两步:
    1、用poplib把邮件的原始文本下载到本地;
    2、用email解析原始文本,还原为邮件对象。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from email.parser import Parser
from email.header import decode_header
from email.utils import parseaddr

import poplib

# 输入邮件地址, 口令和POP3服务器地址:
email = '...@qq.com'
password = 'nzii...bchf'
pop3_server = 'smtp.qq.com'


def guess_charset(msg):
	# 得到字符集
    charset = msg.get_charset()
    if charset is None:
    	 # lower:所有大写字符为小写
        content_type = msg.get('Content-Type', '').lower()
        # find:检测字符串中是否包含子字符串
        # 返回charset=头字符的位置
        pos = content_type.find('charset=')
        if pos >= 0:
        	# strip:移除字符串头尾指定的字符(默认为空格)
            charset = content_type[pos + 8:].strip()
    return charset


def decode_str(s):
    value, charset = decode_header(s)[0]
    if charset:
        value = value.decode(charset)
    return value

# 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:
                	# parseaddr:解析字符串中的email地址
                    hdr, addr = parseaddr(value)
                    # 解码主题
                    name = decode_str(hdr)
                     # 合成内容
                    value = u'%s <%s>' % (name, addr)
            print('%s%s: %s' % ('  ' * indent, header, value))
     # 如果消息由多个部分组成,则返回True
    if (msg.is_multipart()):
    	# 返回list,包含所有的子对象
        parts = msg.get_payload()
        # enumerate将其组成一个索引序列,利用它可以同时获得索引和值
        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':
        	 # 返回list,包含所有的子对象,开启解码
            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))


# 连接到POP3服务器:
server = poplib.POP3_SSL(pop3_server, port=995)
# 可以打开或关闭调试信息:
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)

if __name__ == '__main__':
    print_info(msg)
# 可以根据邮件索引号直接从服务器删除邮件:
# server.dele(index)
# 关闭连接:
server.quit()

访问数据库

使用MySQL

  • 安装
    pip install pymysql
import pymysql

# 打开数据库
conn = pymysql.connect('localhost', 'root', 'root', 'demo')

# 游标对象
cur = conn.cursor()

# -------插入数据--------
sql_i = "INSERT INTO `student` (`uid`, `name`, `code`, `create_date`, `last_date`) VALUES ('uid4', 'dfdsf', '3254645', '2020-03-12', '2020-03-12')"
# -------删除数据--------
sql_d = "DELETE FROM `student` WHERE `uid` = 'uid3' "
# -------更新数据--------
sql_u = "UPDATE student SET name = 'eureka' WHERE uid = 'uid1'"
# -------查询数据--------
sql_q = "SELECT * FROM student"

try:
    # cur.execute(sql_i)
    # cur.execute(sql_d)
    cur.execute(sql_u)
    # 提交事务
    conn.commit()
    print('成功')

    # 查询
    cur.execute(sql_q)
    results = cur.fetchall()  # 查询所有
    # 遍历结果
    for row in results:
        id = row[0]
        uid = row[1]
        name = row[2]
        code = row[3]
        create_date = row[4]
        last_date = row[5]
        print(id, uid, name, code, create_date, last_date)
except Exception as e:
    # 错误回滚
    conn.rollback()
finally:
    # 关闭Cursor和Connection
    conn.close()
    cur.close()

使用Oracle

  • 安装 pip install cx_Oracle
import cx_Oracle

# 连接数据库
conn = cx_Oracle.connect('scott/scott@localhost/ORCL')
# 游标对象
cursor = conn.cursor()

cursor.execute("SELECT * FROM DEPT")
rows = cursor.fetchall()  # 得到所有数据集
for row in rows:
    print("%d, %s, %s" % (row[0], row[1], row[2]))  # python3以上版本中print()要加括号用了

print("Number of rows returned: %d" % cursor.rowcount)

cursor.execute("SELECT * FROM DEPT")
while (True):
    row = cursor.fetchone()  # 逐行得到数据集
    if row == None:
        break
    print("%d, %s, %s" % (row[0], row[1], row[2]))

print("Number of rows returned: %d" % cursor.rowcount)

cursor.close()
conn.close()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值