Python 笔记

Python 笔记

基础

流程控制

if

1
2
3
if 条件1:
elif 条件2:
else:

if实现的三目运算符

1
a = b if a > b else c

字符串

格式化字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
print(1)
print(1,2,3,4)

a = 1
b = 2.1123
c = 'hello'
s = 'a = %d b = %f c = %s' % (a,b,c)

s += ' -- world'

print(s)

s = f'a = {a} b = {b} c = {c}'
print(s)

s = 'a = {0:5d} b = {1:.2f} c = {0}'.format(a,b,c)
print(s)

字符串切片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 字符串切片
s = '0123456789'

print(s[0:5:1])
print(s[0:5:2])
print(s[3:6]) # 默认步长可以不写,默认为1
print(s[:5]) # 开始索引也可以不写,默认从头开始
print(s[5:]) # 结束也可以不写,默认到最后
print(s[:]) # 全默认,默认截取整串
print(s)
print(s[10:20]) # 切片时不会出现下标越界错误


# 切片的下标还可是以负数
# 负数是,是从右向左切片,起始下标为 -1
print(s[-1:-5])
print(s[-1:-5:-1])

# 特殊需要记住的切片方式
# 使用切片实现字符串逆序
print(s[::-1])

列表

列表推导式

1
2
格式: 列表变量 = [表达式 for 变量 in range(10)]
表达式中需要使用后面的变量

函数

不定长参数

1
2
3
def some_func (*args):
print(args)
print(*args) # 手动解包

关键字参数

1
2
3
def some_func (arg1, arg2=1):
pass
some_func(1, arg2 = 2)

不定长关键字参数

1
2
3
def some_func (**kwargs):
print(kwargs) # {'args1': 1, 'arg2': 2}
some_func(arg1=1, arg2 = 2)

多种参数混合

1
2
3
4
def some_func4 (arg1, arg2, arg3, *args, arg4=1, arg5=2, **kwargs):
print(arg1, arg2, arg3, ,arg4, arg5)
print(args)
print(kwargs)

作用域

Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这些语句内定义的变量,外部也可以访问,如下代码:

1
2
3
4
5
6
>>> if True:
... msg = 'I am from Runoob'
...
>>> msg
'I am from Runoob'
>>>

匿名函数 lambda

格式:
lambda [形参1], [形参2], … : [单行表达式] 或 [函数调用]

lambda定义和普通函数的区别:

  1. lambda 没有函数名
  2. lambda 参数列表外没有括号
  3. lambda 函数体中,只能实现简单的表达式计算或函数调用
  4. lambda 函数体中,不能使用Return,if,while,for-in 这些都不行
  5. lambda 函数体中,可以使用if 实现的三目运算符.

使用场景:
变量 = lambda ….

一般情况下,因为lambda的局限性,使得他不能实现复杂功能,只能实现一些简单功能
那么在使用时,一般会实现一个简单的,一次性使用的场景

使用场景

1
2
3
4
#import funtools
list = [1,2,3,4,5]
result = funtools.reduce(lamdba a,b: a + b, list) # 对列表进行累加
result = map(lamdba a: a ** 2, list) # map 数组平方

高阶函数

reduce

函数会对参数序列中元素进行累积

1
2
#import funtools
result = funtools.reduce(lamdba a,b: a + b, list) # 对列表进行累加

map

1
result = map(lamdba a: a ** 2, list) # map 数组平方

filter

1
2
3
list = [1,2,3,4,5,6]
ret = filter(lamdba a: a%2==0, list)
print(list(ret))

sort

1
2
3
4
5
6
7
8
9
10
11
12
13
list = [7,2,3,4,2,1,2]
list.sort()

my_list = [{'id': 1,'name': 'tom','age':12},{'id': 3,'name': 'rose','age':32},{'id': 2,'name': 'Jack','age':22}]

# 默认sort方法是不能对字典进行比较排序的 ,TypeError: '<' not supported between instances of 'dict' and 'dict'

# 按id的升序排序
# my_list.sort(key=lambda d: d['id'])
# 按年龄降序排序
my_list.sort(key=lambda d: d['age'],reverse=True)

print(my_list)

私有属性和方法

1
2
3
4
5
6
7
8
class Person(object):
def __init__(self, name, age):
self.name = name # 共有属性
self.__age = age # __开头的方法是私有方法

# __开头的方法是私有方法
def __some_func(self):
pass

继承

python支持多继承

1
2
3
4
5
class 类名(父类名1,.....):
pass

# 类名.__mro__ 得到了一个元组,元组中的元素是当前类在继承关系上的一个顺序
# super() 表示父类 会按照__mro__的顺序进行查找

类方法和静态方法

1
2
3
4
5
6
7
8
9
# @classmethod  是一个装饰器,用来修饰一个方法成为类方法,当在执行该 类方法时,解释 会自动 将类对象传递到参数 cls中
@classmethod
def some_method():
pass

# 静态方法 实际上就是放到类当中的一堆普通函数
@staticmethod
def some_method():
pass

导入模块

导入单独模块

1
2
3
4
5
6
7
8
# 方式一:
import 模块名
import 模块名 as 别名

# 方式二:
from 模块名 import 成员名
from 模块名 import 成员名 as 成员别名
from 模块名 import *

导入包中的模块

1
2
3
4
5
6
7
import 包名.模块名    ->   包名.模块名.成员
from 包名 import 模块名 -> 模块名.成员
from 包名.模块名 import 成员名 -> 直接使用成员
from 包名.模块名 import * -> 直接使用成员

import 包名
from 包名 import *

模块搜索路径

a. 当前程序所在同级目录
b. 当前程序所在工程的根目录
c. PYTHONPATH 环境变量中设置的目录
d. 系统目录
e. site-packages 这个第三方模块安装目录

包就是一个文件夹
包和普通文件夹的功能在于 包中有一个 init.py 文件 (导入包时,指定可以导入的模块有哪些)

__name__

如果__name__ 出现在当前执行的模块中时,得到的值是 main 这个字符串,用来表示执行的是当前文件
如果__name__ 出现在在了被导入模块中时,得到的值是被导入模块的模块名

异常

异常捕获

1
2
3
4
5
try:
except Exception: # except (可能出现的异常类型)
# except (Exception,... )as e:
else: # 没有异常时执行
finally:

自定义异常

1
2
3
4
5
6
7
8
9
# 格式
class 异常名Error(Exception):
def __init__(self,msg=''):
self.__msg = msg

def __str__(self):
return self.__msg

raise 异常对象 # 抛出异常

文件读写

文件的读写方式

以文本方式打开方式的模式
r -> rt -> read text 以文本方式打开文件读,文件存在,打开成功,文件不存在,打开失败
w -> wt -> write text 以文本方式打开文件写,不管文件是否存在,都会新创建一个新文件
a -> at -> append text 以文件方式打开文件追加,文件不存在,创建文件,文件存在,那么打开文件然后将光标移动到文件的最后

以二进制形式打开文件的模式
rb 以二进制形式打开文件读取
wb 以二进制形式打开文件写入
ab 以二进制形式打开文件追加

(了解)
r+ 可读可写 不会创建不存在的⽂件 从顶部开始写 会覆盖之前此位置的内容
w+ 可读可写 如果⽂件存在 则覆盖整个⽂件不存在则创建
a+ 可读可写 从⽂件顶部读取内容 从⽂件底部添加内容 不存在则创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
file = open('static' + path, 'rb') # 打开文件
file.read() # 读取文件
file.write('内容') # 写入文件
file.close() # 关闭文件

# 文件的读取
read() # 一次性将整个文件内容读取出来,不适合大文件读取
read(size) # 掌握 读取指定的字节数的文件内容 ,如果文件最后没有内容了,那么read的返回值是空字符串 , "" 一般指定4096
readline() # 读取一行
readlines() # 一次性将整个文件内的内容以行的形式读取出来,放到一个列表中

# 文件的写入
write(content) 该函数用来向文件中写入数据
前提: 使用该函数时,打开文件时,必须要给定写入权限,要具有写入模式

with as 打开文件

使用 with as 操作已经打开的文件对象(本身就是上下文管理器),无论期间是否抛出异常,都能保证 with as 语句执行完毕后自动关闭已经打开的文件。

1
2
with open('a.txt', 'a') as f:
f.write("文件内容")

文件和文件夹操作

1
2
3
4
5
6
7
8
os 模块
rename()
remove()
mkdir()
getcwd()
chdir()
listdir() 掌握
rmdir()

注意点

当以二进制模式打开文件进行文件操作时.
read 函数最终读取文件内容为空时,返回的结果为 b’’
表示是一个二进制的空字符串
在 Python2.7版本中. ‘’ == b’’ 结果为True
在 Python3.6版本中. ‘’ == b’’ 结果为False

多任务

进程

一个程序运行后至少有一个进程,一个进程默认有一个线程,进程里面可以创建多个线程,线程是依附在进程里面的,没有进程就没有线程。

进程简单用法

1
2
3
4
5
6
import multiprocessing

# 创建子进程并指定执行的任务
sub_process = multiprocessing.Process (target=任务名)
# 启动进程执行任务
sub_process.start()

Process进程类的说明

Process([group [, target [, name [, args [, kwargs]]]]])

  • group:指定进程组,目前只能使用None
  • target:执行的目标任务名
  • name:进程名字
  • args:以元组方式给执行任务传参
  • kwargs:以字典方式给执行任务传参

Process创建的实例对象的常用方法:

  • start():启动子进程实例(创建子进程)
  • join():等待子进程执行结束
  • terminate():不管任务是否完成,立即终止子进程

Process创建的实例对象的常用属性:

name:当前进程的别名,默认为Process-N,N为从1开始递增的整数

多进程示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import multiprocessing
import time


# 跳舞任务
def dance():
for i in range(5):
print("跳舞中...")
time.sleep(0.2)


# 唱歌任务
def sing():
for i in range(5):
print("唱歌中...")
time.sleep(0.2)

if __name__ == '__main__':
# 创建跳舞的子进程
# group: 表示进程组,目前只能使用None
# target: 表示执行的目标任务名(函数名、方法名)
# name: 进程名称, 默认是Process-1, .....
dance_process = multiprocessing.Process(target=dance, name="myprocess1")
sing_process = multiprocessing.Process(target=sing)

# 启动子进程执行对应的任务
dance_process.start()
sing_process.start()

获取当前进程信息

1
2
3
4
5
6
# 获取当前进程对象
multiprocessing.current_process()
# 获取当前进程id
os.getpid()
# 获取当前父进程id
os.getppid()

进程的传参方式

  • 元组方式传参(args): 元组方式传参一定要和参数的顺序保持一致。
  • 字典方式传参(kwargs): 字典方式传参字典中的key一定要和参数名保持一致。
1
2
3
4
5
# 元组传参方式
sub_process = multiprocessing.Process(target=task, args=(5,))

# 字典传参方式
sub_process = multiprocessing.Process(target=task, kwargs={"count": 3})

进程注意点

  1. 进程之间不共享全局变量
  2. 主进程会等待所有的子进程执行结束再结束

主进程退出时销毁子进程

  • 主进程结束时手动销毁子进程

    sub_process.terminate()

  • 设置守护主进程

    子进程对象.daemon = True

线程

线程是cpu调度的基本单位,每个进程至少都有一个线程,而这个线程就是我们通常说的主线程。

线程的简单用法

1
2
3
4
5
# 导入线程模块
import threading

thread = threading.Thread(target=任务名)
thread.start()

线程类Thread参数说明

Thread([group [, target [, name [, args [, kwargs]]]]])

  • group: 线程组,目前只能使用None
  • target: 执行的目标任务名
  • args: 以元组的方式给执行任务传参
  • kwargs: 以字典方式给执行任务传参
  • name: 线程名,一般不用设置

线程的传参方式

  • 元组方式传参(args) :元组方式传参一定要和参数的顺序保持一致。
  • **字典方式传参(kwargs)**:字典方式传参字典中的key一定要和参数名保持一致。

线程注意点

  1. 线程之间执行是无序的
  2. 主线程会等待所有的子线程执行结束再结束
  3. 线程之间共享全局变量
  4. 线程之间共享全局变量数据出现错误问题

设置守护线程

  1. threading.Thread(target=someTarget, daemon=True)
  2. 线程对象.setDaemon(True)

线程之间共享全局变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 定义全局变量
data_list = list()

# 写入数据任务
def write_data():
data_list.append('数据')

# 读取数据任务
def read_data():
print("read_data:", data_list)

if __name__ == '__main__':
# 创建写入数据的线程
write_thread = threading.Thread(target=write_data)
# 创建读取数据的线程
read_thread = threading.Thread(target=read_data)

互斥锁

对共享数据进行锁定,保证同一时刻只能有一个线程去操作。

1
2
3
4
5
6
7
8
9
10
# 创建锁
mutex = threading.Lock()

# 上锁
mutex.acquire()

...这里编写代码能保证同一时刻只能有一个线程去操作, 对共享数据进行锁定...

# 释放锁
mutex.release()

使用互斥锁需避免死锁,加锁后必须解锁

进程和线程对比

关系对比

  1. 线程是依附在进程里面的,没有进程就没有线程。
  2. 一个进程默认提供一条线程,进程可以创建多个线程。

区别对比

  1. 进程之间不共享全局变量
  2. 线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步
  3. 创建进程的资源开销要比创建线程的资源开销要大
  4. 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
  5. 线程不能够独立执行,必须依存在进程中
  6. 多进程开发比单进程多线程开发稳定性要强

优缺点对比

  • 进程优缺点:
    • 优点:可以用多核
    • 缺点:资源开销大
  • 线程优缺点:
    • 优点:资源开销小
    • 缺点:不能使用多核

小结

  • 进程和线程都是完成多任务的一种方式
  • 多进程要比多线程消耗的资源多,但是多进程开发比单进程多线程开发稳定性要强,某个进程挂掉不会影响其它进程。
  • 多进程可以使用cpu的多核运行,多线程可以共享全局变量。
  • 线程不能单独执行必须依附在进程里面

网络编程

客户端流程

  1. 创建客户端套接字对象
  2. 和服务端套接字建立连接
  3. 发送数据
  4. 接收数据
  5. 关闭客户端套接字

服务端流程

  1. 创建服务端端套接字对象
  2. 绑定端口号
  3. 设置监听
  4. 等待接受客户端的连接请求
  5. 接收数据
  6. 发送数据
  7. 关闭套接字

socket

导入 socket 模块

import socket

创建客户端 socket 对象

socket.socket(AddressFamily, Type)

参数说明:

  • AddressFamily 表示IP地址类型, 分为IPv4和IPv6
  • Type 表示传输协议类型

方法说明:

  • connect((host, port)) 表示和服务端套接字建立连接, host是服务器ip地址,port是应用程序的端口号
  • send(data) 表示发送数据,data是二进制数据
  • recv(buffersize) 表示接收数据, buffersize是每次接收数据的长度

客户端示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
'''
基于socket实现的TCP客户端
'''

# 1. 导入 socket 模块
import socket

# 2. 建立socket 对象
# socket 在初始时的参数:
# 参数一表示使用的IP地址类型
# AF_INET 表示使用的是 IPV4
# AF_INET6 表示使用的是 IPV6
# 参数二表示的是连接的类型
# socket.SOCK_STREAM 表示是以TCP形式创建socket
# socket.SOCK_DGRAM 表示是以UDP形式创建socket(了解)

client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

# 3. 连接服务器
# 参数虽然显示只是address( 包含两项,分别是 ip 和 port)
client_socket.connect(('192.168.13.73',6666))

# 4. 准备数据
data = 'hello,你好'

# 5. 因为准备的数据是str类型,数据是utf-8编码的,需要转换成二进制数据
# 所以需要对数据进行编码 encode()
# 如果 在测试时,接收服务端在win系统上,那么这里编码需要使用 gbk 编码
# 如果是mac,linux, 默认使用就是 utf-8
data = data.encode()
# data = data.encode('utf-8')

# 6. 向服务器发送数据
client_socket.send(data)


# 7. 接收数据
# recv 方法要指定接收数据的大小,单位字节,一般最大写 4096
recv_data = client_socket.recv(1024)
print('未解码:', recv_data)
# 7.1 因为接收的数据是二进制,所以,收完的数据需使用 decode()解码,注意解码时的编码格式
recv_data = recv_data.decode('utf-8')
# recv_data = recv_data.decode('gbk')
print('解码后:', recv_data)


# 8. 判断连接
client_socket.close()

服务端示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
'''
基于socket实现的TCP服务端

'''

# 1. 导入模块
import socket
# from socket import socket

# 2. 建立socket 对象
server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 2.1 因为目前的服务器是一次性的,如果马上重新服务器时,会出现一个错误,原因是地址和端口没有被释放
# OSError: [Errno 48] Address already in use
# 如果想马上释放,要设置一下 socket 选项
server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)

# 3. 绑定IP和端口
# 如果在绑定ip时,没有给定ip,默认绑定的是本地ip地址
server_socket.bind(('',7777))

# 4. 设置监听
server_socket.listen(128)

# 5. 等待客户端连接
# 如果客户连接上来后,该函数会返回两个数据,一个是客户端的Socket对象,另一个是客户端的地址信息
client_socket, ip_port = server_socket.accept()
print(f'客户端 {ip_port[0]} 使用端口 {ip_port[1]} 连接成功...')

# 6.接收客户端的数据
data = client_socket.recv(1024)

# 看一下客户端发来数据的长度
if len(data) != 0:
data = data.decode('utf-8')
print(f'客户端 {ip_port[0]} 发送的数据是 {data} ...')
else:
print(f'客户端 {ip_port[0]} 关闭了连接...')


# 7. 给客户端发送数据

data = '你好也'.encode('utf-8')
client_socket.send(data)


# 8. 关闭客户端
client_socket.close()
# 9. 关闭服务端
server_socket.close()

多进程http服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
'''
使用进程实现多任务面向对象返回指定数据的静态web服务器
'''
import socket
import multiprocessing



class StaticWebServer(object):

def __init__(self,port):
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
self.server.bind(('', port))
self.server.listen(128)

def start(self):
while True:
client, ip_port = self.server.accept()
print(f'客户端 {ip_port[0]} 使用 {ip_port[1]} 端口连接成功....')

# 实现多任务
p = multiprocessing.Process(target=self.task,args=(client,))
p.start()

# 因为是死循环,所以执行不到
self.server.close()



def task(self, client):

request_data = client.recv(1024).decode()
# 判断一下接收数据的长度,如果长度为0,直接关闭掉这次请求
if len(request_data) == 0:
client.close()

else:

# 分割请求报文,拿到请求地址,第二项就是我们要的请求地址
request_path = request_data.split(' ')[1]
print('请求地址是:', request_path)

# 判断是否是 /
if request_path == '/':
request_path = '/index.html'


# 读取文件,但有可能指定的文件不存在
try:
with open('static' + request_path, 'rb') as file:
file_content = file.read()

except Exception as e:
# 文件找不到,拼接404的异常报文
# 响应行
response_line = 'HTTP/1.1 404 NOT FOUND\r\n'
# 响应头
response_head = 'Server: PSWS1.1\r\n'
# 响应体
# 因为 error.html 是纯文本,所以可以使用r模式读取
with open('static/error.html','r') as f:
error_data = f.read()

# 拼接响应报文
response_data = (response_line + response_head + '\r\n' + error_data).encode()

# 发送给客户端
client.send(response_data)

else:
# 找到文件后的报文
# 响应行
response_line = 'HTTP/1.1 200 OK\r\n'
# 响应头
response_head = 'Server: PSWS1.1\r\n'
# 响应体
# 因为 error.html 是纯文本,所以可以使用r模式读取
with open('static' + request_path, 'rb') as f:
response_body = f.read()

# 拼接响应报文
response_data = (response_line + response_head + '\r\n').encode() + response_body

# 发送给客户端
client.send(response_data)

finally:
# 关闭客户连接
client.close()






if __name__ == '__main__':
# 创建一个服务器对象并启动
StaticWebServer(8888).start()