08-27-周三_17-09-29

This commit is contained in:
2025-08-27 17:10:05 +08:00
commit 86df397d8f
12735 changed files with 1145479 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -0,0 +1,231 @@
# Flask简介
`flask`是一款非常流行的`Python Web`框架出生于2010年作者是`Armin Ronacher`,本来这个项目只是作者在愚人节的一个玩笑,后来由于非常受欢迎,进而成为一个正式的项目。
`flask`自2010年发布第一个版本以来大受欢迎深得开发者的喜爱目前在`Github`上的Star数已经超过`55.5k`了,有超`Django`之趋势。`flask`能如此流行的原因,可以分为以下几点:
- 微框架、简洁、只做他需要做的,给开发者提供了很大的扩展性。
- Flask和相应的插件写得很好用起来很爽。
- 开发效率非常高,比如使用`SQLAlchemy``ORM`操作数据库可以节省开发者大量书写`sql`的时间。
`Flask`的灵活度非常之高,他不会帮你做太多的决策,一些你都可以按照自己的意愿进行更改。比如:
- 使用`Flask`开发数据库的时候,具体是使用`SQLAlchemy`还是`MongoEngine`,选择权完全掌握在你自己的手中。区别于`Django``Django`内置了非常完善和丰富的功能,并且如果你想替换成你自己想要的,要么不支持,要么非常麻烦。
- 把默认的`Jinija2`模板引擎替换成其他模板引擎都是非常容易的。
# 安装flask
在pycharm中可以在如下位置安装
<img src="01.URL与视图.assets/image-20240420214815182.png" alt="image-20240420214815182" style="zoom: 67%;" />
在命令行中可以输入如下命令安装
```
pip install flask==2.0.3
```
# Flask项目
## 运行helloworld
```python
import flask
# 导入flask框架
app = flask.Flask(__name__)
# 传入__name__初始化一个Flask实例
@app.route('/')
# app.route装饰器映射URL和执行的函数。这个设置将根URL映射到了hello_world函数上
def hello_world():
return 'Hello'
# 返回的内容就会呈现到浏览器上
if __name__ == '__main__':
# 运行本项目host=0.0.0.0可以让其他电脑也能访问到该网站port指定访问的端口。默认的host是127.0.0.1port为5000
app.run(debug=True,host='0.0.0.0',port=8080)
# 在pycharm中debughostport等代码中的设置无效
```
如果在pycharm中想要修改调试的地址和端口可以进行如下设置
<img src="01.URL与视图.assets/image-20240420214843541.png" alt="image-20240420214843541" style="zoom:80%;" />
# URL与视图
## URL与函数的映射
一个`URL`要与执行函数进行映射,使用的是`@app.route`装饰器。`@app.route`装饰器中,可以指定`URL`的规则来进行更加详细的映射,比如现在要映射一个文章详情的`URL`,文章详情的`URL``/article/id/`id有可能为1、2、3…,那么可以通过以下方式:
```python
@app.route('/article/<id>/')
# 可以用<xxx>来读取对应的内容
def article(id):
return f'{id}article detail'
```
<img src="01.URL与视图.assets/image-20240420214928488.png" alt="image-20240420214928488" style="zoom:80%;" />
其中`<id>`,尖括号是固定写法,语法为`<variable>``variable`默认的数据类型是字符串。如果需要指定类型,则要写成`<converter:variable>`,其中`converter`就是类型名称,可以有以下几种:
- string: 默认的数据类型,接受没有任何斜杠`/`的字符串。
- int: 整形
- float: 浮点型。
- path`string`类似,但是可以传递斜杠`/`
- uuid `uuid`类型的字符串。
- any可以指定多种路径
举例说明
```python
@app.route('/guess/<int:num>')
# 由于指定了int类型所以num可以直接和整数型比较大小
def guess(num):
if num < 66:
return "你猜的数字小了"
elif num > 66:
return "你猜的数字大了"
else:
return "你猜对了"
```
访问结果
<img src="01.URL与视图.assets/image-20240420214957595.png" alt="image-20240420214957595" style="zoom:80%;" />
如果传入一个不是int类型的就无法触发这个路由就会返回404
<img src="01.URL与视图.assets/image-20240420215027672.png" alt="image-20240420215027672" style="zoom:80%;" />
关于any类型可以看下面这个案例
```python
@app.route('/user/<any(article,blog):url_path>')
# 在/user/后面只能是article和blog二选一并且会被传递到url_path变量
def url_path(url_path):
return f"你输入的是{url_path},此处只能是article或者blog"
```
<img src="01.URL与视图.assets/image-20240420215048355.png" alt="image-20240420215048355" style="zoom: 80%;" />
如果输入了别的字符串就会返回404
<img src="01.URL与视图.assets/image-20240420215308782.png" alt="image-20240420215308782" style="zoom:80%;" />
## 构造URL
一般我们通过一个`URL`就可以执行到某一个函数。如果反过来,我们知道一个函数,怎么去获得这个`URL`呢?`url_for`函数就可以帮我们实现这个功能。`url_for()`函数接收两个及以上的参数,他接收**函数名**作为第一个参数,接收对应**URL规则的命名参数**,如果还出现其他的参数,则会添加到`URL`的后面作为**查询参数**。
通过构建`URL`的方式而选择直接在代码中拼`URL`的原因有两点:
1. 将来如果修改了`URL`,但没有修改该`URL`对应的函数名,就不用到处去替换`URL`了。
2. `url_for()`函数会转义一些特殊字符和`unicode`字符串,这些事情`url_for`会自动的帮我们搞定。
下面用一个例子来进行解释:
```python
from flask import Flask,url_for
app = Flask(__name__)
@app.route('/article/<id>/')
def article(id):
return '%s article detail' % id
@app.route('/')
def index():
return url_for("article",id=1)
# 可以使用url_for来帮我们构造一个url会帮助我们检查此url的合法性
```
在访问的时候,会打印出`/article/1/`
## 指定URL末尾的斜杠
当访问一个结尾不带斜线的`URL``/article`,会被重定向到带斜线的`URL``/article/`上去。但是当我们在定义`article``url`的时候,如果在末尾没有加上斜杠,但是在访问的时候又加上了斜杠,这时候就会抛出一个`404`错误页面了:
```python
@app.route('/test')
def haha():
return '你好'
```
<img src="01.URL与视图.assets/image-20240420215354048.png" alt="image-20240420215354048" style="zoom:80%;" />
## 指定HTTP方法
`@app.route()`中可以传入一个关键字参数`methods`来指定本方法支持的`HTTP`方法,默认情况下,只能使用`GET`请求,看以下例子:
```python
@app.route('/search/')
def search():
# return f"你输入的要搜索的东西是{request.args['kw']}"
return f"你输入的要搜索的东西是{request.args.get('kw')}"
# 可以和字典一样去取对应的值
# http://xxxx:5000/search?kw=xxx
```
如果使用post提交就会触发错误
<img src="01.URL与视图.assets/image-20240420215422307.png" alt="image-20240420215422307" style="zoom:80%;" />
如果想要使用post请求可以看如下例子
```python
from flask import Flask,request
app = Flask(__name__)
@app.route('/login/',methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
if username == 'admin' and password == '123456':
return 'Logged in successfully'
else:
return 'Invalid username or password'
```
尝试登陆一下
<img src="01.URL与视图.assets/image-20240420215450240.png" alt="image-20240420215450240" style="zoom:80%;" />
## 页面跳转和重定向
重定向分为永久性重定向和暂时性重定向,在页面上体现的操作就是浏览器会从一个页面自动跳转到另外一个页面。比如用户访问了一个需要权限的页面,但是该用户当前并没有登录,因此我们应该给他重定向到登录页面。
- 永久性重定向:`http`的状态码是`301`,多用于旧网址被废弃了要转到一个新的网址确保用户的访问,最经典的就是京东网站,你输入`www.jingdong.com`的时候,会被重定向到`www.jd.com`,因为`jingdong.com`这个网址已经被废弃了,被改成`jd.com`,所以这种情况下应该用永久重定向。
- 暂时性重定向:`http`的状态码是`302`,表示页面的暂时性跳转。比如访问一个需要权限的网址,如果当前用户没有登录,应该重定向到登录页面,这种情况下,应该用暂时性重定向。
`flask`中,重定向是通过`flask.redirect(location,code=302)`这个函数来实现的,`location`表示需要重定向到的`URL`,应该配合之前讲的`url_for()`函数来使用,`code`表示采用哪个重定向,默认是`302`也即`暂时性重定向`,可以修改成`301`来实现永久性重定向。
以下来看一个例子,关于在`flask`中怎么使用重定向:
```python
from flask import Flask, request, session, redirect, url_for
app = Flask(__name__)
app.secret_key = '啦啦啦,我是买糕的小行家'
# 使用session的话需要配置一个secret_key内容随意最好别人想不到
@app.route('/login/',methods=['POST','GET'])
def login():
if 'username' not in request.form and 'password' not in request.form:
# 如果没有携带用户名和密码,就显示请登陆
return "请登陆"
username = request.form['username']
password = request.form['password']
if username == 'admin' and password == '123456':
session['username'] = 'admin'
# 登陆成功之后服务器存储session值
return 'Logged in successfully'
else:
return 'Invalid username or password'
@app.route("/profile/",methods=['GET'])
def profile():
if 'username' in session:
return f"欢迎用户{session['username']}"
# 如果已经登陆,就欢迎
else:
return redirect(url_for('login'))
# 如果cookie中没有合法的session值就重定向去登陆
```

View File

@@ -0,0 +1,244 @@
# 自定义web框架
web应用本质上就是一个socket服务端浏览器是socket客户端基于请求做出响应客户都先请求服务端做出对应的响应按照http协议的请求协议发送请求服务端按照http协议的响应协议来响应请求这样的网络通信我们就可以自己实现Web框架了。
准备一个html文件
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试页面</title>
</head>
<body>
<h1>标题1</h1>
<img src="https://iproute.cn/images/logo.png" alt="头像">
</body>
</html>
```
编写python的socket服务端
```python
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
conn,addr = sk.accept()
b_msg = conn.recv(1024)
str_msg = b_msg.decode('utf-8')
conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
conn.send(b'hello')
print(str_msg)
conn.close()
sk.close()
```
运行服务端之后,浏览器访问`http://127.0.0.1:8080` 浏览器传给socket的内容如下
```
GET / HTTP/1.1 # 请求行,其中的/是路径
Host: 127.0.0.1:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.116 Safari/537.36
Sec-Fetch-Dest: document
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8
Cookie: smid=sys4OxpO9GZqFqSAA37ZAWOnLB54KsilDWWuipaewHYE9ggajIeWtlHRrIBuSZyR038Q7r4c2XNUXdfdWR-I4A; APP_HOST=http%3A//127.0.0.1%3A49153/; HOST=http%3A//127.0.0.1%3A49153/; kodUserLanguage=zh-CN; kodUserID=1; X-CSRF-TOKEN=i9YQRqWXnS4Iy3uRt3vW; p_h5_u=057F0CAB-3644-4539-A2BC-249B82EA9934
```
修改socket服务端让其返回网页内容
```python
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
conn,addr = sk.accept()
b_msg = conn.recv(1024)
str_msg = b_msg.decode('utf-8')
print(str_msg)
conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
with open('test.html','rb') as f:
f_data = f.read()
conn.send(f_data)
conn.close()
sk.close()
```
这样就可以将网页内容返回给浏览器了
如果想要在网页中携带本地路径的图片那么修改html代码
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试页面</title>
</head>
<body>
<h1>标题1</h1>
<img src="logo.png" alt="头像">
</body>
</html>
```
然后修改socket服务端
```python
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
while 1: # 浏览器多次访问所以需要while
conn,addr = sk.accept()
b_msg = conn.recv(1024)
str_msg = b_msg.decode('utf-8')
path = str_msg.split('\r\n')[0].split(' ')[1]
print('path>>>',path)
conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
if path == '/':
with open('test.html','rb') as f:
f_data = f.read()
conn.send(f_data)
conn.close() # HTTP协议是短链接的一次请求对应一次响应这个请求就结束了所以我们需要写上close不然浏览器自己断了
elif path == '/logo.png':
with open('logo.png','rb') as f:
f_data = f.read()
conn.send(f_data)
conn.close()
sk.close()
```
可以改成使用函数的版本
```python
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
def func1(conn):
with open('test.html', 'rb') as f:
f_data = f.read()
conn.send(f_data)
conn.close()
def func2(conn):
with open('logo.png', 'rb') as f:
f_data = f.read()
conn.send(f_data)
conn.close()
while 1:
conn,addr = sk.accept()
b_msg = conn.recv(1024)
str_msg = b_msg.decode('utf-8')
path = str_msg.split('\r\n')[0].split(' ')[1]
print('path>>>',path)
conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
if path == '/':
func1(conn)
elif path == '/logo.png':
func2(conn)
sk.close()
```
现在还不支持高并发的情况,可以加上多线程
```python
import socket
from threading import Thread
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
def func1(conn):
with open('test.html', 'rb') as f:
f_data = f.read()
conn.send(f_data)
conn.close()
def func2(conn):
with open('logo.png', 'rb') as f:
f_data = f.read()
conn.send(f_data)
conn.close()
while 1:
conn,addr = sk.accept()
b_msg = conn.recv(1024)
str_msg = b_msg.decode('utf-8')
path = str_msg.split('\r\n')[0].split(' ')[1]
print('path>>>',path)
conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
if path == '/':
t = Thread(target=func1,args=(conn,))
t.start()
elif path == '/logo.png':
t = Thread(target=func2,args=(conn,))
t.start()
sk.close()
```
替换字符串,实现不同的时间访问返回时间戳模拟动态内容
在网页中,用特殊的符号`@@666@@`表示需要被替换掉的地方修改html代码
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>测试页面</title>
</head>
<body>
<h1>标题1</h1>
<img src="logo.png" alt="头像">
<h2>@@666@@</h2>
</body>
</html>
```
修改socket服务端
```python
import socket
from threading import Thread
import time
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
def func1(conn):
with open('test.html', 'r',encoding="utf-8") as f:
f_data = f.read()
now = str(time.time())
f_data = f_data.replace("@@666@@",now).encode('utf-8')
conn.send(f_data)
conn.close()
def func2(conn):
with open('logo.png', 'rb') as f:
f_data = f.read()
conn.send(f_data)
conn.close()
while 1:
conn,addr = sk.accept()
b_msg = conn.recv(1024)
str_msg = b_msg.decode('utf-8')
path = str_msg.split('\r\n')[0].split(' ')[1]
print('path>>>',path)
conn.send(b'HTTP/1.1 200 ok \r\n\r\n')
if path == '/':
t = Thread(target=func1,args=(conn,))
t.start()
elif path == '/logo.png':
t = Thread(target=func2,args=(conn,))
t.start()
sk.close()
```

8
Python/Python介绍.md Normal file
View File

@@ -0,0 +1,8 @@
Python是一种解释型、面向对象的高级编程语言以其简洁的语法和动态类型而闻名。它广泛应用于脚本编写、快速开发应用、系统运维、网络编程、Web开发等多个领域。Python的语法和动态类型是解释型语言的本质使其在多数平台上都能高效运行。
Python由吉多·范罗苏姆于1990年代初设计最初作为ABC语言的替代品。随着版本的不断更新和语言新功能的添加Python逐渐被用于独立的、大型项目的开发。Python解释器易于扩展可以使用C语言或C++扩展新的功能和数据类型。此外Python拥有丰富的标准库适用于各个主要系统平台。
Python的语法简单代码量少但功能强大这使得它成为许多开发者的首选语言。尽管Python的运行速度比C和C++慢但其强大的库支持和简单的语法使得开发效率大大提高。Python的应用范围广泛包括系统运维、图形处理、数学处理、文本处理、数据库编程、网络编程、Web编程、多媒体应用、爬虫编写、机器学习和人工智能等。
[Python官方文档](https://www.python.org/doc/)

View File

@@ -0,0 +1,602 @@
# 注释
单行注释:
```python
# 这是一条注释信息
```
多行注释:
```python
'''
这是多行注释
'''
"""
这是多行注释
"""
```
# 变量
变量就是把某些值或者运行的结果临时保存在内存中,以便后续调用。
## 强类型与弱类型
### 强类型
- **定义**:在强类型语言中,类型检查在编译时或运行时都很严格,变量的类型在定义时明确,且不允许隐式转换。
- **特征**
- 不同类型之间的操作会导致错误。
- 类型安全,高度限制了类型不兼容的操作。
- **示例语言**Java、C++、Python、Rust
**示例python**
```python
a = 10
b = "10"
print(a+b)
print(a == b)
# Output:
TypeError: unsupported operand type(s) for +: 'int' and 'str'
......
```
### 弱类型
- **定义**:在弱类型语言中,变量的数据类型可以在运行时发生变化,允许隐式转换。
- **特征**
- 不同类型之间的操作可能会被自动转换。
- 灵活性高,但可能导致运行时错误或意外结果。
- **示例语言**JavaScript、PHP、Ruby
**示例JavaScript**
```python
let a = "5";
let b = 10;
let result = a + b; // result "510"字符串拼接
```
### 对比
| 特性 | 强类型 | 弱类型 |
| :----------- | :----------------------- | :----------------------- |
| 类型检查 | 严格 | 灵活 |
| 隐式转换 | 不允许 | 允许 |
| 错误检测 | 通常在编译时或运行时 | 可能在运行时发现错误 |
| 类型安全 | 高 | 低 |
| 代码可维护性 | 高,类型明确易于理解 | 低,需要额外的类型检查 |
| 性能特征 | 通常性能较好,优化空间大 | 可能有性能开销,需要类型推断 |
## 动态类型与静态类型
### 动态类型
- **定义**:动态类型语言在运行时确定变量的类型,变量可以在程序执行过程中改变其类型。
- **特征**
- 变量不需要在声明时指定类型。
- 类型检查在运行时进行。
- 允许在同一变量中存储不同类型的数据。
- 示例语言JavaScript、Python、Ruby、PHP。
**示例python**
```python
a = 10 # 整数类型
print(a) # 输出: 10
a = "Hello" # 现在是字符串类型
print(a) # 输出: Hello
```
### 静态类型
- **定义**:静态类型语言在编译时确定变量的类型,变量的类型在声明时明确且在整个生命周期内保持不变。
- 特征:
- 变量必须在声明时指定类型。
- 类型检查在编译时进行。
- 不允许在同一变量中存储不同类型的数据。
- 示例语言Java、C、C++、Go、Rust。
**示例C**
```c
#include <stdio.h>
int main() {
int a = 10; // 整数类型
float b = 5.5; // 浮点类型
char c = 'A'; // 字符类型
printf("a = %d\n", a);
printf("b = %.2f\n", b);
printf("c = %c\n", c);
return 0;
}
```
### 对比
| 特性 | 动态类型 | 静态类型 |
| :----------- | :----------------------- | :----------------------- |
| 类型检查时机 | 运行时 | 编译时 |
| 类型声明 | 不需要显式声明 | 需要显式声明 |
| 类型灵活性 | 高,可以随时改变变量类型 | 低,类型在声明后固定 |
| 内存使用 | 可能较高,需要存储类型信息 | 通常较低,编译时确定 |
| 开发效率 | 较高,代码编写更灵活 | 前期较低,需要类型声明 |
| 调试难度 | 可能较高,运行时才发现错误 | 较低,编译时即可发现问题 |
| 性能特征 | 运行时开销较大 | 运行时性能通常更好 |
## 声明变量
```python
a = "Hello World!"
print(a)
```
## 变量命名约定
### Python 变量命名规则
- 只能由字母、数字和下划线组成
- 只能以字母或下划线开头,不能以数字开头
- 不区分大小写,但为了提高可读性,推荐使用小写字母命名,并用下划线分隔多个单词
- 应该具有描述性,以便能够清晰地表达变量的含义
- 不能使用 Python 的关键字,如下:
```python
'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except',
'exec', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'not',
'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield'
```
### 常见的命名方式
- 大驼峰命名法:将多个单词连接在一起,每个单词首字母大写,常用于类、函数和对象的命名。
```python
FirstName = '英格科技'
LastName = 'EaglesLab'
print("你的名字是:", FirstName + LastName)
```
- 下划线命名法:将多个单词连接在一起,使用下划线 `_` 分隔每个单词,所有字母小写,常用于变量和模块名的命名。
```python
first_str = 'Hello'
second_str = 'Python'
print(first_str + " " + second_str)
```
- 全大写命名法:将多个单词连接在一起,每个单词所有字母大写,可以加上适当的分隔符如 `_`,常用于常量或全局变量的命名。
```python
FIRST_STR = 'Hello'
SECOND_STR = 'Python'
print( FIRST_STR+ " " + SECOND_STR)
```
## 变量的赋值
将某个数据赋值给某个变量存储
```python
a = "变量1"
b = "变量2"
print(id(a), id(b))
```
![img-变量赋值1](Python基础语法/变量赋值1.png)
```python
a = "变量1"
b = a
print(id(a), id(b))
```
![img-变量赋值2](Python基础语法/变量赋值2.png)
## 常量
常量是指在程序运行过程中其值不应被改变的变量。
Python 没有内置的常量机制,但通过命名约定和模块结构,可以有效地使用常量来提高代码的可读性和可维护性。在设计大型程序时,合理使用常量是一个良好的编程习惯。
常量通常是通过使用大写字母命名的变量来表示:
```python
PI = 3.14159
MAX_CONNECTIONS = 100
```
# 用户交互
## 输入
解释器在执行该代码的时候,如果遇到 `input()` 就会出现阻塞,程序暂停,等待用户输入。当用户回车以后,程序才会继续向下执行
```python
name = input('请输出你的姓名:')
print(name)
# 注意通过input函数输入进来的内容一律会被当成字符串处理
a = input('请输入一个数字:')
# a = int(input('请输入一个数字:')) # 类型转化
print(type(a))
print(a > 0)
```
## 输出
**基本用法**
```python
a = "Hello world"
print(a)
```
### 格式化输出
```python
name = input("姓名:")
age = input("年龄:")
job = input("工作:")
info = '''
----------- info of %s -----------
姓名:%s
年龄:%s
工作:%s
''' % (name,name,age,job)
print(info)
```
### 字符串格式化标志(占位符)
以下是 Python 中字符串格式化的常见格式化标志及其说明:
| 标志 | 说明 |
| :--- | :--- |
| `s` | 获取传入对象的 `__str__` 方法的返回值 |
| `r` | 获取传入对象的 `__repr__` 方法的返回值 |
| `c` | 将整数转换为Unicode字符或直接添加字符 |
| `o` | 将整数转换为八进制表示 |
| `x` | 将整数转换为十六进制表示 |
| `d` | 将整数、浮点数转换为十进制表示 |
| `e` | 将数值转换为科学计数法小写e |
| `E` | 将数值转换为科学计数法大写E |
| `f` | 将数值转换为浮点表示默认6位小数 |
| `F` | 同 `f`,但使用大写表示特殊值 |
| `g` | 自动选择 `e``f`(小写) |
| `G` | 自动选择 `E``F`(大写) |
| `%` | 输出百分号 |
**示例**
```python
# s获取对象的 __str__ 方法返回值
print("Hello, %s!" % "EaglesLab") # 输出: Hello, EaglesLab!
# r获取对象的 __repr__ 方法返回值
print("Name: %r" % name) # 输出: Name: 'EaglesLab'
# c整数转换为其 Unicode 字符0-1114111
print("Unicode character: %c" % 65) # 输出: Unicode character: A
# o将整数转换为八进制表示
print("Octal: %o" % 10) # 输出: Octal: 12
# x将整数转换为十六进制表示
print("Hexadecimal: %x" % 255) # 输出: Hexadecimal: ff
# d将整数或浮点数转换为十进制表示
print("Decimal: %d" % 42) # 输出: Decimal: 42
# e将整数或浮点数转换为科学计数法小写 e
print("Scientific: %e" % 123456789) # 输出: Scientific: 1.234568e+08
# E将整数或浮点数转换为科学计数法大写 E
print("Scientific: %E" % 123456789) # 输出: Scientific: 1.234568E+08
# f/F将整数或浮点数转换为浮点数表示默认保留小数点后 6 位
print("Float: %f" % 3.14159) # 输出: Float: 3.141590
print("Float: %F" % 3.14159) # 输出: Float: 3.141590
# g/G自动调整为浮点型或科学计数法超出 6 位数使用科学计数法
print("Auto: %g" % 1234567.89) # 输出: Auto: 1234567.89
print("Auto: %G" % 1234567.89) # 输出: Auto: 1234567.89
# %:表示一个百分号
print("Percentage: %.2f%%" % (0.75 * 100)) # 输出: Percentage: 75.00%
```
# 数据类型
## 整数int
- **描述**:表示没有小数部分的数字,可以是正数、负数或零。
- **特性**
- Python 3 中的 `int` 类型是无限大小的。这意味着你可以创建非常大的整数而不必担心溢出overflow的问题。
- Python 会根据整数的大小自动调整内存使用,因此可以存储任意大小的整数,只要计算机的内存允许。
- 支持基本的算术运算,如加、减、乘、除等。
**示例**
```python
a = 10
b = -5
c = 0
print(a + b)
# Output: 5
print(type(a)) # 可以使用 type() 内置函数来查看某个变量或值的数据类型
# Output:
<class 'int'>
```
## 字符串str
- **描述**:字符串是字符的序列,可以通过单引号或双引号定义。
- **特性**
- 字符串是不可变的immutable不能更改其内容。
- 支持切片、连接、重复等操作。
- 可以使用转义字符处理特殊字符。
- 在 Python 中, 加了引号的字符都被认为是字符串!
**示例**
```python
Greeting = "Welcome to 英格科技"
Name = "同学们"
print(Greeting + " " + Name) # 字符串可以通过 + 号进行拼接
# Output:
Welcome to 英格科技 牛老师
```
### 字符串的索引与切片
**字符串的索引**
索引:每个字符在字符串中都有一个唯一的索引,索引从 `0` 开始。
负索引:可以使用负数索引从字符串的末尾开始访问字符,`-1` 表示最后一个字符,`-2` 表示倒数第二个字符,以此类推。
**示例**
```bash
s = "Hello, World!"
# 正索引
print(s[0]) # 输出: H
print(s[7]) # 输出: W
# 负索引
print(s[-1]) # 输出: !
print(s[-5]) # 输出: o
```
**字符串的切片**
切片:可以通过指定起始索引和结束索引来提取字符串的子串。切片的语法为 `s[start:end]`,包含起始索引的字符,但不包含结束索引的字符。
步长:可以指定步长来控制切片的间隔,语法为 `s[start:end:step]`。如果不指定步长,默认为 `1`
**示例**
```python
s = "Hello, World!"
# 基本切片
print(s[0:5]) # 输出: Hello
print(s[7:12]) # 输出: World
# 省略起始或结束索引
print(s[:5]) # 输出: Hello (从开始到索引 4)
print(s[7:]) # 输出: World! (从索引 7 到结束)
# 使用负索引切片
print(s[-6:-1]) # 输出: World
# 步长切片
print(s[::2]) # 输出: Hlo ol! (每隔一个字符)
print(s[::-1]) # 输出: !dlroW ,olleH (反转字符串)
```
### 字符串常用方法
```bash
# 字符串操作示例
words = "beautiful is better than ugly."
# 字符串的基本操作
print(words.capitalize()) # 首字母大写
print(words.swapcase()) # 大小写翻转
print(words.title()) # 每个单词的首字母大写
# 内容居中,总长度,空白处填充
a = "test"
ret = a.center(20, "*")
print(ret)
# 统计字符串中的元素出现的个数
ret = words.count("e", 0, 30)
print(ret)
# startswith 和 endswith 判断
print(a.startswith("a")) # 判断是否以 'a' 开头
print(a.endswith("j")) # 判断是否以 'j' 结尾
print(a.startswith('sdj', 2, 5)) # 判断子串是否在指定范围内
print(a.endswith('ado', 7, 10)) # 判断子串是否在指定范围内
# 寻找字符串中的元素是否存在
print(a.find('sdj', 1, 10)) # 返回索引,找不到返回 -1
print(a.index('sdj', 1, 10)) # 返回索引,找不到抛出异常
# split 以指定字符分割,形成列表
ret = words.split(' ')
print(ret)
ret = words.rsplit(' ', 2) # 指定分割次数
print(ret)
# format 的三种用法
print('{} {} {}'.format('aaron', 18, 'teacher'))
print('{1} {0} {1}'.format('aaron', 18, 'teacher'))
print('{name} {age} {job}'.format(job='teacher', name='aaron', age=18))
# strip 操作
a = '****asdasdasd********'
print(a.strip('*')) # 去除两端指定字符
print(a.lstrip('*')) # 去除左侧指定字符
print(a.rstrip('*')) # 去除右侧指定字符
# replace 操作
print(words.replace('e', 'a', 2)) # 替换 'e' 为 'a',替换两次
# 字符串类型检查
print(words.isalnum()) # 判断字符串是否只由字母或数字组成
print(words.isalpha()) # 判断字符串是否只由字母组成
print(words.isdigit()) # 判断字符串是否只由数字组成
```
## 布尔类型bool
- **描述**:布尔类型只有两个值:`True``False`
- **特性**
- 通常用于条件判断和逻辑运算。
- 布尔值在逻辑表达式中可以进行运算。
布尔类型很简单,就两个值 ,一个 True(真),一个 False(假), 主要用记逻辑判断
```python
a = 3
b = 5
print(a < b, a > b , a != b)
# Output:
True False True
```
# 基本运算符
## 算数运算符
用于执行基本的数学运算
| 运算符 | 描述 | 示例 |
| :----- | :------------------------------------------------- | :-------------- |
| `+` | 加法,返回两个数的和 | `3 + 5``8` |
| `-` | 减法,返回第一个数减去第二个数的结果 | `10 - 4``6` |
| `*` | 乘法,返回两个数的乘积 | `2 * 3``6` |
| `/` | 除法,返回第一个数除以第二个数的结果,结果为浮点数 | `7 / 2``3.5` |
| `//` | 地板除法,返回两个数相除的整数部分 | `7 // 2``3` |
| `%` | 取模,返回第一个数除以第二个数的余数 | `7 % 2``1` |
| `**` | 幂运算,返回第一个数的第二个数次幂 | `2 ** 3``8` |
## 关系运算符
用于比较两个值的关系
| 运算符 | 描述 | 示例 |
| :----- | :--------------------------------------- | :---------------- |
| `==` | 等于,判断两个值是否相等 | `5 == 5``True` |
| `!=` | 不等于,判断两个值是否不相等 | `5 != 3``True` |
| `>` | 大于,判断左侧值是否大于右侧值 | `5 > 3``True` |
| `<` | 小于,判断左侧值是否小于右侧值 | `5 < 3``False` |
| `>=` | 大于等于,判断左侧值是否大于或等于右侧值 | `5 >= 5``True` |
| `<=` | 小于等于,判断左侧值是否小于或等于右侧值 | `3 <= 5``True` |
## 逻辑运算符
用于进行布尔逻辑运算
| 运算符 | 描述 | 示例 |
| :----- | :---------------------------------------------- | :------------------------- |
| `and` | 逻辑与,当且仅当两个表达式都为 True 时返回 True | `True and False``False` |
| `or` | 逻辑或,只要有一个表达式为 True 即返回 True | `True or False``True` |
| `not` | 逻辑非,返回布尔值的反转 | `not True``False` |
## 赋值运算符
用于给变量赋值
| 运算符 | 描述 | 示例 |
| :----- | :-------------------------------- | :-------- |
| `=` | 赋值,将右侧的值赋给左侧的变量 | `x = 5` |
| `+=` | 加法赋值,等同于 `x = x + 5` | `x += 3` |
| `-=` | 减法赋值,等同于 `x = x - 5` | `x -= 2` |
| `*=` | 乘法赋值,等同于 `x = x * 5` | `x *= 4` |
| `/=` | 除法赋值,等同于 `x = x / 5` | `x /= 2` |
| `//=` | 地板除法赋值,等同于 `x = x // 5` | `x //= 3` |
| `%=` | 取模赋值,等同于 `x = x % 5` | `x %= 2` |
| `**=` | 幂赋值,等同于 `x = x ** 5` | `x **= 3` |
## 位运算符
用于对整数的二进制位进行操作
| 运算符 | 描述 | 示例 |
| :----- | :---------------------------------- | :------------------------------------ |
| `&` | 按位与,两个比特位都为 1 时结果为 1 | `5 & 3``1` |
| `\|` | 按位或,只要有一个比特位为 1 结果为 1 | `5 \| 3``7` |
| `^` | 按位异或,当比特位不同结果为 1 | `5 ^ 3``6` |
| `~` | 按位取反,反转所有比特位 | `~5``-6` |
| `<<` | 左移运算,向左移动指定的比特位数 | `5 << 1``10` |
| `>>` | 右移运算,向右移动指定的比特位数 | `5 >> 1``2` |
## 身份运算符
用于比较两个对象的内存地址
| 运算符 | 描述 | 示例 |
| :------- | :--------------------------- | :----------- |
| `is` | 判断两个对象是否为同一对象 | `x is y` |
| `is not` | 判断两个对象是否不是同一对象 | `x is not y` |
## 成员运算符
用于检查值是否在序列中
| 运算符 | 描述 | 示例 |
| :------- | :--------------------- | :---------------------------- |
| `in` | 判断元素是否在序列中 | `3 in [1, 2, 3]``True` |
| `not in` | 判断元素是否不在序列中 | `4 not in [1, 2, 3]``True` |
## 运算符优先级
以下表格列出了从最高到最低优先级的所有运算符:
| 优先级 | 运算符 | 描述 |
| :----- | :----------------------------------------- | :------------------------- |
| 1 | `()` | 括号,改变运算顺序 |
| 2 | `**` | 幂运算 |
| 3 | `+`, `-` | 正负号(正负运算) |
| 4 | `*`, `/`, `//`, `%` | 乘法、除法、地板除法、取模 |
| 5 | `+`, `-` | 加法、减法 |
| 6 | `<<`, `>>` | 位移运算 |
| 7 | `&` | 按位与 |
| 8 | `^` | 按位异或 |
| 9 | ` | ` |
| 10 | `==`, `!=`, `>`, `<`, `>=`, `<=` | 比较运算 |
| 11 | `is`, `is not`, `in`, `not in` | 身份和成员运算 |
| 12 | `not` | 逻辑非 |
| 13 | `and` | 逻辑与 |
| 14 | `or` | 逻辑或 |
| 15 | `=` | 赋值 |
| 16 | `+=`, `-=`, `*=`, `/=`, `//=`, `%=`, `**=` | 赋值运算 |
### 注意事项
1. **括号优先**:使用括号可以改变运算顺序,任何在括号内的运算会优先计算。
2. **运算符结合性**
- 大多数运算符是从左到右结合的,例如加法和减法。
- 幂运算 `**` 是从右到左结合,即 `2 ** 3 ** 2` 等于 `2 ** (3 ** 2)`
3. **比较运算符**:比较运算符的优先级在逻辑运算符之前。
4. **赋值运算符**:赋值运算符的优先级最低。
```python
# 示例 1
result = 2 + 3 * 4 # 结果为 14因为乘法优先于加法
# 示例 2
result = (2 + 3) * 4 # 结果为 20因为括号改变了优先顺序
# 示例 3
result = 2 ** 3 ** 2 # 结果为 512因为幂运算是右结合的
```
了解运算符优先级有助于在编写和阅读代码时清晰地理解表达式的计算顺序。

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

View File

@@ -0,0 +1,796 @@
# Python 基本数据结构
# 列表List
列表是一个有序的可变集合,可以存储不同类型的元素。列表相比于字符串,不仅可以储存不同的数据类型,而且可以储存大量数据。而且列表是有序的,有索引值,可切片,方便取值。
## 定义列表
```python
fruits = ["apple", "banana", "cherry"]
print(fruits)
# Output:
['apple', 'banana', 'cherry']
```
## 增加元素
```python
# 1. 按照索引的位置去增加
fruits.insert(1, "kiwi")
# 2. 在最末尾添加
fruits.append("orange")
# 3. 用迭代的方式去添加
fruits.extend(['a','b','c'])
# 可以立即为再传递一个List进去然后依次添加到末尾,而append会把括号里的当成一个整体添加到末尾
# Output:
['apple', 'kiwi', 'banana', 'cherry', 'orange', 'a', 'b', 'c']
```
## 删除元素
```python
# 1. 删除制定的具体元素
fruits.remove("cherry")
# 2. 按照索引位置去删除
fruits.pop() # 默认是删除最后一个,可以加上索引。并且有返回值,返回值为删除的元素
fruits.pop(1)
# 3. 使用切片范围删除
del fruits[1:3] # 没有返回值
# 4. 清空列表
fruits.clear()
# 5. 删除列表
del fruits
# clear是清空列表的内容变成一个空列表而del是删除列表本身后续无法再次调用...
# Output:
['apple', 'banana', 'cherry']
['apple', 'kiwi', 'banana', 'cherry', 'orange', 'a', 'b', 'c']
[]
```
## 修改元素
```python
# 1. 按照索引修改某个元素的值
fruits = ["apple", "banana", "cherry"]
fruits[1] = "orange"
print(fruits)
# Output:
['apple', 'orange', 'cherry']
# 2. 按照切片范围修改
fruits[0:2] = ['kiwi','orange']
print(fruits)
# Output:
['kiwi', 'orange', 'cherry']
```
## 查找元素
```python
# 1. index(element):返回元素的索引
fruits = ["apple", "banana", "cherry"]
index_of_apple = fruits.index("apple")
print(index_of_apple)
# Output:
0
# 2. count(element):返回元素出现的次数
count_of_cherry = fruits.count("cherry")
print(count_of_cherry)
# Output:
1
```
## 列表的切片
切片是按照一定的索引规律分割列表从而组成一个新的列表,类似与我们字符串中讲到的切片
```python
li = [1,2,4,5,4,2,4]
sub_li = li[2:5]
print(sub_li)
# Output:
[4, 5, 4]
```
## 其他操作
```python
li = [1,2,4,5,4,2,4]
# 1. 统计某个元素出现的次数
print (li.count(4))
# 2. 从列表中找出某个值第一个匹配项的索引位置
print (li.index(2))
# 3. 对列表进行排序
li.sort()
print(li)
# 4. 将列表中的元素反向存放
li.reverse()
print (li)
# Output:
3
1
[1, 2, 2, 4, 4, 4, 5]
[5, 4, 4, 4, 2, 2, 1]
```
# 元组Tuple
有序的不可变集合,也被被称为只读列表,即数据可以被查询,但不能被修改。
## 定义元组
```python
tuple = (1,2,3,'a','b','c')
print(tuple)
print(tuple[1])
# 可以删除元素吗?
tuple.pop()
# Output:
AttributeError: 'tuple' object has no attribute 'pop'
```
## 可变元组
tuple 其实不可变的是地址空间,如果地址空间里存的是可变的数据类型的话,比如列表就是可变的
```python
tuple = (1, 2, 3, [1, 4, 7])
print(tuple)
tuple[3][2] = 100
print(tuple)
# Output:
(1, 2, 3, [1, 4, 7])
(1, 2, 3, [1, 4, 100])
```
在元组 tuple 中,包含一个 list 类型的数据,由于 list 是可以变的,所以我们可以更改元组里 list 的值,但是元组本身的地址空间是不变的。
<img src="Python数据结构/可变元组.png" alt="image-20240904095734746" style="zoom:80%;" />
# 字典Dict
字典是 python 中唯一的映射类型采用键值对key-value的形式存储数据。python 对 key 进行哈希函数运算,根据计算的结果决定 value 的存储地址所以字典是无序存储的且key必须是可哈希的。可哈希表示 key 必须是不可变类型,如:数字、字符串、元组。
不过在 Python 3.6 版本以后,字典会保留插入顺序,变成有序数据结构。
而且键是具有唯一性的,每个键只能出现一次,但是值没有限制,在一个字典中可以出现多个相同的值。
## 定义字典
```python
dic = {'name':'nls','age':18,'job':'teacher'}
print(dic)
print(dic['name'])
print(dic['age'])
print(dic['job'])
# Output:
{'name': 'nls', 'age': 18, 'job': 'teacher'}
nls
18
teacher
```
## 增加键值
```python
dic = {'name':'nls','age':18,'job':'teacher'}
# 1. 直接通过键值对来增加
dic['hobby'] = 'study' # 如果键已经存在,那么就会替换原来的值
print(dic)
# Output: {'name': 'nls', 'age': 18, 'job': 'teacher', 'hobby': 'study'}
# 2. 在字典中添加键值对时,如果指定的键已经存在则不做任何操作,如果原字典中不存在指定的键值对,则会添加。
dic.setdefault('name','牛老师')
dic.setdefault('gender','男')
print(dic)
# Output:
{'name': 'nls', 'age': 18, 'job': 'teacher', 'hobby': 'study', 'gender': '男'}
```
## 删除键值
```python
dic = {'name':'nls','age':18,'job':'teacher'}
# 1. 删除指定的键,并且返回对应的值
name = dic.pop('job')
hobby = dic.pop('hobby','查无此项') # 可以在后面加上一个异常处理如果key不存在就输出后面的内容
print(dic)
print(name)
print(hobby)
# Output:
{'name': 'nls', 'age': 18}
teacher
查无此项
# 2. 使用del关键字删除指定的键值对
del dic['name']
# 3. 删除最后插入的键值对
dic_pop = dic.popitem()
print(dic_pop)
# 4. 清空字典
dic.clear()
print(dic)
# Output: {}
```
## 修改键值
```python
dic = {'name':'nls','age':18,'job':'teacher'}
dic['age'] = 25
print(dic)
# Output:
{'name': 'nls', 'age': 25, 'job': 'teacher'}
```
## 查找键值
```python
dic = {'name':'nls','age':18,'job':'teacher'}
# 1. 直接通过键名获取,如果不存在会报错
value = dic['name']
print(value)
# 2. 使用get方法获取键值,若不存在则返回 None可以自定义异常返回值
value = dic.get('job','查无此项')
print(value)
# Output:
nls
teacher
# 3. IN关键字存在返回True反之False
exists = "name" in dic
print(exists)
# Output:
True
```
## 其他操作
```python
dic = {'name':'nls','age':18,"phone":['1888888888','0511-10101010']}
# 字典里面的value也可以是容器类型的数据比如列表字典等等...但是key必须是不可变的
print(dic)
# 1. 对键和值进行迭代操作
for i in dic.items():
# 将键和值作为元祖列出
print(i)
for i in dic:
# 只迭代键
print(i)
# Output:
('name', 'nls')
('age', 18)
('phone', ['1888888888', '0511-10101010'])
name
age
phone
# 2. 使用keys()和values()方法获取键值
keys = dic.keys()
print(keys,type(keys))
value = dic.values()
print(value,type(value))
# Output:
dict_keys(['name', 'age', 'phone']) <class 'dict_keys'>
dict_values(['nls', 18, ['1888888888', '0511-10101010']]) <class 'dict_values'>
```
# 集合Set
集合是**无序**的,**不重复****确定性**的数据集合,它里面的元素是可哈希的(不可变类型),但是集合本身是不可哈希(所以集合做不了字典的键)的。以下是集合最重要的两点:
- 去重,把一个列表变成集合,就自动去重了。
- 关系测试,测试两组数据之前的交集、差集、并集等关系。
## 定义集合
```python
set1 = {1,2,3,'a','b','c'} # 推荐方法
set2 = set({1,2,3})
print(set1, set2)
# Output:
{1, 2, 3, 'b', 'c', 'a'} {1, 2, 3}
```
## 增加元素
```python
set1 = {1,2,3,'a','b','c'}
# 1. 使用add()方法为集合增加元素
set1.add('d')
print(set1)
# Output:
{'a', 1, 2, 3, 'c', 'd', 'b'}
# 2. 使用update()方法迭代的去增加
set1.update('e','f')
# update接收的参数应该是可迭代的数据类型比如字符串、元组、列表、集合、字典。这些都可以向集合中添加元素但是整型、浮点型不可以
set1.update([4,5,6])
print(set1)
# Output:
{1, 2, 3, 4, 5, 6, 'a', 'd', 'b', 'c', 'f', 'e'}
```
## 删除元素
```python
set1 = {1,2,3,'a','b','c'}
# 1. 使用remove()方法删除元素
set1.remove('a')
print(set1)
# 2. 随机删除某个元素
set1.pop()
print(set1)
# Output:
{1, 2, 3, 'c', 'b'}
{2, 3, 'c', 'b'}
# 3. 删除集合本身
del set1
```
## 查找元素
```python
set1 = {1,2,3,'a','b','c'}
exists = "a" in set1 # True
print(exists)
```
## 关系测试
### 交集
取出两个集合共有的元素
```python
set1 = {1,2,3,4,5}
set2 = {3,4,5,6,7}
print(set1 & set2)
print(set1.intersection(set2))
# Output:
{3, 4, 5}
{3, 4, 5}
```
### 反交集
```python
set1 = {1,2,3,4,5}
set2 = {3,4,5,6,7}
print(set1 ^ set2)
print(set1.symmetric_difference(set2))
# Output:
{1, 2, 6, 7}
{1, 2, 6, 7}
```
### 并集
合并两个集合的所有元素
```python
set1 = {1,2,3,4,5}
set2 = {3,4,5,6,7}
print(set1 | set2)
print(set2.union(set1))
# Output:
{1, 2, 3, 4, 5, 6, 7}
{1, 2, 3, 4, 5, 6, 7}
```
### 差集
第一个集合去除二者共有的元素
```python
set1 = {1,2,3,4,5}
set2 = {3,4,5,6,7}
print(set1 - set2)
print(set1.difference(set2))
# Output:
{1, 2}
{1, 2}
```
### 子集与超集
当一共集合的所有元素都在另一个集合里,则称这个集合是另一个集合的子集,另一个集合是这个集合的超集
```python
set1 = {1,2,3}
set2 = {1,2,3,4,5,6}
print(set1 < set2)
print(set1.issubset(set2)) # 这两个相同都是说明set1是set2子集。
print(set2 > set1)
print(set2.issuperset(set1)) # 这两个相同都是说明set2是set1超集
```
## 不可变集合
```python
set1 = {1,2,3,4,5,6}
set2 = frozenset(set1)
print(set2,type(set2))
set2.add(7) # 不可以修改,会报错
# Output:
AttributeError: 'frozenset' object has no attribute 'add'
```
# 数据结构的总结
| 数据结构 | 描述 | 特性 | 常见操作 | 适用场景 | 优点 | 缺点 |
| :-------------------- | :--------------- | :--------------------- | :--------------------- | :--------------------------------------- | :-------------------------- | :------------------------------------ |
| **列表** | 有序的可变集合 | 有序、可变、支持重复 | 添加、删除、修改、查找 | 需要维护元素顺序的场景,如队列、栈的实现 | 灵活性高,支持多种操作 | 查询复杂度为 O(n),插入和删除性能较差 |
| **元组** | 有序的不可变集合 | 有序、不可变、支持重复 | 查找 | 元素不需要修改的场景,如函数参数、字典键 | 更节省内存,速度较快 | 不支持修改 |
| **集合** | 无序的可变集合 | 无序、可变、不支持重复 | 添加、删除、查找 | 需要去重和快速查找的场景,如集合运算 | 快速查找和去重 | 不支持索引,元素无序 |
| **字典** | 有序的键值对集合 | 有序、可变、键唯一 | 添加、删除、修改、查找 | 需要快速查找和存储键值对的场景,如缓存 | 快速查找O(1) 平均复杂度) | 键必须是不可变类型,内存开销较大 |
| **字符串** | 字符的序列 | 不可变 | 查找、切片、替换 | 文本处理 | 易于使用,内置丰富的方法 | 不可修改,性能较低 |
# 流程控制
## 判断语句
**语法:**
```python
if 条件:
满足条件后要执行的代码
```
<img src="Python数据结构/流程控制.png" alt="img-流程控制" style="zoom:80%;" />
### 单分支判断
```python
# 提示用户输入年龄
age = int(input("请输入你的年龄:"))
# 单分支判断
if age >= 18:
print("已经成年,可以去网吧上网了")
print("欢迎来到Eagles网吧")
print("你还没成年,要好好学习")
```
### 双分支判断
```python
"""
if 条件:
满足条件执行代码
else:
if条件不满足就走这段
"""
# 提示用户输入年龄
age = int(input("请输入你的年龄:"))
# 单分支判断
if age >= 18:
print("已经成年,可以去网吧上网了")
print("欢迎来到Eagles网吧")
else:
print("你还没成年,要好好学习")
```
### 多分支判断
```python
if 条件:
满足条件执行代码
elif 条件:
上面的条件不满足就走这个
elif 条件:
上面的条件不满足就走这个
elif 条件:
上面的条件不满足就走这个
else:
上面所有的条件不满足就走这段
# 成绩判断程序
# 提示用户输入成绩
score = int(input("请输入成绩0-100"))
# 多分支判断成绩
if score >= 90 and score <= 100:
print("优秀")
elif score >= 60 and score < 90:
print("良好")
elif score >= 0 and score < 60:
print("不及格")
else:
print("输入错误请输入0到100之间的成绩。")
```
## 循环语句 - while
**语法:**
```python
while 条件:
循环体
# 循环条件可以直接是True/False或者1/0也可以是某个语句...
```
如果条件为真,那么循环体则执行
如果条件为假,那么循环体不执行
**示例:猜数字小游戏**
```python
print('猜数字游戏开始')
num = 54
while True:
guess = int(input("您猜数字是什么?(输入0-100的数字):"))
if guess < num:
print("您猜小了")
continue
elif guess > num:
print("您猜大了")
continue
break
print("您猜对了!")
# Output:
猜数字游戏开始
您猜数字是什么(输入0-100的数字):10
您猜小了
您猜数字是什么(输入0-100的数字):50
您猜小了
您猜数字是什么(输入0-100的数字):60
您猜大了
您猜数字是什么(输入0-100的数字):54
您猜对了
```
### 循环终止语句
#### break
用于完全结束一个循环,跳出循环体执行循环后面的语句
#### continue
和 break 有点类似,区别在于 continue 只是终止本次循环接着还执行后面的循环break 则完全终止循环
#### while …… else
while 后面的 else 作用是指,当 while 循环正常执行完,中间没有被 break 中止的话,就会执行 else 后面的语句
- **语法:**
```python
while condition:
# 循环体
if some_condition:
break
else:
# 循环正常结束时执行的代码
```
- **示例:**
```python
# 寻找素数的示例
num = 10
i = 2
while i < num:
if num % i == 0:
print(f"{num} 不是素数,因为它可以被 {i} 整除。")
break
i += 1
else:
print(f"{num} 是一个素数!")
```
## 循环语句 - for
for 循环:用户按照顺序循环可迭代对象的内容
**语法:**
```python
for variable in iterable:
# 循环体
# 执行的代码
```
### 遍历列表
```python
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
# Output:
apple
banana
cherry
```
### 遍历字符串
```python
word = "helloworld"
for letter in word:
print(letter)
# Output:
h
e
l
l
o
w
o
r
l
d
```
## enumerate
enumerate枚举对于一个可迭代的iterable/可遍历的对象如列表、字符串enumerate 将其组成一个索引序列,利用它可以同时获得索引和值。
```python
li = ['甲','乙','丙','丁']
for i in enumerate(li):
print(i)
for index,value in enumerate(li):
print(index,value)
for index,value in enumerate(li,100): #从哪个数字开始索引
print(index,value)
# Output:
(0, '甲')
(1, '乙')
(2, '丙')
(3, '丁')
0
1
2
3
100
101
102
103
```
## range
指定范围,生成指定数字
```bash
for i in range(1,10):
print(i)
for i in range(1,10,2): # 步长
print(i)
for i in range(10,1,-2): # 反向步长
print(i)
```
### 小游戏案例
石头简单布
```bash
import random
# random产生随机值或者从给定值中随机选择
# 可选择的选项
options = ["石头", "剪子", "布"]
print("欢迎来到石头剪子布游戏!")
print("请从以下选项中选择:")
for i, option in enumerate(options):
print(f"{i + 1}. {option}")
# 玩家选择
player_choice = int(input("请输入你的选择1-3")) - 1
# 计算机随机选择
computer_choice = random.randint(0, 2)
print(f"\n你选择了{options[player_choice]}")
print(f"计算机选择了:{options[computer_choice]}")
# 判断胜负
if player_choice == computer_choice:
print("平局!")
elif (player_choice == 0 and computer_choice == 1) or \
(player_choice == 1 and computer_choice == 2) or \
(player_choice == 2 and computer_choice == 0):
print("你赢了!🎉")
else:
print("你输了!😢")
```
# 课后作业
## 完善猜数字小游戏
1. 生成一个随机数
2. 猜错3次了以后程序自动退出...
## 完善石头剪刀布游戏
1. 更新游戏规则,实现三局两胜制
2. 胜利条件:
- **2:1**
- **3:0**
# 扩展阅读
**内置类型**https://docs.python.org/zh-cn/3.9/library/stdtypes.html#

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,457 @@
# Python 文件操作
在 Python 中文件操作是非常常见的任务。Python 提供了内置的函数来处理文件的读写操作。
## 文件的类型
1. **文本文件**:包含可读的字符(如 `.txt``.csv`)。一般使用 UTF-8 编码,可以使用文本编辑器查看。
2. **二进制文件**:包含非文本数据(如图像、音频、视频文件,后缀如 `.jpg``.png``.mp3`)。以原始字节格式存储。需要使用专用软件查看。
## 文件操作的过程
1. 打开文件
2. 读写文件
- 读文件:将文件内容读入内存
- 写文件:将内存内容写入文件
3. 关闭文件
# 操作方法
## 打开文件
使用 `open()` 函数打开文件,并且返回文件对象:
1. 第一个参数是文件名(文件名区分大小写),第二个参数是打开方式
2. 如果文件存在返回文件操作对象
3. 如果文件不存在抛出异常
```python
f = open("文件名", "访问方式")
```
### 文件路径
- 绝对路径:从根路径开始描述文件的位置,例如:`F:\技术文件\课件-笔记\课件修订\Python\01.Python基础语法`,具有唯一性,不会出错,不管写在哪里都可以准确的找到文件的位置
- 相对路径:相对当前位置进行文件定位,容易出错,需要对路径比较熟悉
### 以不同模式打开
| 访问方式 | 说明 |
| :-------- | :---- |
| `r` | 以**只读**方式打开文件。文件的指针将会放在文件的开头,这是**默认模式**。如果文件不存在,抛出异常 |
| `w` | 以**只写**方式打开文件。如果文件存在会被覆盖。如果文件不存在,创建新文件 |
| `a` | 以**追加**方式打开文件。如果该文件已存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件进行写入 |
| `r+` | 以**读写**方式打开文件。文件的指针将会放在文件的开头。如果文件不存在,抛出异常 |
| `w+` | 以**读写**方式打开文件。如果文件存在会被覆盖。如果文件不存在,创建新文件 |
| `a+` | 以**读写**方式打开文件。如果该文件已存在,文件指针将会放在文件的结尾。如果文件不存在,创建新文件进行写入 |
| `r+b` | 以**二进制读写**方式打开。文件指针在开头,可读可写,文件必须存在 |
| `w+b` | 以**二进制读写**方式打开。文件指针在开头,先清空再写入,不存在则创建 |
| `a+b` | 以**二进制读写**方式打开。文件指针在末尾,追加写入,可读全文,不存在则创建 |
对于非文本文件,我们只能使用 `b` 模式,表示以 `bytes` 方式操作(所有文件本质上最终都是字节存储,使用这种模式无需考虑文本文件的字符编码),读取到的内容是字节类型,写入时也需要提供字节类型,不能指定编码。
### 文件编码
`f = open()` 是由操作系统打开文件,那么如果我们没有指定编码,那么打开文件的默认编码取决于操作系统,操作系统会用自己的默认编码去打开文件,在 Windows 下是 gbk在 Linux 下是 utf-8。
```python
f = open('example.txt','r',encoding='utf-8')
```
## 读取文件
- **`read(size)`**:读取指定大小的内容,如果没有指定,读取全部内容。
- **`readline()`**:读取一行。
- **`readlines()`**:读取所有行并返回一个列表。
示例:
```python
file = open('example.txt', 'r')
content = file.read() # 读取全部内容
line = file.readline() # 读取一行
lines = file.readlines() # 读取所有行
print(content)
print('-----------')
print(line)
print('-----------')
print(lines)
# 思考?
# 为什么后面两个print读取不到东西呢
```
### 文件指针
有时候我们想要改变文件指针(光标)的位置,就需要用到 `seek()` 来操作指针
**语法:**
```python
file.seek(offset, whence)
```
- **`offset`**:要移动的字节数。
- `whence`
(可选):指定偏移量的基准位置。可以取以下值:
- `0`:从文件开头开始计算(默认值)。
- `1`:从当前位置开始计算。
- `2`:从文件末尾开始计算
**示例**
```python
file = open('example.txt', 'r')
content = file.read() # 读取全部内容
file.seek(0) # 把光标移到到文件的开头
line = file.readline() # 读取一行
file.seek(0) # 再次把光标移动到文件的开头
lines = file.readlines() # 读取所有行
print(content)
print('-----------')
print(line)
print('-----------')
print(lines)
```
### 按行读取文件内容
- `read()` 默认会把文件的所有内容一次性读取到内存
- `readline()` 可以一次读取一行内容
```python
# 方式一、通过循环按行读取文件所有内容
file1 = open("example.txt")
i = 1
while True:
text1 = file1.readline().strip()
if text1:
print("这是第%s行内容" % i)
i += 1
print(text1)
else:
break
file1.close()
file2 = open("example.txt")
# 通过for遍历按行读取文件所有内容
for i in file2.readlines():
print(i.strip())
file2.close()
```
## 写入内容
可以对文件对象调用 `write()` 实现写入内容
**语法**
```python
file.write()
```
**示例:日记记录**
```python
# 日记程序
# 以追加模式打开日记文件
file = open('diary.txt', 'a',encoding='utf-8')
# 获取用户输入的日记内容
content = input("请输入今天的日记:")
# 将日记内容写入文件
file.write(content + "\n")
print("日记已保存!")
# 关闭文件
file.close()
```
## 关闭文件
使用 `close()` 方法关闭文件,释放系统资源,防止文件被一直占用。
```python
file.close()
```
## with 结构
使用 `with` 语句可以自动管理文件的打开和关闭,避免忘记关闭文件的情况。
```python
with open('example.txt', 'r') as file: # 获取file文件对象
content = file.read()
```
**案例:简单的备份程序**
将一个文本文件的内容复制到另一个文件,用于简单的备份。
```python
# 简单备份小程序
source = 'a.txt'
destination = 'b.txt'
with open(source, 'r',encoding='utf-8') as src:
content = src.read()
with open(destination, 'w',encoding='utf-8') as dest:
dest.write(content)
print(f"备份成功!'{source}' 的内容已复制到 '{destination}'")
```
## 其他文件操作
除了上述讲到的常用操作之外,还有很多其他的操作。这里我们列出在这,就不一一带着大家去看了,用到的时候可以回头来查一下就行。
```python
class CustomFile:
def __init__(self, *args, **kwargs):
"""初始化文件对象."""
pass
@staticmethod
def __new__(*args, **kwargs):
"""创建并返回一个新的对象."""
pass
def close(self, *args, **kwargs):
"""关闭文件."""
pass
def fileno(self, *args, **kwargs):
"""返回文件描述符."""
pass
def flush(self, *args, **kwargs):
"""刷新文件内部缓冲区."""
pass
def isatty(self, *args, **kwargs):
"""判断文件是否是一个终端设备."""
pass
def read(self, *args, **kwargs):
"""读取指定字节的数据."""
pass
def readable(self, *args, **kwargs):
"""检查文件是否可读."""
pass
def readline(self, *args, **kwargs):
"""仅读取一行数据."""
pass
def seek(self, *args, **kwargs):
"""移动文件指针到指定位置."""
pass
def seekable(self, *args, **kwargs):
"""检查指针是否可操作."""
pass
def tell(self, *args, **kwargs):
"""获取当前指针位置."""
pass
def truncate(self, *args, **kwargs):
"""截断文件,仅保留指定之前的数据."""
pass
def writable(self, *args, **kwargs):
"""检查文件是否可写."""
pass
def write(self, *args, **kwargs):
"""写入内容到文件."""
pass
def __next__(self, *args, **kwargs):
"""实现迭代器的 next() 方法."""
pass
def __repr__(self, *args, **kwargs):
"""返回文件对象的字符串表示."""
pass
def __getstate__(self, *args, **kwargs):
"""自定义对象的序列化状态."""
pass
...
```
### 解释
| 方法名 | 说明 |
| :--- | :--- |
| `__init__` | 初始化文件对象的方法。通常在这里设置文件的基本属性 |
| `__new__` | 静态方法,用于创建新的对象实例 |
| `close` | 关闭文件,释放系统资源 |
| `fileno` | 返回文件描述符,通常用于与底层操作系统交互 |
| `flush` | 刷新文件的内部缓冲区,将未写入的数据写入文件 |
| `isatty` | 判断文件是否是一个终端设备tty用于检查文件是否连接到一个用户界面 |
| `read` | 读取指定字节的数据,可以用来读取文件内容 |
| `readable` | 检查文件对象是否可读 |
| `readline` | 读取文件中的一行数据,常用于逐行读取文件内容 |
| `seek` | 移动文件指针到指定位置,允许在文件中随机访问 |
| `seekable` | 检查文件指针是否可操作,确定文件是否支持随机访问 |
| `tell` | 返回当前文件指针的位置 |
| `truncate` | 截断文件,只保留指定位置之前的数据 |
| `writable` | 检查文件对象是否可写 |
| `write` | 向文件写入内容 |
| `__next__` | 实现迭代器的 `next()` 方法,用于支持迭代访问文件的内容 |
| `__repr__` | 返回文件对象的字符串表示,通常用于调试 |
| `__getstate__` | 自定义对象的序列化状态,用于存储和恢复对象的状态 |
# 案例练习
## 案例一:文件修改
文件的数据是存放于硬盘上的,因而只存在覆盖、不存在修改这么一说,我们平时看到的修改文件,都是模拟出来的效果,具体的说有两种实现方式:
方式一将硬盘存放的该文件的内容全部加载到内存在内存中是可以修改的修改完毕后再由内存覆盖到硬盘wordvimnodpad++ 等编辑器)
```python
import os
with open('a.txt') as read_f,open('a.txt.new','w') as write_f:
data = read_f.read()
data = data.replace('Hello','nihao')
write_f.write(data)
os.remove('a.txt')
os.rename('a.txt.new','a.txt')
```
方式二:将硬盘存放的该文件的内容一行一行地读入内存,修改完毕就写入新文件,最后用新文件覆盖源文件
```python
import os
with open('a.txt') as read_f,open('a.txt.new','w') as write_f:
for line in read_f:
line = line.replace('nihao','Hello')
write_f.write(line)
os.remove('a.txt')
os.rename('a.txt.new','a.txt')
```
## 案例二:商品信息管理与总价计算
#### 背景
在实际的商业管理中,商品的管理和销售记录是非常重要的。我们需要一种方式来处理商品信息,包括商品名称、价格和数量。通过这些信息,我们可以计算出总价,帮助商家了解销售情况。
#### 目标
本案例旨在通过 Python 读取存储在文本文件中的商品信息,并将其转换为易于操作的数据结构。具体目标包括:
1. 从文件 `a.txt` 中读取每一行的商品信息。
2. 将读取的信息构建为包含字典的列表,每个字典表示一个商品,包含名称、价格和数量。
3. 计算所有商品的总价,并输出结果。
#### 文件内容示例
`a.txt` 文件的内容如下:
```
apple 10 3
tesla 100000 1
mac 3000 2
lenovo 30000 3
chicken 10 3
```
每行代表一个商品,格式为:`商品名称 价格 数量`
#### 代码示例
```python
# 初始化商品列表
products = []
# 读取文件并构建商品列表
with open('a.txt', 'r', encoding='utf-8') as file:
for line in file:
# 去除行首尾空白并分割
parts = line.strip().split()
if len(parts) == 3: # 确保有三个部分
product = {
'name': parts[0],
'price': int(parts[1]), # 转换为整数
'amount': int(parts[2]) # 转换为整数
}
products.append(product)
# 输出商品列表
print("商品列表", products)
total_price = 0
# 计算总价
for i in products:
total_price += i['price'] * i['amount']
# 输出总价
print("总价:", total_price)
# Output:
[{'name': 'apple', 'price': 10, 'amount': 3}, {'name': 'tesla', 'price': 100000, 'amount': 1}, {'name': 'mac', 'price': 3000, 'amount': 2}, {'name': 'lenovo', 'price': 30000, 'amount': 3}, {'name': 'chicken', 'price': 10, 'amount': 3}]
总价: 196060
```
## 案例三:基于文件的账户验证
将用户信息存放在文件 **user.txt** 中,并且格式如下
```
张三|123456
```
**代码示例:**
```python
db = {}
with open("sql.txt","r", encoding="utf-8") as f:
data = f.readlines()
print(data)
for i in data:
ret = i.strip().split("|")
# ret = ["张三", "123"]
print(ret)
db[ret[0]] = ret[1]
# db["张三"] = "123"
print(db)
while True:
username = input("请输入用户名:")
if username in db:
password = input("请输入密码:")
if password == db[username]:
print("登录成功")
else:
print("密码错误登录失败")
else:
print("用户名不存在")
```
# 课后作业
1. 完成上述几个案例
2. 扩展案例三 基于文件的账户验证。为其增加注册功能并且让整个代码更加合理
3. 尝试一下实现密码错误3次封号的功能

View File

@@ -0,0 +1,196 @@
# Python 介绍
> Life is short, I need python人生苦短我用 python
## Python 起源
Python 的作者是著名的"**龟叔**" Guido van Rossum (吉多.范罗苏姆1989 年,龟叔为了打发无聊的圣诞节,决心开发一个新的**解释程序**,作为 ABC 的一种继承。于是便开始编写 Python。
<img src="Python环境部署/龟叔.png" alt="img-龟叔" style="zoom: 80%;" />
**ABC** 是由吉多参加设计的一种教学语言就吉多本人看来ABC 这种语言非常优美和强大,是**专门为非专业程序员设计的**。但是 ABC 并没有成功。**Guido** 本人看来,**ABC** 失败的原因是高级语言为时过早,并且平台迁移能力弱,难以添加新功能,仅仅专注于编程初学者,没有把有经验的编程人员纳入其中,在 **Python** 中解决了这些问题,让拓展模块的编写非常容易,并且可以在多平台进行运行....
Python 的意思是蟒蛇,是取自英国 20 世纪 70 年代首播的电视喜剧《蒙提.派森干的飞行马戏团》(Monty Pythons Flying Circus)Guido 非常喜欢这个剧,所以选择 Python 作为新语言的名字。
1991 年,第一个 Python 编译器诞生。它是用 C 语言实现的,并能够调用 C 语言的库文件。
## 解释型和编译型语言
解释型语言是指在运行时由解释器逐行读取和执行源代码的语言。在这种语言中代码不需要被提前编译成机器代码而是直接由解释器逐行解析并执行。这使得开发过程更加灵活程序员可以快速测试和修改代码。Python、JavaScript 和 Ruby 等都是常见的解释型语言。尽管解释型语言在开发时提供了便利,但由于逐行解释执行,通常在性能上不如编译型语言。
编译型语言则是指在执行之前源代码需要通过编译器转换成机器代码或中间代码。这个过程通常会产生一个独立的可执行文件运行时不再需要源代码或编译器。这种做法通常能提高程序的执行效率因为编译后的代码可以直接在机器上运行。C、C++ 和 Go 等语言都是编译型语言。虽然编译型语言在执行速度上表现出色,但编译过程通常较长,调试和修改代码时也不如解释型语言方便。
## 编程语言排行
- [TIOBE Index](https://www.tiobe.com/tiobe-index/)
- [PYPL Index](https://pypl.github.io/PYPL.html)
<img src="Python环境部署/编程语言排行.png" alt="img-编程语言排行" style="zoom:80%;" />
## Python 应用领域
**人工智能**
Python 是人工智能和机器学习领域的首选语言,主要得益于其简洁的语法和丰富的科学计算库。以下是几种典型的 Python 库:
- **NumPy**:支持大量的维度数组与矩阵运算,此外也针对数组运算提供了大量的数学函数库。
- **SciPy**:基于 NumPy 的科学计算库,提供了许多算法和函数,适用于数值积分与优化、线性代数、统计等科学计算任务。
- **Matplotlib**:强大的绘图库,可以生成各种图形,包括线图、散点图、柱状图等,常用于数据可视化。
- **TensorFlow**:由 Google 开发的开源深度学习框架,广泛用于构建和训练神经网络。
**云计算**
Python 是云计算领域最火的语言之一广泛用于构建和管理云基础设施。Python 的简洁性和可读性使得它成为开发云服务、自动化任务、数据处理脚本的理想选择。
**WEB开发**
Python 拥有众多优秀的 Web 框架,适合快速开发高效、安全的 Web 应用程序。许多大型网站和服务都是用 Python 开发的,例如 YouTube、Dropbox、豆瓣等。以下是几种典型的 Web 框架:
- **Django**:一个高层次的 Python Web 框架,鼓励快速开发和简洁、实用的设计,是全栈框架的代表。
- **Flask**:一个轻量级的 Web 框架,强调简单性和灵活性,适合构建小型项目或微服务。
**系统运维**
Python 是系统运维人员的必备语言。它可以用于编写脚本来自动化任务、管理服务器、处理文件和文本、与操作系统进行交互等。Python 的跨平台性使得它在不同的操作系统上都能有效发挥作用。
**金融**
Python 在金融领域特别是量化交易和金融分析方面得到广泛应用。Python 的灵活性和丰富的金融数据分析库,使得它在金融工程领域的使用日益增多,重要性逐年提高。
**图形界面开发 (GUI)**
Python 也可以用于开发桌面应用程序,以下是几种常用的图形界面开发库:
- **PyQt**:基于 Qt 框架的 Python 绑定,适合开发复杂的桌面应用程序。
- **WxPython**:基于 wxWidgets 的 Python GUI 库,提供了跨平台的原生控件。
- **TkInter**Python 的标准 GUI 库,适合初学者和轻量级应用的快速开发。
## Python 实际应用
**谷歌**Google App Engine、code.google.com、Google Earth、谷歌爬虫、Google 广告等项目都在大量使用 Python 开发。
**CIA**:美国中情局网站就是用 Python 开发的。
**NASA**美国航天局NASA大量使用 Python 进行数据分析和运算。
**YouTube**:世界上最大的视频网站 YouTube 就是用 Python 开发的。
**Dropbox**:美国最大的在线云存储网站,全部用 Python 实现,每天网站处理 10 亿个文件的上传和下载。
**Instagram**:美国最大的图片分享社交网站,每天超过 3000 万张照片被分享,全部用 Python 开发的。
**Facebook**:大量的基础库均通过 Python 实现的。
**Redhat**:世界上最流行的 Linux 发行版本中的 yum 包管理工具就是用 Python 开发的。
**豆瓣**:公司几乎所有的业务均是通过 Python 开发的。
**知乎**:国内最大的问答社区,通过 Python 开发(国外 Quora
除上面之外,还有搜狐、金山、腾讯、盛大、网易、百度、阿里、淘宝、土豆、新浪、果壳等公司都在使用 Python 完成各种各样的任务
## Python 设计哲学
<img src="Python环境部署/Python设计哲学.png" alt="img-Python设计哲学" style="zoom:80%;" />
Python 的设计哲学与其他编程语言相比,有几个显著的不同之处:
1. **可读性优先**
- Python 强调代码的可读性,力求清晰明了。这与如 C++ 或 Java 等语言相比,后者往往更关注性能或复杂的语法结构。
2. **简洁性**
- Python 鼓励用较少的代码实现功能,通常会有简化的语法,而像 Java 这样的语言则要求更多的样板代码。
3. **动态类型**
- Python 是动态类型语言,变量类型在运行时决定,这与静态类型语言(如 C# 和 Java形成鲜明对比后者在编译时必须声明变量类型。
4. **多范式支持**
- Python 支持多种编程范式(如面向对象、函数式编程),而一些语言则更倾向于某一特定范式,如 Java 主要是面向对象的。
5. **强大的标准库**
- Python 附带了一个丰富的标准库,提供了大量现成的模块和功能,而其他语言可能需要依赖外部库或框架。
6. **社区和文化**
- Python 拥有一个积极的社区,强调开放和共享,鼓励用户贡献代码和文档,而其他语言的社区文化可能更加保守或封闭。
# Python 环境部署
## Python 解释器
- 打开官网https://www.python.org/downloads
- 找到对应的版本,这里选择版本 `3.9.8`
**3.9.8 版本**下载链接https://www.python.org/ftp/python/3.9.8/python-3.9.8-amd64.exe
<img src="Python环境部署/Python解释器下载1.png" alt="img-Python解释器下载1" style="zoom:80%;" />
<img src="Python环境部署/Python解释器下载2.png" alt="img-Python解释器下载2" style="zoom:80%;" />
打开安装包,添加环境变量,可以选择默认安装或者自定义安装(一般自定义安装主要是修改安装路径)
<img src="Python环境部署/Python解释器安装1.png" alt="img-Python解释器安装1" style="zoom:80%;" />
<img src="Python环境部署/Python解释器安装2.png" alt="img-Python解释器安装2" style="zoom:80%;" />
**注意**:必须勾选上下面的 “Add Python 3.9 to PATH”添加到系统环境变量中
**测试是否安装成功**
在键盘上按下 `win+R` 然后在左下弹出窗口中输入 `cmd` 回车
在 cmd 终端中输入 python如果可以看到如下内容说明 python 环境安装成功,并且请核对版本号是否与我们安装的一致……
```bash
C:\Users\test>python
Python 3.9.8 (tags/v3.9.8:bb3fdcf, Nov 5 2021, 20:48:33) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>>
```
## Python 编辑器
Python 可用的编辑器有很多因为编辑器只负责编写代码实际的代码执行还是由我们刚刚安装的解释器进行编译解释。所以编写Python 代码对编辑器的要求不是很高。不过为了便于我们敲代码,我们还是要选择一些更加高级,功能更多的编辑器来使用。这样在编写代码的时候可以事半功倍
对于 Python 而言,最出名,最好用的编辑器就是 Pycharm 。我们后续学习也主要使用 Pycharm 编辑器。
### Pycharm
下载地址http://www.jetbrains.com/pycharm/download/#section=windows
因为社区版可能会缺少部分功能,所以直接选择专业版。
**安装过程**
1. 下载安装包并且安装
2. 使用 EaglesLab 提供的补丁工具进行激活
3. 查看设置 -> `About`
**上述操作环节较多,认真观看老师的操作~**
<img src="Python环境部署/Python编辑器-PyCharm激活.png" alt="img-Python编辑器-PyCharm激活" style="zoom:80%;" />
**创建第一个项目**
1. 点击New Project来创建一个项目项目Name和Location可以自定义
<img src="Python环境部署/新建项目.png" alt="img-新建项目" style="zoom:80%;" />
2. 在项目名称上右键来创建一个Python File
<img src="Python环境部署/新建文件.png" alt="img-新建文件" style="zoom: 80%;" />
3. 编写第一个 Python 代码
```python
print('Hello World!')
```
然后右键 -> `Run demo.py` 来运行
<img src="Python环境部署/运行文件.png" alt="img-运行文件" style="zoom:80%;" />
```bash
# Output
C:\Users\test\PycharmProjects\pythonProject\.venv\Scripts\python.exe C:\Users\test\PycharmProjects\pythonProject\demo.py
hello world
Process finished with exit code 0
```
### Vscode
是一款由微软开发且跨平台的免费源代码编辑器。该软件以扩展的方式支持语法高亮、代码自动补全、代码重构功能,并且内置了命令行工具和 Git 版本控制系统。

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 KiB

View File

@@ -0,0 +1,161 @@
# 协程
协程Coroutine是用户态的轻量级线程由程序自身控制调度通过协作式多任务实现并发。它能在单线程内挂起和恢复执行无需操作系统介入切换开销极小尤其适合 I/O 密集型任务​​(如网络请求、文件读写)。
**与线程/进程对比**
- **​资源消耗**​​:协程内存占用更低(共享进程内存),线程需独立栈空间,进程资源消耗最大
- **​切换开销​**​:协程切换在用户态完成,速度极快;线程/进程切换依赖操作系统,开销较大
- **适用场景**​​:协程适合高并发 I/O 操作;线程适合 CPU 密集型任务;进程适合多核并行计算
**核心优势**
- **高并发**​​:单线程可处理数千级并发连接(如 Web 服务器)
- **无锁机制​**​:避免多线程同步问题(如死锁、竞态条件)
- **代码简洁​**​:用同步语法写异步逻辑,避免回调地狱
# 实现方式
## 生成器函数
通过 yield 暂停执行并传递值:需要手动管理状态,适用简单场景
```python
def simple_coroutine():
print("协程启动")
x = yield # 暂停点,等待外部传入值
print(f"接收值: {x}")
coro = simple_coroutine()
next(coro) # 启动协程,执行到第一个 yield
coro.send(10) # 恢复执行x 赋值为 10
```
## async/await
通过 asyncio 库实现异步编程
```python
import asyncio
async def fetch_data(url):
print(f"请求 {url}")
await asyncio.sleep(1) # 挂起协程,让出控制权给事件循环,模拟异步等待
return f"来自 {url} 的数据"
async def main():
tasks = [fetch_data("url1"), fetch_data("url2")]
results = await asyncio.gather(*tasks) # 实现多任务并发调度:并发执行
print(results)
if __name__ == "__main__":
asyncio.run(main())
```
# 基本语法
**定义协程函数**
使用 `async def` 声明协程函数
```python
import asyncio
async def my_coroutine():
print("协程开始")
await asyncio.sleep(1) # 模拟 I/O 操作
print("协程结束")
```
**运行协程**
协程需要通过**事件循环**执行
```python
async def main():
await my_coroutine() # 等待协程完成
if __name__ == "__main__":
asyncio.run(main())
```
# 事件循环
事件循环是协程的调度核心,负责执行、切换和监控协程任务
```python
import asyncio
async def task1():
print("任务1开始")
await asyncio.sleep(2)
print("任务1结束")
async def task2():
print("任务2开始")
await asyncio.sleep(1)
print("任务2结束")
async def main():
await asyncio.gather(task1(), task2()) # 并发执行多个协程
if __name__ == "__main__":
asyncio.run(main())
```
# 进阶用法
## 任务
将协程封装为任务,更灵活地控制执行
```python
async def main():
task = asyncio.create_task(my_coroutine()) # 创建任务
await task # 等待任务完成
```
## 超时控制
设置协程执行的超时时间
```python
async def slow_task():
await asyncio.sleep(10)
async def main():
try:
await asyncio.wait_for(slow_task(), timeout=3)
except asyncio.TimeoutError:
print("任务超时")
```
## 协程同步
使用锁 `Lock` 保护共享资源
```python
lock = asyncio.Lock()
async def safe_write():
async with lock: # 异步上下文管理器
# 安全地操作共享资源
pass
```
# 课后作业
- [必须] 动手完成本章节案例
- [扩展] 阅读官方文档相关章节
- [扩展] 用协程实现进程章节的爬虫案例

View File

@@ -0,0 +1,169 @@
# 手工操作 - 穿孔卡片
1946年第一台计算机诞生--20世纪50年代中期计算机工作还在采用手工操作方式。此时还没有操作系统的概念。
![img-穿孔卡片1](操作系统发展史/穿孔卡片1.png)
![img-穿孔卡片2](操作系统发展史/穿孔卡片2.png)
程序员将对应于程序和数据的已穿孔的纸带(或卡片)装入输入机,然后启动输入机把程序和数据输入计算机内存,接着通过控制台开关启动程序针对数据运行;计算完毕,打印机输出计算结果;用户取走结果并卸下纸带(或卡片)后,才让下一个用户上机。
手工操作方式两个特点:
1. 用户独占全机。不会出现因资源已被其他用户占用而等待的现象,但资源的利用率低。
2. CPU 等待手工操作。CPU的利用不充分。
20世纪50年代后期出现人机矛盾手工操作的慢速度和计算机的高速度之间形成了尖锐矛盾手工操作方式已严重损害了系统资源的利用率使资源利用率降为百分之几甚至更低不能容忍。唯一的解决办法只有摆脱人的手工操作实现作业的自动过渡。这样就出现了成批处理。
# 批处理 - 磁带存储
批处理系统:加载在计算机上的一个系统软件,在它的控制下,计算机能够自动地、成批地处理一个或多个用户的作业(这作业包括程序、数据和命令)。
## 联机批处理系统
首先出现的是联机批处理系统,即作业的输入/输出由CPU来处理。
![img-手工操作计算机](操作系统发展史/手工操作计算机.png)
主机与输入机之间增加一个存储设备——磁带,在运行于主机上的监督程序的自动控制下,计算机可自动完成:成批地把输入机上的用户作业读入磁带,依次把磁带上的用户作业读入主机内存并执行并把计算结果向输出机输出。完成了上一批作业后,监督程序又从输入机上输入另一批作业,保存在磁带上,并按上述步骤重复处理。
监督程序不停地处理各个作业,从而实现了作业到作业的自动转接,减少了作业建立时间和手工操作时间,有效克服了人机矛盾,提高了计算机的利用率。
但是在作业输入和结果输出时主机的高速CPU仍处于空闲状态等待慢速的输入/输出设备完成工作: 主机处于“忙等”状态。
## 脱机批处理系统
为克服与缓解高速主机与慢速外设的矛盾提高CPU的利用率又引入了脱机批处理系统即输入/输出脱离主机控制。
![img-脱机批处理系统](操作系统发展史/脱机批处理系统.png)
卫星机:一台不与主机直接相连而专门用于与输入/输出设备打交道的。
1. 从输入机上读取用户作业并放到输入磁带上。
2. 从输出磁带上读取执行结果并传给输出机
不足:每次主机内存中仅存放一道作业,每当它运行期间发出输入/输出I/O请求后高速的CPU便处于等待低速的I/O完成状态致使CPU空闲。
# 多道程序系统
## 多道程序设计技术
所谓多道程序设计技术就是指允许多个程序同时进入内存并运行。即同时把多个程序放入内存并允许它们交替在CPU中运行它们共享系统中的各种硬、软件资源。当一道程序因I/O请求而暂停运行时CPU便立即转去运行另一道程序。
![img-单道程序工作示例](操作系统发展史/单道程序工作示例.png)
在A程序计算时I/O空闲 A程序I/O操作时CPU空闲B程序也是同样必须A工作完成后B才能进入内存中开始工作两者是串行的全部完成共需时间=T1+T2。
![img-多道程序工作示例](操作系统发展史/多道程序工作示例.png)
将A、B两道程序同时存放在内存中它们在系统的控制下可相互穿插、交替地在CPU上运行当A程序因请求I/O操作而放弃CPU时B程序就可占用CPU运行这样 CPU不再空闲而正进行A I/O操作的I/O设备也不空闲显然CPU和I/O设备都处于“忙”状态大大提高了资源的利用率从而也提高了系统的效率A、B全部完成所需时间<<T1+T2
单处理机系统中多道程序运行时的特点
1. 多道计算机内存中同时存放几道相互独立的程序
2. 宏观上并行同时进入系统的几道程序都处于运行过程中即它们先后开始了各自的运行但都未运行完毕
3. 微观上串行实际上各道程序轮流地用CPU并交替运行
多道程序系统的出现标志着操作系统渐趋成熟的阶段先后出现了作业调度管理处理机管理存储器管理外部设备管理文件系统管理等功能
由于多个程序同时在计算机中运行开始有了**空间隔离**的概念只有内存空间的隔离才能让数据更加安全稳定
出了空间隔离之外多道技术还第一次体现了**时空复用**的特点遇到IO操作就切换程序使得cpu的利用率提高了计算机的工作效率也随之提高
## 多道批处理系统
20世纪60年代中期在前述的批处理系统中引入多道程序设计技术后形成多道批处理系统简称批处理系统)。
 它有两个特点
1. 多道系统内可同时容纳多个作业这些作业放在外存中组成一个后备队列系统按一定的调度原则每次从后备作业队列中选取一个或多个作业进入内存运行运行作业结束退出运行和后备作业进入运行均由系统自动实现从而在系统中形成一个自动转接的连续的作业流
2. 成批在系统运行过程中不允许用户与其作业发生交互作用作业一旦进入系统用户就不能直接干预其作业的运行
- 批处理系统的追求目标提高系统资源利用率和系统吞吐量以及作业流程的自动化
- 批处理系统的一个重要缺点不提供人机交互能力给用户使用计算机带来不便
- 虽然用户独占全机资源并且直接控制程序的运行可以随时了解程序运行情况但这种工作方式因独占全机造成资源效率极低
- 一种新的追求目标既能保证计算机效率又能方便用户使用计算机 20世纪60年代中期计算机技术和软件技术的发展使这种追求成为可能
# 分时系统
由于CPU速度不断提高和采用分时技术一台计算机可同时连接多个用户终端而每个用户可在自己的终端上联机使用计算机好象自己独占机器一样
![img-传统PLC运行模式](操作系统发展史/传统PLC运行模式.png)
分时技术把处理机的运行时间分成很短的时间片按时间片轮流把处理机分配给各联机作业使用
若某个作业在分配给它的时间片内不能完成其计算则该作业暂时中断把处理机让给另一作业使用等待下一轮时再继续其运行由于计算机速度很快作业运行轮转得很快给每个用户的印象是好象他独占了一台计算机而每个用户可以通过自己的终端向系统发出各种操作控制命令在充分的人机交互情况下完成作业的运行
具有上述特征的计算机系统称为分时系统它允许多个用户同时联机使用计算机
特点
1. 多路性若干个用户同时使用一台计算机微观上看是各用户轮流使用计算机宏观上看是各用户并行工作
2. 交互性用户可根据系统对请求的响应结果进一步向系统提出新的请求这种能使用户与系统进行人机对话的工作方式明显地有别于批处理系统因而分时系统又被称为交互式系统
3. 独立性用户之间可以相互独立操作互不干扰系统保证各用户程序运行的完整性不会发生相互混淆或破坏现象
4. 及时性系统可对用户的输入及时作出响应分时系统性能的主要指标之一是响应时间它是指从终端发出命令到系统予以应答所需的时间
**分时系统的主要目标**对用户响应的及时性即不至于用户等待每一个命令的处理时间过长
分时系统可以同时接纳数十个甚至上百个用户由于内存空间有限往往采用对换又称交换方式的存储方法即将未轮到的作业放入磁盘一旦轮到”,再将其调入内存而时间片用完后又将作业存回磁盘俗称滚进”、“滚出使同一存储区域轮流为多个用户服务
**注意分时系统的分时间片工作在没有遇到IO操作的时候就用完了自己的时间片被切走了这样的切换工作其实并没有提高cpu的效率反而使得计算机的效率降低了。但是我们牺牲了一点效率却实现了多个程序共同执行的效果这样你就可以在计算机上一边听音乐一边聊qq了。**
# 实时系统
虽然多道批处理系统和分时系统能获得较令人满意的资源利用率和系统响应时间但却不能满足实时控制与实时信息处理两个应用领域的需求于是就产生了实时系统即系统能够及时响应随机发生的外部事件并在严格的时间范围内完成对该事件的处理
实时系统在一个特定的应用中常作为一种控制设备来使用
**实时系统可分成两类**
1. 实时控制系统当用于飞机飞行导弹发射等的自动控制时要求计算机能尽快处理测量系统测得的数据及时地对飞机或导弹进行控制或将有关信息通过显示终端提供给决策人员当用于轧钢石化等工业生产过程控制时也要求计算机能及时处理由各类传感器送来的数据然后控制相应的执行机构
2. 实时信息处理系统当用于预定飞机票查询有关航班航线票价等事宜时或当用于银行系统情报检索系统时都要求计算机能对终端设备发来的服务请求及时予以正确的回答此类对响应及时性的要求稍弱于第一类
**实时操作系统的主要特点**
3. 及时响应每一个信息接收分析处理和发送的过程必须在严格的时间限制内完成
4. 高可靠性需采取冗余措施双机系统前后台工作也包括必要的保密措施等
分时——现在流行的PC服务器都是采用这种运行模式即把CPU的运行分成若干时间片分别处理不同的运算请求 linux系统
实时——一般用于单片机上PLC等比如电梯的上下控制中对于按键等动作要求进行实时处理
# 通用操作系统
操作系统的三种基本类型多道批处理系统分时系统实时系统
- 通用操作系统具有多种类型操作特征的操作系统可以同时兼有多道批处理分时实时处理的功能或其中两种以上的功能
- 实时处理+批处理=实时批处理系统。首先保证优先处理实时任务,插空进行批处理作业。常把实时任务称为前台作业,批作业称为后台作业。
- 分时处理+批处理=分时批处理系统。即:时间要求不强的作业放入“后台”(批处理)处理,需频繁交互的作业在“前台”(分时)处理,处理机优先运行“前台”作业。
从上世纪60年代中期国际上开始研制一些大型的通用操作系统这些系统试图达到功能齐全可适应各种应用范围和操作方式变化多端的环境的目标但是这些系统过于复杂和庞大不仅付出了巨大的代价且在解决其可靠性可维护性和可理解性方面都遇到很大的困难相比之下UNIX操作系统却是一个例外这是一个通用的多用户分时交互型的操作系统它首先建立的是一个精干的核心而其功能却足以与许多大型的操作系统相媲美在核心层以外可以支持庞大的软件系统它很快得到应用和推广并不断完善对现代操作系统有着重大的影响至此操作系统的基本概念功能基本结构和组成都已形成并渐趋完善
# 操作系统的进一步发展
进入20世纪80年代大规模集成电路工艺技术的飞跃发展微处理机的出现和发展掀起了计算机大发展大普及的浪潮一方面迎来了个人计算机的时代同时又向计算机网络分布式处理巨型计算机和智能化方向发展于是操作系统有了进一步的发展个人计算机操作系统网络操作系统分布式操作系统等
## 个人计算机操作系统
个人计算机上的操作系统是联机交互的单用户操作系统它提供的联机交互功能与通用分时系统提供的功能很相似
由于是个人专用因此一些功能会简单得多然而由于个人计算机的应用普及对于提供更方便友好的用户接口和丰富功能的文件系统的要求会愈来愈迫切
## 网络操作系统
计算机网络通过通信设施将地理上分散的具有自治功能的多个计算机系统互连起来实现信息交换资源共享互操作和协作处理的系统
网络操作系统在原来各自计算机操作系统上按照网络体系结构的各个协议标准增加网络管理模块其中包括通信资源共享系统安全和各种网络应用服务
## 分布式操作系统
表面上看分布式系统与计算机网络系统没有多大区别分布式操作系统也是通过通信网络将地理上分散的具有自治功能的数据处理系统或计算机系统互连起来实现信息交换和资源共享协作完成任务。——硬件连接相同
但有如下一些明显的区别
1. 分布式系统要求一个统一的操作系统实现系统操作的统一性
2. 分布式操作系统管理分布式系统中的所有资源它负责全系统的资源分配和调度任务划分信息传输和控制协调工作并为用户提供一个统一的界面
3. 用户通过这一界面实现所需要的操作和使用系统资源至于操作定在哪一台计算机上执行或使用哪台计算机的资源则是操作系统完成的用户不必知道此谓系统的透明性
4. 分布式系统更强调分布式计算和处理因此对于多机合作和系统重构坚强性和容错能力有更高的要求希望系统有更短的响应时间高吞吐量和高可靠性
# 操作系统的作用
现代的计算机系统主要是由一个或者多个处理器主存硬盘键盘鼠标显示器打印机网络接口及其他输入输出设备组成
一般而言现代计算机系统是一个复杂的系统
其一如果每位应用程序员都必须掌握该系统所有的细节那就不可能再编写代码了严重影响了程序员的开发效率全部掌握这些细节可能需要一万年....
其二并且管理这些部件并加以优化使用是一件极富挑战性的工作于是计算安装了一层软件系统软件称为操作系统它的任务就是为用户程序提供一个更好更简单更清晰的计算机模型并管理刚才提到的所有设备
**总结:**
1. 程序员无法把所有的硬件操作细节都了解到管理这些硬件并且加以优化使用是非常繁琐的工作这个繁琐的工作就是操作系统来干的有了他程序员就从这些繁琐的工作中解脱了出来只需要考虑自己的应用软件的编写就可以了应用软件直接使用操作系统提供的功能来间接使用硬件
2. 精简的说的话操作系统是一个协调管理和控制计算机硬件资源和软件资源的控制程序

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@@ -0,0 +1,481 @@
# 操作系统线程理论
## 进程
进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
## 线程
60年代在 OS 中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了很多弊端:
1. 进程是资源拥有者,创建、撤消与切换存在较大的时空开销,因此需要引入轻型进程。
2. 对称多处理机SMP出现可以满足多个运行单位而多个进程并行开销过大。
因此在 80 年代出现了能独立运行的基本单位线程Threads。进程是资源分配的最小单位线程是CPU调度的最小单位每一个进程中至少有一个线程。 
## 进程和线程的关系
![img-进程和线程关系](线程/进程和线程关系.png)
线程与进程的区别可以归纳为以下4点
1. 地址空间和其它资源共享(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
2. 通信进程间通信IPC线程间可以直接读写进程数据段如全局变量来进行通信需要进程同步和互斥手段的辅助以保证数据的一致性。
3. 调度和切换:线程上下文切换比进程上下文切换要快得多。
4. 多线程操作系统中,进程不是一个可执行的实体。
## 使用线程的实际场景
开启一个打字处理软件进程,该进程肯定需要办不止一件事情,比如监听键盘输入,处理文字,定时自动将文字保存到硬盘,这三个任务操作的都是同一块数据,因而不能用多进程。只能在一个进程里并发地开启三个线程,如果是单线程,那就只能是,键盘输入时,不能处理文字和自动保存,自动保存时又不能输入和处理文字。
## 内存中的线程
![img-内存中的线程](线程/内存中的线程.png)
线程通常是有益的,但是带来了不小程序设计难度,线程的问题是:
1. 父进程有多个线程,那么开启的子线程是否需要同样多的线程
2. 在同一个进程中,如果一个线程关闭了文件,而另外一个线程正准备往该文件内写内容呢?
因此,在多线程的代码中,需要更多的心思来设计程序的逻辑、保护程序的数据。
# python 线程使用
## 全局解释器锁 GIL
Python 代码的执行由 Python 解释器主循环控制。Python 在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。对 Python 解释器的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
在多线程环境中Python 解释器按以下方式执行:
![img-GIL](线程/GIL.png)
1. 设置 GIL
2. 切换到一个线程去运行
3. 运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0))
4. 把线程设置为睡眠状态
5. 解锁 GIL
6. 再次重复以上所有步骤。
## 创建线程
**直接创建线程对象**
```python
from threading import Thread
import time
def task(name, delay):
print(f"{name} 开始执行")
time.sleep(delay)
print(f"{name} 执行完毕")
if __name__ == "__main__":
# 通过 Thread 类实例化指定目标函数target和参数args/kwargs
t1 = Thread(target=task, args=("线程A", 2))
t1.start() # 启动线程
t1.join() # 等待线程结束
```
**继承 Thread 类**
```python
from threading import Thread
import time
# 通过子类化 Thread 并重写 run() 方法
class MyThread(Thread):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print(f"{self.name} 运行中...")
time.sleep(1)
if __name__ == "__main__":
t1 = MyThread("自定义线程")
t1.start()
```
## 多线程
**示例代码**:多线程运行
```python
import threading
import time
import os
def task(name, delay):
print(f"当前线程 ID (Python标识符): {threading.get_ident()}")
print(f"线程对象标识符: {threading.current_thread().ident}")
print(f"{name}-{os.getpid()} 开始执行")
time.sleep(delay)
print(f"{name}-{os.getpid()} 执行完毕")
if __name__ == "__main__":
threads = [threading.Thread(target=task, args=(f"线程{i}", 2)) for i in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
print("主线程/主进程pid", os.getpid())
```
## 常用方法
| 方法 | 含义 |
| :------ | :------- |
| `Thread.isAlive()` | Thread 类中的对象方法:返回线程是否活动的 |
| `Thread.getName()` | 返回线程名 |
| `Thread.setName()` | 设置线程名 |
| `threading.currentThread()` | 返回当前的线程变量 |
| `threading.enumerate()` | 返回一个包含正在运行的线程的列表 |
| `threading.activeCount()` | 返回正在运行的线程数量 |
**示例代码**
```python
from threading import Thread
import threading
from multiprocessing import Process
import os
def work():
import time
time.sleep(3)
print(threading.current_thread().getName())
if __name__ == "__main__":
t = Thread(target=work)
t.start()
print(t.is_alive())
print(threading.current_thread().getName())
print(threading.current_thread())
print(threading.enumerate())
print(threading.active_count())
t.join()
print("主线程/主进程")
print(t.is_alive())
```
## 守护线程
在 Python 中守护线程Daemon Thread 是一种特殊的线程,它的生命周期与主线程(或程序的主进程)绑定。当所有非守护线程(即普通线程)结束时,无论守护线程是否完成任务,它都会被强制终止。这种机制常用于执行后台支持任务(如日志记录、心跳检测等),无需等待其完成。
**核心特性**
- **依赖主线程存活**:主线程结束时,守护线程立即终止(即使任务未完成)。
- **后台服务**:通常用于非关键性任务,即使意外终止也不会影响程序逻辑。
- **资源释放风险**:守护线程被终止时,可能不会正常释放资源(如文件句柄、网络连接),需谨慎使用。
**示例代码**
```python
import threading
import time
def background_task():
while True:
print("守护线程运行中...")
time.sleep(1)
# 创建线程并设置为守护线程
daemon_thread = threading.Thread(target=background_task)
daemon_thread.daemon = True
daemon_thread.start()
# 主线程执行其他操作
time.sleep(3)
print("主线程结束,守护线程将被终止")
```
# 线程同步机制
## 互斥锁
保证同一时刻只有一个线程能访问共享资源,防止数据竞争。
**代码示例**
```python
import threading
import time
def increment():
global shared_counter
with lock: # 自动获取和释放锁lock.acquire() 和 lock.release()
tmp = shared_counter + 1
time.sleep(0.1)
shared_counter = tmp
if __name__ == "__main__":
shared_counter = 0
lock = threading.Lock()
# 启动多个线程修改共享变量
threads = [threading.Thread(target=increment) for _ in range(100)]
for t in threads:
t.start()
for t in threads:
t.join()
print(shared_counter) # 输出 100无竞争
```
## 死锁与可重入锁
**死锁**:两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为**死锁进程**
**示例代码**
```python
from threading import Lock as Lock
import time
mutexA = Lock()
mutexA.acquire()
mutexA.acquire() # 上面已经拿过一次key了这边就拿不到了,程序被阻塞到这里
print(123)
mutexA.release()
mutexA.release()
```
**可重入锁**`threading.RLock` 允许同一线程多次获取锁避免死锁。RLock 内部维护着一个 Lock和一个 counter 变量counter 记录了 acquire 的次数,从而使得资源可以被多次 acquire。直到一个线程所有的 acquire 都被 release其他的线程才能获得资源。
```python
from threading import RLock as Lock
import time
mutexA=Lock()
mutexA.acquire()
mutexA.acquire()
print(123)
mutexA.release()
mutexA.release()
```
## 同步锁
- 协调线程间的执行顺序(如生产者-消费者模型)。
- 控制并发数量(如限制同时访问数据库的连接数)。
### 信号量
控制同时访问资源的线程数量:适用于限制并发数
```python
import threading
semaphore = threading.Semaphore(3) # 最多允许3个线程同时运行
def task():
with semaphore:
print(f"{threading.current_thread().name} 正在工作")
# 模拟耗时操作
threading.Event().wait(3)
# 启动10个线程但最多3个并发执行
threads = [threading.Thread(target=task) for _ in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
```
### 条件变量
实现线程间通知机制:适用于生产者-消费者模型
```python
import threading
queue = []
condition = threading.Condition()
def producer():
with condition:
queue.append("EaglesLab")
condition.notify() # 通知等待的消费者
def consumer():
with condition:
while not queue:
condition.wait() # 等待生产者通知
data = queue.pop()
print(f"消费数据: {data}")
# 启动生产者和消费者线程
threading.Thread(target=producer).start()
threading.Thread(target=consumer).start()
```
### 事件
简单线程间状态通知:事件常用于跨线程的状态同步。
```python
import threading
event = threading.Event()
def waiter():
print("等待事件触发...")
event.wait() # 阻塞直到事件被设置
print("事件已触发!")
def setter():
threading.Event().wait(2)
event.set() # 设置事件
threading.Thread(target=waiter).start()
threading.Thread(target=setter).start()
```
# 线程池
线程池通过预创建并复用一组线程,减少频繁创建/销毁线程的开销,适用于 **I/O 密集型任务**​​(如网络请求、文件读写)
- ​​优点​​:资源复用、负载均衡、简化线程管理。
- 适用场景批量下载、Web 服务器请求处理、数据库并发查询。
## 基本操作
通过 `concurrent.futures.ThreadPoolExecutor` 实现
```python
from concurrent.futures import ThreadPoolExecutor
def task(n):
return n * n
# 创建线程池(推荐使用 with 上下文管理)
with ThreadPoolExecutor(max_workers=5) as executor:
# 提交任务方式1submit 逐个提交
future = executor.submit(task, 5)
print(future.result()) # 输出 25
# 提交任务方式2map 批量提交
results = executor.map(task, [1, 2, 3])
print(list(results))
```
## 注意事项
- **线程数量**​​:建议设为 CPU 核心数 × 2I/O 密集型)
- **异常处理**​​:通过 `try-except` 捕获 `future.result()` 的异常
- **资源释放​**​:使用 `shutdown()` 或上下文管理器自动关闭线程池
## 同步机制结合
当多个线程访问共享资源(如全局变量、文件)时,需通过同步机制避免资源竞争和数据不一致。
**代码示例**
```python
from concurrent.futures import ThreadPoolExecutor
from threading import Lock
def task():
global counter
with lock: # 使用锁保护共享变量
counter += 1
if __name__ == "__main__":
counter = 0
lock = Lock()
with ThreadPoolExecutor(max_workers=5) as executor:
futures = [executor.submit(task) for _ in range(100)]
for future in futures:
future.result()
print(f"最终计数:{counter}")
```
# 案例解析
## 案例1
基于条件变量同步机制,实现多线程-生产/消费者模型完整版本
```python
import threading
def producer(i):
with condition:
queue.append(f"EaglesLab {i}")
condition.notify() # 通知等待的消费者
def consumer(i):
with condition:
# 等待直到队列不为空或生产结束
while not queue and not producer_done:
condition.wait() # 等待生产者通知
if queue:
data = queue.pop()
elif producer_done:
return
print(f"消费者-{i} 消费数据: {data}")
if __name__ == "__main__":
queue = []
condition = threading.Condition() # 初始化条件变量
producer_done = False
# 启动生产者和消费者线程
pt = [threading.Thread(target=producer, args=(i,)) for i in range(3)]
ct = [threading.Thread(target=consumer, args=(i,)) for i in range(10)]
for t in pt + ct:
t.start()
with condition:
producer_done = True
condition.notify_all()
for t in pt:
t.join()
for t in ct:
t.join()
print("Main Process/Thread Done...")
```
# 课后作业
- [必须] 动手完成本章节案例
- [扩展] 阅读官方文档相关章节
- [扩展] 用多线程实现进程章节的爬虫案例

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,994 @@
# 进程
进程Process是计算机中**的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。**在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
狭义定义:**进程是正在运行的程序的实例**an instance of a computer program that is being executed
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
1. 进程是一个实体。每一个进程都有它自己的地址空间一般情况下包括文本区域text region、数据区域data region和堆栈stack region。文本区域存储处理器执行的代码数据区域存储变量和进程执行期间使用的动态分配的内存堆栈区域存储着活动过程调用的指令和本地变量。
2. 进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
# 进程调度方式
**扩展阅读**
要想多个进程交替运行,操作系统必须对这些进程进行调度,这个调度也不是随即进行的,而是需要遵循一定的法则,由此就有了进程的调度算法。
- **先来先服务调度FCFS, First-Come, First-Served**
先来先服务FCFS调度算法是一种最简单的调度算法该算法既可用于作业调度也可用于进程调度。FCFS算法比较有利于长作业进程而不利于短作业进程。由此可知本算法适合于CPU繁忙型作业而不利于I/O繁忙型的作业进程
- **短作业优先调度SJF, Shortest Job First**
短作业进程优先调度算法SJ/PF是指对短作业或短进程优先调度的算法该算法既可用于作业调度也可用于进程调度。但其对长作业不利不能保证紧迫性作业进程被及时处理作业的长短只是被估算出来的。
- **最高优先级调度Priority Scheduling**
每个进程被赋予一个优先级。系统总是选择优先级最高数值最小或最大的进程执行。如果两个进程有相同优先级则可以按FCFS调度。
- **时间片轮转调度Round Robin, RR**
时间片轮转(Round RobinRR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例。在时间片轮转法中需要将CPU的处理时间分成固定大小的时间片例如几十毫秒至几百毫秒。如果一个进程在被调度选中之后用完了系统规定的时间片但又未完成要求的任务则它自行释放自己所占有的CPU而排到就绪队列的末尾等待下一次调度。同时进程调度程序又去调度当前就绪队列中的第一个进程。
显然轮转法只能用来调度分配一些可以抢占的资源。这些可以抢占的资源可以随时被剥夺而且可以将它们再分配给别的进程。CPU是可抢占资源的一种。但打印机等资源是不可抢占的。由于作业调度是对除了CPU之外的所有系统硬件资源的分配其中包含有不可抢占资源所以作业调度不使用轮转法。
在轮转法中,时间片长度的选取非常重要。首先,时间片长度的选择会直接影响到系统的开销和响应时间。如果时间片长度过短,则调度程序抢占处理机的次数增多。这将使进程上下文切换次数也大大增加,从而加重系统开销。反过来,如果时间片长度选择过长,例如,一个时间片能保证就绪队列中所需执行时间最长的进程能执行完毕,则轮转法变成了先来先服务法。时间片长度的选择是根据系统对响应时间的要求和就绪队列中所允许最大的进程数来确定的。
- **多级反馈队列调度Multilevel Feedback Queue, MLFQ**
前面介绍的各种用作进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,则短进程优先和基于进程长度的抢占式调度算法都将无法使用。
而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。在采用多级反馈队列调度算法的系统中,调度算法的实施过程如下所述。
1. 应设置多个就绪队列并为各个队列赋予不同的优先级。第一个队列的优先级最高第二个队列次之其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同在优先权愈高的队列中为每个进程所规定的执行时间片就愈小。例如第二个队列的时间片要比第一个队列的时间片长一倍……第i+1个队列的时间片要比第i个队列的时间片长一倍。
2. 当一个新进程进入内存后首先将它放入第一队列的末尾按FCFS原则排队等待调度。当轮到该进程执行时如它能在该时间片内完成便可准备撤离系统如果它在一个时间片结束时尚未完成调度程序便将该进程转入第二队列的末尾再同样地按FCFS原则等待调度执行如果它在第二队列中运行一个时间片后仍未完成再依次将它放入第三队列……如此下去当一个长作业(进程)从第一队列依次降到第n队列后在第n 队列便采取按时间片轮转的方式运行。
3. 仅当第一队列空闲时调度程序才调度第二队列中的进程运行仅当第1(i-1)队列均空时才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时又有新进程进入优先权较高的队列(第1(i-1)中的任何一个队列)则此时新进程将抢占正在运行进程的处理机即由调度程序把正在运行的进程放回到第i队列的末尾把处理机分配给新到的高优先权进程。
# 进程的并行与并发
**并行**并行是指两者同时执行比如赛跑两个人都在不停的往前跑资源够用比如三个线程四核的CPU
**并发**:并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核CPU资源)同时只能过一个人A走一段后让给BB用完继续给A交替使用目的是提高效率。
**并行**:是从微观上,也就是在一个精确的时间片刻,有不同的程序在执行,这就要求必须有多个处理器。
**并发**是从宏观上在一个时间段上可以看出是同时执行的比如一个服务器同时处理多个session。
# 同步异步阻塞非阻塞
## 进程状态介绍
<img src="进程/进程状态转换图1.png" alt="img-进程状态转换图1" />
在了解其他概念之前,我们首先要了解进程的几个状态。在程序运行的过程中,由于被操作系统的调度算法控制,程序会进入几个状态:就绪,运行和阻塞。
- 就绪(Ready)状态当进程已分配到除CPU以外的所有必要的资源只要获得处理机便可立即执行这时的进程状态称为就绪状态。
- 执行/运行Running状态当进程已获得处理机其程序正在处理机上执行此时的进程状态称为执行状态。
- 阻塞(Blocked)状态正在执行的进程由于等待某个事件发生而无法执行时便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种例如等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。
<img src="进程/进程状态转换图2.png" alt="img-进程状态转换图2" style="zoom:80%;" />
## 同步和异步
所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。
所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。
## 阻塞与非阻塞
阻塞和非阻塞这两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关。也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的
## 同步/异步与阻塞/非阻塞
- 同步阻塞形式:效率最低。就是你专心排队,什么别的事都不做。
- 异步阻塞形式:效率较高。如果在银行等待办理业务的人采用的是异步的方式去等待消息被触发(通知),也就是领了一张小纸条,假如在这段时间里他不能离开银行做其它的事情,那么很显然,这个人被阻塞在了这个等待的操作上面;异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞。
- 同步非阻塞形式:效率低下。想象一下你一边打着电话一边还需要抬头看到底队伍排到你了没有,如果把打电话和观察排队的位置看成是程序的两个操作的话,这个程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的。
- 异步非阻塞形式:效率更高。因为打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不同的操作中来回切换。比如说,这个人突然发觉自己烟瘾犯了,需要出去抽根烟,于是他告诉大堂经理说,排到我这个号码的时候麻烦到外面通知我一下,那么他就没有被阻塞在这个等待的操作上面,自然这个就是异步+非阻塞的方式了。
很多人会把同步和阻塞混淆是因为很多时候同步操作会以阻塞的形式表现出来同样的很多人也会把异步和非阻塞混淆因为异步操作一般都不会在真正的IO操作处被阻塞。
# 进程的创建与结束
## 进程的创建
但凡是硬件,都需要有操作系统去管理,只要有操作系统,就有进程的概念,就需要有创建进程的方式,一些操作系统只为一个应用程序设计,比如微波炉中的控制器,一旦启动微波炉,所有的进程都已经存在。
而对于通用系统跑很多应用程序需要有系统运行过程中创建或撤销进程的能力主要分为4中形式创建新的进程
1. 系统初始化查看进程linux中用ps命令windows中用任务管理器前台进程负责与用户交互后台运行的进程与用户无关运行在后台并且只在需要时才唤醒的进程称为守护进程如电子邮件、web页面、新闻、打印
2. 一个进程在运行过程中开启了子进程如nginx开启多进程os.fork,subprocess.Popen等
3. 用户的交互式请求,而创建一个新进程(如用户双击暴风影音)
4. 一个批处理作业的初始化(只在大型机的批处理系统中应用)
无论哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的。
## 进程的结束
1. 正常退出自愿如用户点击交互式页面的叉号或程序执行完毕调用发起系统调用正常退出在linux中用exit在windows中用ExitProcess
2. 出错退出自愿python a.py中a.py不存在
3. 严重错误非自愿执行非法指令如引用不存在的内存1/0等可以捕捉异常try...except...
4. 被其他进程杀死非自愿如kill -9
# Python 多进程编程
我们可以使用 python 中的 **multiprocess** 包来实现多进程编程。
由于 multiprocess 包提供的子模块非常多,为了方便大家归类记忆,将这部分大致分为四个部分:创建进程部分,进程同步部分,进程池部分,进程之间数据共享。
## 进程创建
process 模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建。
**直接创建**Process 类 + target 函数
```python
from multiprocessing import Process
p = Process(target=func, args=(arg1,))
p.start()
```
**​​继承类创建**​​:自定义 Process 子类,重写 run() 方法
```python
from multiprocessing import Process
class MyProcess(Process):
def run(self):
print('Hello World!')
```
**代码示例**
```python
# 直接创建
from multiprocessing import Process
def func(name):
print("hello %s" % name)
print("子进程结束")
if __name__ == '__main__':
p = Process(target=func, args=('nls',)) # 实例化对象子进程p 传递函数名和函数的实参
p.start() # 启动子进程
print("主进程结束...")
# 继承类创建
import os
from multiprocessing import Process
class MyProcess(Process):
def __init__(self,name):
super().__init__()
self.name=name
def run(self):
print(os.getpid())
print('%s 正在和女主播聊天' %self.name)
if __name__ == '__main__':
p1 = MyProcess('张三')
p1.start()
print('主进程结束...')
```
**方法介绍:**
| 方法 | 含义 |
| :----------------- | :------------------------------------------------------------ |
| `p.start()` | 启动进程,并调用该子进程中的 `p.run() ` |
| `p.run()` | 进程启动时运行的方法正是它去调用target指定的函数我们自定义类的类中一定要实现该方法 |
| `p.terminate()` | 强制终止进程 p不会进行任何清理操作如果 p 创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果 p 还保存了一个锁那么也将不会被释放,进而导致死锁 |
| `p.is_alive()` | 如果 p 仍然运行,返回 True |
| `p.join([timeout])` | 主线程等待p终止强调是主线程处于等的状态而p是处于运行的状态。timeout是可选的超时时间需要强调的是p.join只能join住start开启的进程而不能join住run开启的进程 |
| `p.daemon()` | 默认值为 False如果设为 True代表 p 为后台运行的守护进程当p的父进程终止时p 也随之终止,并且设定为 True 后p 不能创建自己的新进程必须在p.start()之前设置 |
| `p.name()` | 进程的名称 |
| `p.pid()` | 进程的 pid |
| `p.exitcode()` | 进程在运行时为None、如果为N表示被信号N结束 |
| `p.authkey()` | 进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性这类连接只有在具有相同的身份验证键时才能成功 |
注意在Windows操作系统中由于没有fork(linux操作系统中创建进程的机制),在创建子进程的时候会自动 import 启动它的这个文件,而在 import 的时候又执行了整个文件。因此如果将 process() 直接写在文件中就会无限递归创建子进程报错。所以必须把创建子进程的部分使用 `if __name__ =='__main__'` 判断保护起来import 的时候,就不会递归运行了。
### join
正常情况下,是主进程先执行结束,然后等待子进程执行结束以后,整个程序退出。如果在 start 了以后使用 join那么将会阻塞也可以理解为同步主进程等子进程结束以后主进程才能继续执行。
```python
from multiprocessing import Process
def func(name):
print("hello %s" % name)
print("子进程")
if __name__ == '__main__':
p = Process(target=func,args=('nls',))
p.start()
p.join() # 阻塞等待完成
print("主程序")
```
### 查看进程号
我们可以通过 os 模块中提供的 getpid 的方法来获取当前进程的进程号
```python
import os
from multiprocessing import Process
def func():
print('子进程id',os.getpid(),'父进程id',os.getppid())
print("子进程结束")
if __name__ == '__main__':
p = Process(target=func,args=())
p.start()
print("主进程id",os.getpid())
print("主程序结束,等待子进程结束中...")
```
由此我们可以看出在子进程中查看他的父进程的id号等同于我们在主进程中查看到的id号可以说明子进程确实是由我们的主进程创建的。
### 多进程实例
多个进程同时运行(注意,子进程的执行顺序不是根据启动顺序决定的)
```python
from multiprocessing import Process # 从 multiprocessing 包中导入 Process 模块
import time
def func(name): # 创建一个函数,当作一个任务
print("hello %s" % name)
time.sleep(1)
print("子进程结束")
if __name__ == '__main__':
for i in range(5):
p = Process(target=func, args=('nls',))
p.start()
print("主程序结束,等待子进程....")
```
使用join方法
```python
from multiprocessing import Process # 从multiprocessing包中导入Process模块
import time
def func(name): # 创建一个函数,当作一个任务
print("hello %s" % name)
time.sleep(1)
print("子进程结束")
if __name__ == '__main__':
for i in range(5):
p = Process(target=func, args=('nls',))
p.start()
p.join()
print("主程序结束,等待子进程....")
```
发现如果使用了join方法后子进程变成了顺序执行每个子进程结束以后下一个子进程才能开始。同一时刻只能由一个子进程执行变成了一种阻塞的方式。
**代码示例**
```python
import multiprocessing
import time
# 定义子进程执行的函数
def worker(num):
print(f"进程 {num} 开始工作")
time.sleep(2)
print(f"进程 {num} 工作结束")
if __name__ == '__main__':
processes = []
# 创建并启动 3 个进程
for i in range(3):
p = multiprocessing.Process(target=worker, args=(i,))
processes.append(p)
p.start()
# 等待所有进程完成
for p in processes:
p.join()
print("所有进程完成,主进程退出...")
```
### 守护进程
随着主进程的结束而结束,主进程创建守护进程,进程之间是互相独立的,主进程代码运行结束,守护进程随即终止。
1. 守护进程会在主进程代码执行结束后就终止
2. 守护进程内无法再开启子进程,否则抛出异常
**示例代码**
```python
from multiprocessing import Process
import time
def foo():
print(123)
time.sleep(1)
print("end123") # 父进程代码执行结束,所以这里不会输出
def bar():
print(456)
time.sleep(3)
print("end456")
if __name__ == '__main__':
p1=Process(target=foo)
p2=Process(target=bar)
p1.daemon=True # 设置为守护进程
p1.start()
p2.start()
time.sleep(0.1)
print("main-------")
```
### socket 聊天并发实例
**示例代码**
```python
# 服务端
import socket
import multiprocessing
def handle_client(conn, addr):
"""
子进程处理客户端连接的函数
[优化点]:添加异常处理防止僵尸进程
"""
print(f"客户端 {addr} 已连接")
try:
while True:
data = conn.recv(1024)
if not data: # 客户端主动断开连接
break
print(f"接收自 {addr} 的数据: {data.decode()}")
conn.sendall(f"服务端响应: {data.decode().upper()}".encode())
except ConnectionResetError:
print(f"客户端 {addr} 异常断开")
finally:
conn.close()
if __name__ == "__main__":
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 端口复用
server.bind(("127.0.0.1", 8888))
server.listen(5)
print("服务器已启动,等待连接...")
try:
while True:
conn, addr = server.accept()
# 创建子进程处理连接
process = multiprocessing.Process(
target=handle_client,
args=(conn, addr),
daemon=True, # 设置守护进程防止僵尸进程
)
process.start()
conn.close() # 主进程关闭连接副本
except KeyboardInterrupt:
print("\n服务器正在关闭...")
finally:
server.close()
# 客户端 - 超简版
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8888))
while True:
msg = input(">>: ").strip()
if not msg:
continue
client.send(msg.encode("utf-8"))
msg = client.recv(1024)
print(msg.decode("utf-8"))
```
## 进程间数据共享
在 Python 中,由于进程间内存空间相互独立,直接共享数据需借助特定机制。
**示例代码**
```python
from multiprocessing import Process
import os
# 全局变量
count = 0
def increment():
global count
count += 1
print(f"子进程 {os.getpid()} 修改后的 count: {count}")
if __name__ == '__main__':
# 创建两个子进程
p1 = Process(target=increment)
p2 = Process(target=increment)
p1.start()
p2.start()
p1.join()
p2.join()
print(f"主进程中的 count: {count}")
```
### 原生共享内存方案Value/Array
适合共享​​简单数据类型​​(如整数、浮点数)或数组。通过底层共享内存实现,无需复制数据,性能较高。
**代码示例**
```python
from multiprocessing import Process, Value, Array
def increment(num):
num.value += 1
if __name__ == '__main__':
counter = Value('i', 0) # 'i' 表示整数类型
arr = Array('d', [0.0, 1.0, 2.0]) # 'd' 表示双精度浮点数
processes = [Process(target=increment, args=(counter,)) for _ in range(5)]
for p in processes:
p.start()
for p in processes:
p.join()
print(counter.value) # 数据共享,但数据不一致
```
### 进程同步机制
共享数据时需通过锁Lock、信号量Semaphore等保证数据一致性。
**示例代码**
```python
from multiprocessing import Process, Value, Lock
# 同步可避免多进程同时修改数据导致的错误
def safe_increment(num, lock):
with lock: # 自动获取和释放锁: lock.acquire() 和 lock.release()
num.value += 1
if __name__ == "__main__":
counter = Value("i", 0)
lock = Lock() # 创建对象
processes = [Process(target=safe_increment, args=(counter, lock)) for _ in range(5)]
for p in processes:
p.start()
for p in processes:
p.join()
print(counter.value)
```
### Manager 代理对象方案
适合共享​​复杂数据结构​​(如字典、列表)。通过代理模式,由 Manager 服务进程管理数据,子进程通过代理访问。
`Manager` 提供了一种方式来创建可以在多个进程之间共享的对象。`Manager` 允许不同的进程通过代理对象来共享数据结构,包括列表、字典、命名空间等,而无需显式的进程间通信机制(如队列或管道)。`Manager` 实现了进程间的同步机制,确保多个进程可以安全地读写共享数据。
`multiprocessing.Manager` 提供了一个管理器对象,这个管理器可以生成各种共享对象,如列表、字典、队列、锁等。所有这些对象都可以被不同的进程安全地访问和修改。
**共享对象类型**
- `list`:共享的列表。
- `dict`:共享的字典。
- `Namespace`:共享的命名空间,允许存储任意属性。
- `Queue`:共享的队列,用于进程间通信。
- `Lock`:锁,用于进程同步,防止数据竞争。
**基本使用流程**
1.`multiprocessing.Manager()` 创建管理器对象。
2. 使用管理器对象来创建共享数据结构(如 `list``dict` 等)。
3. 在多个进程中共享这些数据结构。
4. 进程完成后,关闭管理器对象。
**示例代码**
```python
from multiprocessing import Manager, Process
def update_dict(shared_dict, key):
shared_dict[key] = key * 2
if __name__ == '__main__':
with Manager() as manager:
shared_dict = manager.dict()
processes = [Process(target=update_dict, args=(shared_dict, i)) for i in range(3)]
for p in processes:
p.start()
for p in processes:
p.join()
print(shared_dict)
```
### 共享内存高级方案
## 进程间通信
IPC(Inter-Process Communication)
在计算机系统中,进程是操作系统分配资源的基本单位,每个进程拥有独立的内存空间和资源。由于进程间的内存隔离,​​进程间通信成为实现多进程协作的关键技术。
队列和管道都是将数据存放于内存中,队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可扩展性。
### 管道
点对点通信,返回两个连接对象。
**使用方式**
```python
from multiprocessing import Pipe
conn1, conn2 = Pipe()
conn1.send("Hello")
print(conn2.recv())
```
### 队列
安全传递数据,支持多生产者和消费者。
**使用方式**
```python
from multiprocessing import Queue
q = Queue([maxsize]) # 创建共享的进程队列maxsize 是队列中允许的最大项数,默认为大小限制。
```
**常见方法**
| 方法 | 含义 |
| :------- | :--------|
| `q.get(block=True, timeout=None)`| 返回q中的一个项目。如果q为空此方法将阻塞直到队列中有项目可用为止。block用于控制阻塞行为默认为True. 如果设置为False将引发Queue.Empty异常定义在Queue模块中。timeout是可选超时时间用在阻塞模式中。如果在制定的时间间隔内没有项目变为可用将引发Queue.Empty异常。|
| `q.get_nowait()`| 同q.get(False)方法。|
| `q.put(obj, block=True, timeout=None)`| 将obj放入队列。如果队列已满此方法将阻塞至有空间可用为止。block控制阻塞行为默认为True。如果设置为False将引发Queue.Empty异常定义在Queue库模块中。timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引发Queue.Full异常。|
| `q.qsize()` | 返回队列中目前项目的正确数量。此函数的结果并不可靠因为在返回结果和在稍后程序中使用结果之间队列中可能添加或删除了项目。在某些系统上此方法可能引发NotImplementedError异常。|
| `q.empty()` | 如果调用此方法时 q为空返回True。如果其他进程或线程正在往队列中添加项目结果是不可靠的。也就是说在返回和使用结果之间队列中可能已经加入新的项目。 |
| `q.full()` | 如果q已满返回为True. 由于线程的存在结果也可能是不可靠的参考q.empty方法。|
| `q.close()` | 关闭队列防止队列中加入更多数据。调用此方法时后台线程将继续写入那些已入队列但尚未写入的数据但将在此方法完成时马上关闭。如果q被垃圾收集将自动调用此方法。关闭队列不会在队列使用者中生成任何类型的数据结束信号或异常。例如如果某个使用者正被阻塞在get操作上关闭生产者中的队列不会导致get方法返回错误。|
| `q.cancel_join_thread()` | 不会再进程退出时自动连接后台线程。这可以防止join_thread()方法阻塞。|
| `q.join_thread()` | 连接队列的后台线程。此方法用于在调用q.close()方法后等待所有队列项被消耗。默认情况下此方法由不是q的原始创建者的所有进程调用。调用q.cancel_join_thread()方法可以禁止这种行为。|
**代码示例**
```python
from multiprocessing import Queue
q=Queue(3)
q.put('1')
q.put('2')
q.put('3')
# q.put(3) # 队列已满,阻塞方式:等待队列中 get('旧数据')
try:
q.put_nowait('4') # 非阻塞方式:但会抛出异常
except:
print('队列已经满了')
print(q.full())
print(q.get())
print(q.get())
print(q.get())
# print(q.get()) # 队列已空,阻塞方式:等待队列中 put('新数据')
try:
q.get_nowait() # 非阻塞方式:但会抛出异常
except:
print('队列已经空了')
print(q.empty())
```
我们可以使用队列,是的进程和进程之间的数据能够交换,比如某个进程用于产生数据,某个进程用于拿去数据。这样,进程和进程之间就可以通信了。
### 案例分析1
定义了两个进程,一个用于产生数据,一个用于消费数据,使用队列进行数据交换。
```python
from multiprocessing import Process, Queue
import time
def func_put(q):
for i in range(3):
q.put(f"数据{i+1}")
def func_get(q):
time.sleep(1)
while True:
try:
print(f"GET到数据{q.get_nowait()}")
except Exception:
print("数据已经全部拿走")
break
if __name__ == "__main__":
q = Queue()
p_put = Process(target=func_put, args=(q,))
p_get = Process(target=func_get, args=(q,))
p_put.start()
p_put.join()
p_get.start()
p_get.join()
```
### 案例分析2
多个进程计算并通过队列返回结果
```python
import multiprocessing
import time
def calculate_square(num, queue):
result = num * num
print(
f"进程 {multiprocessing.current_process().name} 计算 {num} 的平方,结果是: {result}"
)
# multiprocessing.current_process().name 获取当前进程的名称,便于调试和输出。
time.sleep(1)
queue.put(result)
if __name__ == "__main__":
numbers = [1, 2, 3, 4, 5]
queue = multiprocessing.Queue()
processes = []
for num in numbers:
p = multiprocessing.Process(target=calculate_square, args=(num, queue))
processes.append(p)
p.start()
for p in processes:
p.join()
results = []
while not queue.empty():
results.append(queue.get())
print(f"所有进程计算结果: {results}")
print("主进程结束...")
```
### 生产者消费者模型
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
**为什么要使用生产者和消费者模式**
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
**什么是生产者消费者模式**
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
**示例代码**
```python
from multiprocessing import Process, Queue, current_process
import time
import random
import os
def consumer(q):
while True:
res = q.get()
if res is None: break # 接收结束信号
time.sleep(random.randint(1, 3))
print(f"进程 {current_process().name}{res}")
def producer(q):
for i in range(10):
time.sleep(random.randint(1, 3)) # 恢复生产者延时
res = f"包子{i}"
q.put(res)
print(f"进程 {current_process().name} 生产了 {res}")
if __name__ == "__main__":
q = Queue()
# 生产者进程
producers = [Process(target=producer, args=(q,)) for _ in range(1)]
# 消费者进程
consumers = [Process(target=consumer, args=(q,)) for _ in range(10)]
# 启动所有进程
for p in producers + consumers:
p.start()
# 等待生产者完成
for p in producers:
p.join()
# 发送毒丸信号(每个消费者一个)
for _ in range(len(consumers)):
q.put(None)
# 等待消费者完成
for c in consumers:
c.join()
```
**JoinableQueue([maxsize])**
创建可连接的共享进程队列。这就像是一个 Queue 对象,但队列允许项目的使用者通知生产者项目已经被成功处理。通知进程是使用共享的信号和条件变量来实现的。
| 方法 | 含义 |
| :----- | :----- |
| `q.task_done()` | 消费者使用此方法发出信号,表示 q.get() 返回的结果已经被处理。如果调用此方法的次数大于从队列中删除的结果数量,将引发 ValueError 异常。|
| `q.join() ` | 生产者将使用此方法进行阻塞,直到队列中所有项目均被处理。阻塞将持续到为队列中的每个项目均调用 q.task_done() 方法为止。|
**示例代码**
```python
from multiprocessing import Process, JoinableQueue, current_process
import random
import time
def consumer(q):
while True:
res = q.get() # 阻塞
time.sleep(random.randint(1, 3))
print(f"进程 {current_process().name}{res}")
q.task_done() # 每调用一次,队列内部计数器减
def producer(q):
for i in range(10):
time.sleep(random.randint(1, 3))
res = f"包子{i}"
q.put(res)
print(f"进程 {current_process().name} 生产了 {res}")
q.join() # 阻塞直到计数器归零,确保所有任务被处理
if __name__ == "__main__":
q = JoinableQueue()
# 生产者进程
producers = [Process(target=producer, args=(q,)) for _ in range(1)]
# 消费者进程:主进程结束后,守护进程自动终止,避免无限阻塞
consumers = [Process(target=consumer, args=(q,), daemon=True) for _ in range(10)]
# 启动所有进程
for p in producers + consumers:
p.start()
# 等待生产者完成
for p in producers:
p.join()
# 等待队列任务处理完毕
print("所有任务已完成,程序正常退出")
```
## 进程池
进程池multiprocessing.Pool是预先创建并管理一组子进程的技术用于高效处理批量任务。通过复用固定数量的进程避免频繁创建/销毁进程的开销,提升 CPU 密集型任务的性能。
- 那么在成千上万个任务需要被执行的时候,我们就需要去创建成千上万个进程么?首先,创建进程需要消耗时间,销毁进程也需要消耗时间。第二即便开启了成千上万的进程,操作系统也不能让他们同时执行,这样反而会影响程序的效率。因此我们不能无限制的根据任务开启或者结束进程。
- 定义一个池子,在里面放上固定数量的进程,有需求来了,就拿一个池中的进程来处理任务,等到处理完毕,进程并不关闭,而是将进程再放回进程池中继续等待任务。如果有很多任务需要执行,池中的进程数量不够,任务就要等待之前的进程执行任务完毕归来,拿到空闲进程才能继续执行。也就是说,池中进程的数量是固定的,那么同一时间最多有固定数量的进程在运行。这样不会增加操作系统的调度难度,还节省了开闭进程的时间,也一定程度上能够实现并发效果。
- `multiprocessing.Pool``multiprocessing` 模块中的一个非常有用的工具用于管理进程池Pool of Processes。它允许你并行地执行函数并且可以轻松地分配多个任务到多个进程中执行从而提高程序的执行效率。`Pool` 使得多进程编程的管理变得更加容易,尤其是在需要并行处理大量数据时。
### 基本概念
`Pool` 是进程的集合,用于执行并行任务。它提供了一种简化的接口来并行执行多个任务,将任务分配给多个进程并管理它们的执行。
**进程池的好处**
- 通过限制并发进程的数量,可以有效地管理资源消耗。
- 可以自动调度和分配任务到多个进程。
- 提供了多种方法(如 `apply``map``apply_async``map_async`)来调度任务并收集结果。
**创建进程池**
```python
from multiprocessing import Pool
# 创建包含4个子进程的进程池默认值为CPU核心数
pool = Pool(processes=4)
```
**关闭进程池**
```python
pool.close() # 停止接收新任务
pool.join() # 阻塞主进程,等待所有子进程结束
```
### 提交任务
**代码示例**:同步阻塞方式
```python
from multiprocessing import Pool
import time
def task(x):
time.sleep(1) # 模拟耗时操作
return x * x
if __name__ == "__main__":
start = time.time()
with Pool(4) as pool:
results = []
for i in range(4):
res = pool.apply(task, (i,)) # 同步提交,逐个执行
results.append(res)
print(f"结果:{results},耗时:{time.time()-start:.2f}秒")
```
**代码示例**:异步非阻塞方式
```python
from multiprocessing import Pool
import time
def task(x):
time.sleep(1)
return x * x
if __name__ == '__main__':
start = time.time()
with Pool(4) as pool: # 自动调用 pool.close() 和 pool.join()
async_results = [pool.apply_async(task, (i,)) for i in range(4)] # 异步提交任务
results = [res.get() for res in async_results] # 阻塞直到所有结果返回
print(f"结果:{results},耗时:{time.time()-start:.2f}秒")
```
**示例代码**:批量处理
```python
from multiprocessing import Pool
import time
def task(x):
time.sleep(1)
return x * x
if __name__ == "__main__":
start = time.time()
with Pool(4) as pool:
results = pool.map(task, range(4)) # 批量提交
print(f"结果:{results},耗时:{time.time()-start:.2f}秒")
```
### 回调函数
需要回调函数的场景:进程池中任何一个任务一旦处理完了,就立即告知主进程:我好了,你可以处理我的结果了。主进程则调用一个函数去处理该结果,该函数即回调函数。
我们可以把耗时间阻塞的任务放到进程池中然后指定回调函数主进程负责执行这样主进程在执行回调函数时就省去了I/O的过程直接拿到的是任务的结果。
在 Python 的多进程编程中apply_async 的回调函数callback是一种异步处理任务结果的机制它允许在子进程完成任务后自动触发特定逻辑而无需阻塞主进程。
**回调函数的执行机制**
- ​运行环境​​:回调函数在​**​主进程​**​中执行,而非子进程。这意味着:回调函数内无法直接操作子进程的变量或资源。回调中应避免耗时操作,否则会阻塞主进程。
- ​参数传递​​:回调函数默认接收​​**任务的返回值​**​作为唯一参数。若需传递额外参数,可通过闭包或全局变量实现。
**示例代码**
```python
from multiprocessing import Pool
def square(x):
return x * x
def collect_result(result, result_list):
result_list.append(result)
if __name__ == '__main__':
with Pool(4) as pool:
results = []
# 提交10个任务并绑定回调
for i in range(10):
pool.apply_async(square, (i,), callback=lambda r: collect_result(r, results))
pool.close()
pool.join()
print("最终结果:", sorted(results))
```
### 案例分析
实时爬取网页内容并存储至本地文件。
```python
from multiprocessing import Pool
import requests
import os
import time
def get_page(url):
print("<进程%s> get %s" % (os.getpid(), url))
respone = requests.get(url)
if respone.status_code == 200:
return {"url": url, "text": respone.text}
def pasrse_page(res):
print("<进程%s> parse %s" % (os.getpid(), res["url"]))
parse_res = "url:<%s> size:[%s]\n" % (res["url"], len(res["text"]))
with open("db.txt", "a") as f:
f.write(parse_res)
if __name__ == "__main__":
start = time.time()
urls = [
"https://www.baidu.com",
"https://www.python.org",
"https://www.openstack.org",
"http://www.sina.com.cn/",
]
with Pool(4) as pool:
# 提交任务并绑定回调
async_results = [pool.apply_async(get_page, (i,), callback=pasrse_page) for i in urls]
results = [res.get() for res in async_results] # 获取结果
print(f"耗时:{time.time()-start:.2f}秒")
```
# 课后作业
- [必须] 动手完成本章节案例
- [扩展] 阅读官方文档相关章节

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

View File

@@ -0,0 +1,612 @@
# 核心概念
- Socket套接字网络通信的端点支持不同主机间的进程通信。
- 协议类型:
- TCP
- UDP
- 地址族:...
# TCP 协议工作流程
![img-socket编程1](套接字的工作流程/socket编程1.png)
## 服务端
**实现步骤:**
1. 创建 Socket 对象:`socket.socket(socket.AF_INET, socket.SOCK_STREAM)`
2. 绑定地址与端口​​:`bind(('0.0.0.0', 12345))`0.0.0.0 表示监听所有 IP
3. ​​监听连接​​:`listen(5)` (参数为最大等待连接)
4. 接收连接​​:`accept()` 返回客户端 Socket 和地址
5. 数据交换​​:`send()``recv()` 方法收发数据
6. 关闭连接​​:`close()` 释放资源
## 客户端
**实现步骤:**
1. ​创建 Socket 对象:`socket.socket(socket.AF_INET, socket.SOCK_STREAM)`
2. ​连接服务器​​:`connect(('127.0.0.1', 12345))`
3. 数据交换​​:同服务器端 `send()``recv()`
## 示例代码
```python
# server.py 代码
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 8080))
server.listen(5)
conn, client_addr = server.accept()
print(conn, client_addr, sep="\n")
from_client_data = conn.recv(1024)
print(from_client_data.decode("utf-8"))
conn.send(from_client_data.upper())
conn.close()
server.close()
# client.py 代码
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8080))
client.send("hello".encode("utf-8"))
from_server_data = client.recv(1024)
print(from_server_data)
client.close()
```
# UDP 协议工作流程
<img src="套接字的工作流程/socket编程2.png" alt="img-socket编程2" style="zoom:80%;" />
## 服务端
**实现步骤:**
1. 创建 Socket 对象:`socket.socket(socket.AF_INET, socket.SOCK_DGRAM)`
2. 绑定地址与端口​​:`bind(('0.0.0.0', 12345))`
3. 数据交换​​:`sendto()``recvfrom()` 方法收发数据
## 客户端
**实现步骤:**
1. 创建 Socket 对象:`socket.socket(socket.AF_INET, socket.SOCK_DGRAM)`
2. 数据交换​​:同服务器端 `sendto()``recvfrom()`
## 示例代码
```python
# server.py
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server.bind(("127.0.0.1", 8000))
from_client_data, addr = server.recvfrom(1024)
print(from_client_data, addr, sep="\n")
server.sendto(from_client_data.upper(), addr)
server.close()
# client.py
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client.sendto("hello".encode("utf-8"), ("127.0.0.1", 8000))
from_server_data, addr = client.recvfrom(1024)
print(from_server_data, addr, sep="\n")
client.close()
```
# 相关方法
服务端套接字函数
| 函数 | 含义 |
| :---------- | :-------------------------------------------- |
| `s.bind()` | 绑定(主机,端口号)到套接字 |
| `s.listen()` | 开始 TCP 监听 |
| `s.accept()` | 被动接受 TCP 客户的连接,(阻塞式)等待连接的到来 |
客户端套接字函数
| 函数 | 含义 |
| :-------------- | :------------------------------------------------------- |
| `s.connect()` | 主动初始化 TCP 服务器连接 |
| `s.connect_ex()` | `connect()`函数的扩展版本,出错时返回出错码,而不是抛出异常 |
公共用途的套接字函数
| 函数 | 含义 |
| :--------------- | :------------------------------------------------------------ |
| `s.recv()` | 接收TCP数据 |
| `s.send()` | 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完) |
| `s.sendall()` | 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完) |
| `s.recvfrom()` | 接收UDP数据 |
| `s.sendto()` | 发送UDP数据 |
| `s.getpeername()` | 连接到当前套接字的远端的地址 |
| `s.getsockname()` | 当前套接字的地址 |
| `s.getsockopt()` | 返回指定套接字的参数 |
| `s.setsockopt()` | 设置指定套接字的参数 |
| `s.close()` | 关闭套接字 |
面向锁的套接字方法
| 函数 | 含义 |
| :--------------- | :---------------------------- |
| `s.setblocking()` | 设置套接字的阻塞与非阻塞模式 |
| `s.settimeout()` | 设置阻塞套接字操作的超时时间 |
| `s.gettimeout()` | 得到阻塞套接字操作的超时时间 |
面向文件的套接字的函数
| 函数 | 含义 |
| :------------ | :---------------------------- |
| `s.fileno()` | 套接字的文件描述符 |
| `s.makefile()` | 创建一个与该套接字相关的文件 |
# 粘包问题
## 原因
**TCP 协议特性**
- 流式传输TCP 将数据视为连续字节流,不会自动分割数据包,导致接收端无法区分消息边界
- ​缓冲区机制​​:发送端和接收端均存在缓冲区,发送端可能将多个小数据包合并发送,接收端可能将多次接收的数据合并处理
- Nagle算法优化TCP 为提高传输效率,会合并多个小数据包(例如间隔短且数据量小)后发送
**​​代码逻辑缺陷**
- 连续发送​​:在未明确分隔消息的情况下连续调用 `send()`,导致数据在缓冲区中合并
- 接收不完整​​:接收端 `recv()` 方法未动态调整缓冲区大小,导致部分数据残留
## 示例代码
### Demo1
通过 **​​连续发送小数据包****接收缓冲区合并** 的场景模拟粘包现象:
```python
# 服务端代码(连续发送两次数据)​
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8888))
server.listen(5)
print("服务端已启动,等待客户端连接...")
conn, addr = server.accept()
print(f"客户端 {addr} 已连接")
try:
# 连续发送两次数据(未处理粘包)
conn.send(b'Hello') # 第一次发送
conn.send(b'World') # 第二次发送(可能合并到缓冲区)
except KeyboardInterrupt:
print("服务端主动终止")
finally:
conn.close()
server.close()
# 客户端代码(一次性接收数据)​
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8888))
try:
# 一次性接收数据(可能合并两次发送的内容)
data = client.recv(1024)
print(f"接收到的数据: {data.decode()}") # 预期输出可能是 'HelloWorld'
except ConnectionResetError:
print("服务端已关闭连接")
finally:
client.close()
```
**现象解释**
1. Nagle 算法​:发送端连续调用 `send()` 发送小数据包,可能将两次发送合并为一个包。
2. 缓冲区机制:接收端 `recv(1024)` 的缓冲区较大,一次性读取了所有待接收数据
### Demo2
通过 **​大缓冲区发送 + 小缓冲区接收** 的场景模拟接收不完整问题:
```python
# 服务端代码(发送 10KB 大数据包)​
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8888))
server.listen(5)
print("服务端已启动,等待客户端连接...")
conn, addr = server.accept()
try:
# 发送 10KB 数据(超过常规接收缓冲区)
big_data = b'A' * 1024 * 10 # 10KB
conn.sendall(big_data)
print(f"已发送 {len(big_data)} 字节数据")
except KeyboardInterrupt:
print("服务端主动终止")
finally:
conn.close()
server.close()
# 客户端代码(仅接收 4KB 数据)
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8888))
try:
# 设置接收缓冲区为 4KB且仅接收一次
partial_data = client.recv(4096) # 只接收一次
print(f"实际接收长度: {len(partial_data)} 字节") # 输出 4096残留 6KB 未接收
except ConnectionResetError:
print("服务端已关闭连接")
finally:
client.close()
```
**现象解释**
1. TCP 流式特性TCP 将数据视为连续字节流,无边界标识
2. ​​缓冲区限制​​:`recv()` 方法一次性读取的字节数受参数限制,未循环接收导致残留
## 解决方案
固定包头法:发送端在数据前附加一个固定长度的包头,标明数据长度,接收端根据包头解析完整数据。
### struct 模块
把一个类型,如数字,转成固定长度的 bytes
![img-struct模块](套接字的工作流程/struct模块.png)
**常见用法**
```python
import struct
# 将一个数字转化成等长度的bytes类型。
ret = struct.pack('i', 18334)
print(ret, len(ret))
# 通过unpack反解回来 返回一个元组回来
ret1 = struct.unpack('i',ret)[0]
print(ret1)
```
### Demo1
```python
# 服务端代码(连续发送两次数据)​
import socket
import struct
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8888))
server.listen(5)
print("服务端已启动,等待客户端连接...")
conn, addr = server.accept()
print(f"客户端 {addr} 已连接")
try:
data = b'Hello'
header = struct.pack("!I", len(data))
conn.send(header + data) # 第一次发送
data = b'World'
header = struct.pack("!I", len(data))
conn.send(header + data) # 第二次发送
except KeyboardInterrupt:
print("服务端主动终止")
finally:
conn.close()
server.close()
# 客户端代码
import socket
import struct
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8888))
try:
# 接收固定长度包头
header = client.recv(4)
data_size = struct.unpack("!I", header)[0]
received_data = client.recv(data_size)
print(f"实际接收长度: {len(received_data)} 字节")
print(received_data)
except ConnectionResetError:
print("服务端已关闭连接")
finally:
client.close()
```
### Demo2
```python
# 服务端代码(发送 10KB 大数据包)​
import socket
import struct
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('0.0.0.0', 8888))
server.listen(5)
print("服务端已启动,等待客户端连接...")
conn, addr = server.accept()
try:
# 发送 10KB 数据
big_data = b'A' * 1024 * 10 # 10KB
# 发送固定长度包头
header = struct.pack("!I", len(big_data))
conn.sendall(header + big_data)
print(f"已发送 {len(big_data)} 字节数据")
except KeyboardInterrupt:
print("服务端主动终止")
finally:
conn.close()
server.close()
# 客户端代码
import socket
import struct
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8888))
try:
# 接收固定长度包头
header = client.recv(4)
data_size = struct.unpack("!I", header)[0]
# 接收完整数据
received_data = b""
while len(received_data) < data_size:
remaining = data_size - len(received_data)
chunk = client.recv(min(remaining, 1024))
if not chunk:
break
received_data += chunk
print(f"实际接收长度: {len(received_data)} 字节")
except ConnectionResetError:
print("服务端已关闭连接")
finally:
client.close()
```
# 实践案例
## 循环通信
服务端和客户端之间可以循环进行通信。
```python
# server.py
import socket
import struct
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 8080))
server.listen(5)
print("服务端已启动,等待连接...")
conn = None
try:
while True:
conn, client_addr = server.accept()
print(f"客户端 {client_addr} 已连接")
while True:
header = conn.recv(4)
data_size = struct.unpack("!I", header)[0]
received_data = b""
while len(received_data) < data_size:
remaining = data_size - len(received_data)
chunk = conn.recv(min(remaining, 1024))
if not chunk:
break
received_data += chunk
if (
not received_data
or received_data.decode("utf-8").strip().upper() == "EXIT"
):
break
print(f"收到消息: {received_data.decode('utf-8')}")
response = received_data.decode("utf-8").upper()
header = struct.pack("!I", len(received_data))
conn.send(header + response.encode("utf-8"))
except ConnectionResetError:
print("客户端异常断开")
except KeyboardInterrupt:
print("服务端主动终止")
except Exception as e:
print(f"未知异常: {e}")
finally:
if conn:
conn.close()
server.close()
# client.py
import socket
import struct
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8080))
try:
while True:
msg = input("请输入消息 (输入 EXIT 退出): ")
if not msg.strip():
print("消息不能为空,请重新输入")
continue
if msg.strip().upper() == "EXIT":
break
header = struct.pack("!I", len(msg.encode("utf-8")))
client.send(header + msg.encode("utf-8"))
header = client.recv(4)
data_size = struct.unpack("!I", header)[0]
received_data = b""
while len(received_data) < data_size:
remaining = data_size - len(received_data)
chunk = client.recv(min(remaining, 1024))
if not chunk: break
received_data += chunk
print(f"服务端返回: {received_data.decode('utf-8')}")
except KeyboardInterrupt:
print("客户端主动终止")
except BrokenPipeError:
print("服务端已关闭连接,终止发送")
except Exception as e:
print(f"异常 {e}")
finally:
client.close()
```
## 远程命令执行
客户端发送执行指令给服务端,服务端接收到指令并执行后将结果返回给客户端。
```python
import socket
import subprocess
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 8080))
server.listen(5)
print("服务端已启动,等待连接...")
conn = None
try:
while True:
conn, client_addr = server.accept()
print(f"客户端 {client_addr} 已连接")
while True:
cmd = conn.recv(1024)
if not cmd or cmd.decode('utf-8').strip().upper() == 'EXIT':
break
print(f"收到的命令: {cmd.decode('utf-8')}")
ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
correct_msg = ret.stdout.read()
error_msg = ret.stderr.read()
conn.send(correct_msg + error_msg)
except ConnectionResetError:
print("客户端异常断开")
except KeyboardInterrupt:
print("服务端主动终止")
except Exception as e:
print(f"未知异常: {e}")
finally:
if conn:
conn.close()
server.close()
```
## 时间服务端
```python
# server.py
import socket
import time
import struct
# 创建UDP套接字设置端口复用
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 避免端口占用
server_socket.bind(('', 8090))
print("时间服务器已启动,等待客户端请求...")
try:
while True:
# 接收客户端请求(空消息即可触发)
data, client_addr = server_socket.recvfrom(1024)
print(f"收到来自 {client_addr} 的请求")
# 获取当前时间并封装为二进制数据
current_time = int(time.time()) # 获取时间戳(秒级)
packed_time = struct.pack("!I", current_time) # 转为网络字节序的4字节数据提升传输效率
# 发送时间数据
server_socket.sendto(packed_time, client_addr)
except KeyboardInterrupt:
print("\n服务端主动终止")
finally:
server_socket.close()
# client.py
import socket
import struct
import time
# 创建UDP套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_address = ('127.0.0.1', 8090)
try:
# 发送空消息触发时间请求
client_socket.sendto(b'', server_address)
print("已发送时间请求,等待响应...")
# 接收时间数据最多等待2秒
client_socket.settimeout(2)
data, _ = client_socket.recvfrom(1024)
# 解析时间数据
if len(data) == 4:
timestamp = struct.unpack("!I", data)[0] # 解析网络字节序数据
print(f"服务器时间: {time.ctime(timestamp)}")
else:
print("错误:收到无效时间数据")
except socket.timeout:
print("请求超时,服务端未响应")
except ConnectionRefusedError:
print("连接被拒绝,请检查服务端是否运行")
except KeyboardInterrupt:
print("\n客户端主动终止")
finally:
client_socket.close()
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -0,0 +1,48 @@
# 操作系统基础
操作系统Operating System简称 OS是管理和控制计算机硬件与软件资源的计算机程序是直接运行在“裸机”上的最基本的系统软件任何其他软件都必须在操作系统的支持下才能运行。
<img src="操作系统基础/操作系统.png" alt="img-操作系统" />
操作系统应该分成两部分功能
1. 隐藏了丑陋的硬件调用接口(键盘、鼠标、音箱等等怎么实现的,就不需要你管了),为应用程序员提供调用硬件资源的更好,更简单,更清晰的模型(系统调用接口)。应用程序员有了这些接口后,就不用再考虑操作硬件的细节,专心开发自己的应用程序即可。
例如:操作系统提供了文件这个抽象概念,对文件的操作就是对磁盘的操作,有了文件我们无需再去考虑关于磁盘的读写控制(比如控制磁盘转动,移动磁头读写数据等细节)
2. 将应用程序对硬件资源的竞态请求变得有序化
例如很多应用软件其实是共享一套计算机硬件比方说有可能有三个应用程序同时需要申请打印机来输出内容那么a程序竞争到了打印机资源就打印然后可能是b竞争到打印机资源也可能是c这就导致了无序打印机可能打印一段a的内容然后又去打印c...,操作系统的一个功能就是将这种无序变得有序。
# socket
回顾一下五层通讯流程
<img src="操作系统基础/五层通讯.png" alt="img-五层通讯" />
但实际上从传输层开始以及以下,都是操作系统帮咱们完成的,下面的各种包头封装的过程
![img-socket抽象层](操作系统基础/socket抽象层.png)
Socket又称为套接字它是应用层与TCP/IP协议族通信的中间软件抽象层它是一组接口。在设计模式中Socket其实就是一个门面模式它把复杂的TCP/IP协议族隐藏在Socket接口后面对用户来说一组简单的接口就是全部让Socket去组织数据以符合指定的协议。当我们使用不同的协议进行通信时就得使用不同的接口还得处理不同协议的各种细节这就增加了开发的难度软件也不易于扩展(就像我们开发一套公司管理系统一样,报账、会议预定、请假等功能不需要单独写系统,而是一个系统上多个功能接口,不需要知道每个功能如何去实现的)。于是UNIX BSD就发明了socket这种东西socket屏蔽了各个协议的通信细节使得程序员无需关注协议本身直接使用socket提供的接口来进行互联的不同主机间的进程的通信。这就好比操作系统给我们提供了使用底层硬件功能的系统调用通过系统调用我们可以方便的使用磁盘文件操作使用内存而无需自己去进行磁盘读写内存管理。socket其实也是一样的东西就是提供了tcp/ip协议的抽象对外提供了一套接口同过这个接口就可以统一、方便的使用tcp/ip协议的功能了。
其实站在你的角度上看socket就是一个模块。我们通过调用模块中已经实现的方法建立两个进程之间的连接和通信。也有人将socket说成ip+port因为ip是用来标识互联网中的一台主机的位置而port是用来标识这台机器上的一个应用程序。 所以我们只要确立了ip和port就能找到一个应用程序并且使用socket模块来与之通信。
# 套接字家族
套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种或者称为有两个种族,分别是基于文件型的和基于网络型的。
## 基于文件类型的套接字家族
套接字家族的名字AF_UNIX
unix一切皆文件基于文件的套接字调用的就是底层的文件系统来取数据两个套接字进程运行在同一机器可以通过访问同一个文件系统间接完成通信
## 基于网络类型的套接字家族
套接字家族的名字AF_INET
(还有AF_INET6被用于ipv6还有一些其他的地址家族不过他们要么是只用于某个平台要么就是已经被废弃或者是很少被使用或者是根本没有实现所有地址家族中AF_INET是使用最广泛的一个python支持很多种地址家族但是由于我们只关心网络编程所以大部分时候我么只使用AF_INET)
## 课后作业
- [扩展] 阅读计算机网络相关书籍

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,530 @@
# 垃圾回收机制
如果将应用程序比作人的身体:所有你所写的那些优雅的代码,业务逻辑,算法,应该就是大脑。垃圾回收就是应用程序就是相当于人体的腰子,过滤血液中的杂质垃圾,没有腰子,人就会得尿毒症,垃圾回收器为你的应该程序提供内存和对象。如果垃圾回收器停止工作或运行迟缓,像尿毒症,你的应用程序效率也会下降,直至最终崩溃坏死。
在 C/C++ 中采用**用户自己管理维护内存**的方式。自己管理内存极其自由,可以任意申请内存,但也为大量内存泄露、悬空指针等 bug 埋下隐患。
因此,现在的很多高级语言(Python、Java、C#、Golang)等,都底层封装实现了垃圾回收机制,不需要我们用户自己维护和管理。
# Python 垃圾回收机制
网上能够搜到大量的资料,但是所有的资料都在论述一个观点,就是**引用计数器为主,分代回收和标记清除为辅**。
- Python 的 GC 模块主要运用了“引用计数”reference counting来跟踪和回收垃圾。
- 在引用计数的基础上,还可以通过“标记-清除”mark and sweep解决容器对象可能产生的循环引用的问题
- 并且通过“分代回收”generation collection以空间换取时间的方式来进一步提高垃圾回收的效率。
# 引用计数器
引用计数是 Python 管理内存的基础机制。每一个对象在 Python 中都有一个 `引用计数器`,用于跟踪有多少个引用指向该对象。当没有引用指向某个对象时,这个对象就不再需要,系统会自动回收其占用的内存。
## 工作原理
- 引用计数增加:
- 对象被创建
- 对象被引用
- 对象被作为参数,传到函数中
- 对象作为一个元素,存储在容器中
```python
a = 14 # 对象被创建
b = a # 对象被引用 
func(a) # 对象被作为参数,传到函数中
List = [a,"a","b",2] # 对象作为一个元素,存储在容器中
```
- 引用计数减少
- 对象被显式销毁
- 变量重新赋予新的对象
- 对象离开它的作用域
- 对象所在的容器被销毁,或从容器中删除对象。
```python
del a # 删除 a 的引用,引用计数减为 1
del b # 删除 b 的引用,引用计数减为 0内存被释放
```
**示例**
```python
import sys
import gc
class MyObject:
def __init__(self, name):
self.name = name
print(f"[+] 对象 {self.name} 被创建")
def __del__(self):
print(f"[-] 对象 {self.name} 被销毁")
def main():
print("\n=== 场景:基础引用计数 ===")
a = MyObject("A") # 初始引用计数为1
# 当调用 sys.getrefcount(a) 时,引用计数会加 1
print(sys.getrefcount(a))
# 重复调用函数不会增加引用计数
print(sys.getrefcount(a))
# 对象作为一个元素,存储在容器中
List = [1, a]
print(sys.getrefcount(a))
b = a # 引用计数加1
print(sys.getrefcount(a))
del b # 引用计数减1
print(sys.getrefcount(a)) # 应输出: 2
del a # 引用计数归零,对象被销毁
# print(sys.getrefcount(a)) UnboundLocalError: local variable 'a' referenced before assignment
```
## [扩展] 底层实现
在python程序中创建的任何对象都会放在 **refchain 双向链表**中
例如:
```python
name = "小猪佩奇" # 字符串对象
age = 18 # 整形对象
hobby = ["吸烟","喝酒","烫头"] # 列表对象
```
这些对象都会放到多个**双向链表**当中,也就是帮忙维护了 python 中所有的对象。也就是说如果你得到了 **refchain**,也就得到了 python 程序中的所有对象。
<img src="Python垃圾回收机制/双向链表.png" alt="img-双向链表" style="zoom:80%;" />
## [扩展] 底层结构体源码
Python 解释器由 c 语言开发完成py 中所有的操作最终都由底层的 c 语言来实现并完成,所以想要了解底层内存管理需要结合 python 源码来进行解释。
```c
#define PyObject_HEAD PyObject ob_base ;
#define PyObject_VAR_HEAD PyVarObject ob_base;
//宏定义,包含上一个、下一个,用于构造双向链表用。(放到refchain链表中时要用到)
#define _PyObject_HEAD_EXTRA \
struct _object *_ob_next; \
struct _object *_ob_prev;
typedef struct _object {
_PyObject_HEAD_EXTRA //用于构造双向链表
Py_ssize_t ob_refcnt; //引用计数器
struct _typeobject *ob_type; //数据类型
} PyObject;
typedef struct {
PyObject ob_base; // PyObject对象
Py_ssize_t ob_size; /* Number of items in variable part, 即:元素个数*/
} PyVarObject;
```
在 C 源码中如何体现每个对象中都有的相同的值PyObject 结构体4个值_ob_next、_ob_prev、ob_refcnt、*ob_type
9-13行 定义了一个结构体第10行实际上就是67两行用来存放前一个对象和后一个对象的位置。
这个结构体可以存贮四个值(**这四个值是对象都具有的**)。
在 C 源码中如何体现由多个元素组成的对象PyObject + ob_size(元素个数)
15-18行又定义了一个结构体第16行相当于代指了9-13行中的四个数据。
而17行又多了一个数据字段叫做元素个数。
**以上源码是Python内存管理中的基石其中包含了**
- **PyObject**此结构体中包含3个元素。
- PyObject_HEAD_EXTRA用于构造双向链表。
- ob_refcnt引用计数器。
- *ob_type数据类型。
- **PyVarObject**次结构体中包含4个元素ob_base 中包含3个元素
- ob_basePyObject 结构体对象,即:包含 PyObject 结构体中的三个元素。
- ob_size内部元素个数。
## [扩展] 类型封装结构体
在我们了解了这两个结构体,现在我们来看看每一个数据类型都封装了哪些值:
**float 类型**
```c
typedef struct {
PyObject_HEAD # 这里相当于代表基础的4个值
double ob_fval;
} PyFloatObject;
data = 3.14
内部会创建:
_ob_next = refchain 中的上一个对象
_ob_prev = refchain 中的后一个对象
ob_refcnt = 1 引用个数
ob_type= float 数据类型
ob_fval = 3.14
```
**int 类型**
道理都是相同的第2行代指第二个重要的结构体第三行是 int 类型特有的值,总结下来就是这个结构体中有几个值,那么创建这个类型对象的时候内部就会创建几个值。
```c
struct _longobject {
PyObject_VAR_HEAD
digit ob_digit[1];
};
// longobject.h
/* Long (arbitrary precision) integer object interface */
typedef struct _longobject PyLongObject; /* Revealed in longintrepr.h */
```
**list 类型**
```c
typedef struct {
PyObject_VAR_HEAD
/* Vector of pointers to list elements. list[0] is ob_item[0], etc. */
PyObject **ob_item;
/* ob_item contains space for 'allocated' elements. The number
* currently in use is ob_size.
* Invariants:
* 0 <= ob_size <= allocated
* len(list) == ob_size
* ob_item == NULL implies ob_size == allocated == 0
* list.sort() temporarily sets allocated to -1 to detect mutations.
*
* Items must normally not be NULL, except during construction when
* the list is not yet visible outside the function that builds it.
*/
Py_ssize_t allocated;
} PyListObject;
```
**tuple 类型**
```c
typedef struct {
PyObject_VAR_HEAD
PyObject *ob_item[1];
/* ob_item contains space for 'ob_size' elements.
* Items must normally not be NULL, except during construction when
* the tuple is not yet visible outside the function that builds it.
*/
} PyTupleObject;
```
**dict 类型**
```c
typedef struct {
PyObject_HEAD
Py_ssize_t ma_used;
PyDictKeysObject *ma_keys;
PyObject **ma_values;
} PyDictObject;
```
## 引用计数的优缺点
- 优点:
- 引用计数简单且高效,能够立即回收不再使用的对象。
- 对象的销毁过程是确定性的,即当引用计数为 0 时,内存立刻被回收。
- 缺点:
- 需要为对象分配引用计数空间,增大了内存消耗。
- 当需要释放的对象比较大时,需要对引用的所有对象循环嵌套调用,可能耗时比较长。
- 无法处理**循环引用**。当两个或多个对象互相引用时,它们的引用计数永远不会为 0导致内存泄漏。
## 循环引用问题
一种编程语言利用引用计数器实现垃圾管理和回收已经是比较完美的了只要计数器为0就回收不为0就不回收即简单明了又能实现垃圾管理。
但是如果真正这样想就太单纯了因为仅仅利用引用计数器实现垃圾管理和回收就会存在一个BUG就是循环引用问题。
循环引用是指多个对象之间互相引用,导致它们的引用计数无法归零,从而无法被回收。例如:
```python
v1 = [1,2,3] # refchain中创建一个列表对象由于v1=对象所以列表引对象用计数器为1.
v2 = [4,5,6] # refchain中再创建一个列表对象因v2=对象所以列表对象引用计数器为1.
v1.append(v2) # 把v2追加到v1中则v2对应的[4,5,6]对象的引用计数器加1最终为2.
v2.append(v1) # 把v1追加到v1中则v1对应的[1,2,3]对象的引用计数器加1最终为2.
del v1 # 引用计数器-1
del v2 # 引用计数器-1
# 最终v1,v2引用计数器都是1
```
<img src="Python垃圾回收机制/循环引用.png" alt="img-循环引用.png" style="zoom:80%;" />
两个引用计数器现在都是1那么它们都不是垃圾所以都不会被回收但如果是这样的话我们的代码就会出现问题。
我们删除了v1和v2那么就没有任何变量指向这两个列表那么这两个列表之后程序运行的时候都无法再使用但是这两个列表的引用计数器都不为0所以不会被当成垃圾进行回收所以这两个列表就会一直存在在我们的内存中永远不会销毁当这种代码越来越多时我们的程序一直运行内存就会一点一点被消耗然后内存变满满了之后就爆栈了。这时候如果重新启动程序或者电脑这时候程序又会正常运行其实这就是因为循环引用导致数据没有被及时的销毁导致了内存泄漏。
**所以大家要记得,因为引用计数器存在循环应用的问题,所以在 python 的垃圾管理机制中引入新的机制—标记清除和分代回收**
```python
import sys
import gc
class MyObject:
def __init__(self, name):
self.name = name
print(f"[+] 对象 {self.name} 被创建")
def __del__(self):
print(f"[-] 对象 {self.name} 被销毁")
def main():
print("\n=== 循环引用 ===")
obj1 = MyObject("循环引用-1")
obj2 = MyObject("循环引用-2")
# 创建循环引用
obj1.link = obj2
obj2.link = obj1
print(sys.getrefcount(obj1)) # 输出: 2obj1被obj1和obj2.link引用
print(sys.getrefcount(obj2)) # 输出: 2obj2被obj2和obj1.link引用
del obj1, obj2 # 删除外部引用,但循环引用仍存在
print("删除外部引用后,循环引用对象未被回收")
# 手动触发垃圾回收(解决循环引用)
print("手动执行垃圾回收...")
gc.collect() # 输出两个对象的 __del__ 方法
```
# 标记清除
## 引入目的
为了解决循环引用问题python 的底层不会单单只用引用计数器,引入了一个机制叫做标记清除。
## 实现原理
在 python 的底层中,再去维护一个链表,这个链表中专门放那些可能存在循环引用的对象。
那么哪些情况可能导致循环引用的情况发生?
容器:`list/dict/tuple/set` 甚至 `class`
<img src="Python垃圾回收机制/环状双向链表1.png" alt="img-环状双向链表.png" style="zoom:80%;" />
<img src="Python垃圾回收机制/第二个链表.png" alt="img-第二个链表.png" style="zoom:80%;" />
第二个链表只存储**可能存在循环引用问题的对象**
维护两个链表的作用是,在 python 内部某种情况下,会去扫描可能存在循环引用的链表中的每个元素,在循环一个列表的元素时,由于内部还有子元素 ,如果存在循环引用(v1 = [1,2,3,v2]和v2 = [4,5,6,v1])比如从v1的子元素中找到了v2又从v2的子元素中找到了v1那么就检查到循环引用如果有循环引用就让双方的引用计数器各自-1如果是0则垃圾回收。
## 标记清除算法
【标记清除Mark—Sweep】算法是一种基于追踪回收tracing GC技术实现的垃圾回收算法。它分为两个阶段第一阶段是标记阶段GC 会把所有的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收。那么 GC 又是如何判断哪些是活动对象哪些是非活动对象的呢?
对象之间通过引用(指针)连在一起,构成一个有向图,**对象构成这个有向图的节点,而引用关系构成这个有向图的边**。从根对象root object出发沿着有向边遍历对象可达的reachable对象标记为活动对象**不可达的对象就是要被清除的非活动对象**。根对象就是全局变量、调用栈、寄存器。
<img src="Python垃圾回收机制/标记清除.png" alt="img-标记清除" />
在上图中我们把小黑点视为全局变量也就是把它作为root object从小黑点出发对象1可直达那么它将被标记对象2、3可间接到达也会被标记而4和5不可达那么1、2、3就是活动对象4和5是非活动对象会被 GC 回收。
1. 寻找跟对象root object的集合作为垃圾检测动作的起点跟对象也就是一些全局引用和函数栈中的引用这些引用所指向的对象是不可被删除的。
2. 从root object集合出发沿着root object集合中的每一个引用如果能够到达某个对象则说明这个对象是可达的那么就不会被删除这个过程就是垃圾检测阶段。
3. 当检测阶段结束以后,所有的对象就分成可达和不可达两部分,所有的可达对象都进行保留,其它的不可达对象所占用的内存将会被回收,这就是垃圾回收阶段。(底层采用的是**链表**将这些集合的对象连接在一起)。
# 分代回收
## 引入目的
- 什么时候扫描去检测循环引用?
- **标记和清除的过程效率不高**。清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。
为了解决上述的问题python又引入了分代回收。
## 实现原理
将第二个链表可能存在循环引用的链表维护成3个环状双向的链表
- `G0`:对象刚创建时
- `G1`:经过一轮 `GC` 扫描存活下来的对象
- `G2`:经过多轮 `GC` 扫描存活下来的对象
<img src="Python垃圾回收机制/分代回收.png" alt="img-分代回收" style="zoom:80%;" />
<img src="Python垃圾回收机制/环状双向链表2.png" alt="img-环状双向链表2.png" style="zoom:80%;" />
## 触发 GC 时机
当某世代中分配的对象数量与被释放的对象之差达到某个阈值的时,将触发对该代的扫描。每次 `GC` 都会将存活对象移至下一代。
-`G0` 对象超过 700 触发 `GC`
-`G0` 触发 `GC` 超过 10 次,`G1` 触发 `GC`
-`G1` 触发 `GC` 超过 10 次,`G2` 触发 `GC`
```python
import gc
threshold = gc.get_threshold()
print("各世代的阈值:", threshold)
# 修改各代阈值
# gc.set_threshold(threshold0, threshold1, threshold2)
```
**扩展**
```c
// 分代的C源码
#define NUM_GENERATIONS 3
struct gc_generation generations[NUM_GENERATIONS] = {
/* PyGC_Head, threshold, count */
(uintptr_t)_GEN_HEAD(0), (uintptr_t)_GEN_HEAD(0)}, 700, 0}, // 0代
(uintptr_t)_GEN_HEAD(1), (uintptr_t)_GEN_HEAD(1)}, 10, 0}, // 1代
(uintptr_t)_GEN_HEAD(2), (uintptr_t)_GEN_HEAD(2)}, 10, 0}, // 2代
};
```
## 弱代假说
为什么要按一定要求进行分代扫描?
这种算法的根源来自于**弱代假说**
这个假说由两个观点构成:**首先是年轻的对象通常死得也快,而老对象则很有可能存活更长的时间。**
假定现在我用 Python 创建一个新对象 n1 = "ABC"
根据假说,我的代码很可能仅仅会使用 ABC 很短的时间。这个对象也许仅仅只是一个方法中的中间结果,并且随着方法的返回这个对象就将变成垃圾了。大部分的新对象都是如此般地很快变成垃圾。然而,偶尔程序会创建一些很重要的,存活时间比较长的对象,例如 web 应用中的 session 变量或是配置项。
频繁的处理零代链表中的新对象,可以将让 Python 的**垃圾收集器把时间花在更有意义的地方**:它处理那些很快就可能变成垃圾的新对象。同时只在很少的时候,当满足一定的条件,收集器才回去处理那些老变量。
# 总结
将上面三个点学习之后,基本上应付面试没有太大问题了。
在 python 中维护了 refchain 的双向环状链表,这个链表中存储创建的所有对象,而每种类型的对象中,都有一个 ob_refcnt 引用计数器的值,它维护者引用的个数+1-1最后当引用计数器变为0时则进行垃圾回收对象销毁、refchain 中移除)。
但是在python中对于那些可以有多个元素组成的对象可能会存在循环引用的问题并且为了解决这个问题python又引入了标记清除和分代回收在其内部维护了4个链表分别是
* refchain
* 2代10次
* 1代10次
* 0代700个
在源码内部,当达到各自的条件阈值时,就会触发扫描链表进行标记清除的动作(如果有循环引用,引用计数器就各自-1
到这里我们只需要把这些给面试官说完就可以了。
————————————————
**同时为了提高内存的分配效率Python 还引入了缓存机制**
# [扩展] Python缓存机制
## 内存池
在 Python 中为了避免重复创建和销毁一些常见的对象,引入了内存池的概念
```python
v1 = 7
v2 = 9
v3 = 9
# 按理说在python中会创建3个对象都加入refchain中。
```
然而 python 在启动解释器时python 认为-5、-4、….. 、256bool、一定规则的字符串这些值都是常用的值所以就会在内存中帮你先把这些值先创建好接下来进行验证
```python
# 启动解释器时python内部帮我们创建-5、-4、...255、256的整数和一定规则的字符串
v1 = 9 # 内部不会开辟内存,直接去池中获取
v2 = 9 # 同上都是去数据池里直接拿9所以v1和v2指向的内存地址是一样的
print(id(v1),id(v2))
v3 = 256 # 内部不会开辟内存,直接去池中获取
v4 = 256 # 同上都是去数据池里直接拿256所以v3和v4指向的内存地址是一样的
print(id(v3),id(4))
v5 = 257
v6 = 257
print(id(v5),id(v6))
```
### 代码块缓存机制
在大多数情况下,同一个代码块(一个模块、一个函数、一个类、一个文件等都可以理解为一个代码块)中,也会存在一定的缓存机制
Python在执行同一个代码块的初始化对象的命令时会检查是否其值是否已经存在如果存在会将其重用即将两个变量指向同一个对象。换句话说执行同一个代码块时遇到初始化对象的命令时他会将初始化的这个变量与值存储在一个字典中在遇到新的变量时会先在字典中查询记录如果有同样的记录那么它会重复使用这个字典中的之前的这个值。所以在用命令模式执行时同一个代码块会把i1、i2两个变量指向同一个对象满足缓存机制则他们在内存中只存在一个id相同。
* 适用对象: intfloatstrbool。
* 对象的具体细则:(了解)
- int(float)**任何数字**在同一代码块下都会复用。
* boolTrue和False在字典中会以**10**方式存在,并且复用。
* str**几乎所有的字符串**都会符合字符串驻留机制。
```python
# 同一个代码块内的缓存机制————任何数字在同一代码块下都会复用
i1 = 1000
i2 = 1000
print(id(i1))
print(id(i2))
# 同一个代码块内的缓存机制————几乎所有的字符串都会符合缓存机制
s1 = 'hfdjka6757fdslslgaj@!#fkdjlsafjdskl;fjds中国'
s2 = 'hfdjka6757fdslslgaj@!#fkdjlsafjdskl;fjds中国'
print(id(s1))
print(id(s2))
# 同一个代码块内的缓存机制————非数字、str、bool类型数据指向的内存地址一定不同
t1 = (1,2,3)
t2 = (1,2,3)
l1 = [1,2,3]
l2 = [1,2,3]
print(id(t1))
print(id(t2))
print(id(l1))
print(id(l2))
```
### 小对象的内存管理
- **小对象**Python 将小对象定义为大小小于 512 字节的对象。对于小对象Python 会从专门的内存池中分配内存,而不是每次都向操作系统申请内存。这减少了频繁的系统调用,从而提高了性能。
- **内存池**Python 维护一个内存池将小对象的内存分配和释放集中管理。通过这种方式Python 避免了频繁的内存碎片化问题。
### 大对象的内存管理
- **大对象**:对于大小超过 512 字节的对象Python 直接向操作系统申请内存。这些对象的分配和释放不经过内存池。
## free_list
当一个对象的引用计数器为0的时候按理说应该回收但是在python内部为了优化不会去回收而是将对象添加到free_list链表中当作缓存。以后再去创建对象时就不再重新开辟内存而是直接使用free_list。
```python
v1 = 3.14 # 创建float型对象加入refchain并且引用计数器的值为1
del v1 #refchain中移除按理说应该销毁但是python会将对象添加到free_list中。
v2 = 3.14 # 就不会重新开辟内存去free_list中获取对象对象内部数据初始化再放到refchain中。
```
但是free_list也是有容量的不是无限收纳, 假设默认数量为80只有当free_list满的时候才会直接去销毁。
代表性的有float/list/tuple/dict这些数据类型都是以free_list方式来进行回收的。
**总结一下就是引用计数器为0的时候有的是直接销毁而有些需要先加入缓存当中的。**
# [扩展] C 源码
`arena`是 CPython 的内存管理结构之一。代码在`Python/pyarena.c`中其中包含了 C 的内存分配和解除分配的方法。
[https://github.com/python/cpython/blob/master/Python/pyarena.c](https://github.com/python/cpython/blob/master/Python/pyarena.c)
`Modules/gcmodule.c`,该文件包含垃圾收集器算法的实现。
[https://github.com/python/cpython/blob/master/Modules/gcmodule.c](https://github.com/python/cpython/blob/master/Modules/gcmodule.c)

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -0,0 +1,147 @@
# Python推导式
## 列表推导式
列表推导式主要是用于按照我们指定的内容填充,生成一个新的列表,基本语法如下:
> new_list = [ expression for item in iterable if condition ]
### 示例
```python
# 生成0~10的一个列表
l = [i for i in range(11)]
print(l) # 输出: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 生成平方数的列表
squares = [x ** 2 for x in range(10)]
print(squares) # 输出: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# 生成偶数的列表
even_numbers = [x for x in range(20) if x % 2 == 0]
print(even_numbers) # 输出: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
```
推导式没有特殊之处,只是一种便利的编程方式....
### 案例
1. 找到嵌套列表中名字含有两个及以上a的所有名字
```python
fruits = [['peach','Lemon','Pear','avocado','cantaloupe','Banana','Grape'],
['raisins','plum','apricot','nectarine','orange','papaya']]
print([name for lst in fruits for name in lst if name.count('a') >= 2])
/
# Output:
['avocado', 'cantaloupe', 'Banana', 'papaya']
```
2. 30以内所有能被3整除的数
```python
multiples = [i for i in range(30) if i % 3 == 0]
print(multiples)
```
## 集合推导式
集合推导式用于生成一个新的集合。它的语法与列表推导式类似,但使用大括号 `{}`
```python
new_set = {expression for item in iterable if condition}
```
### 示例
```python
# 生成平方数的集合
squares_set = {x ** 2 for x in range(10)}
print(squares_set) # 输出: {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}
# 生成唯一的字符集合
unique_chars = {char for char in "hello world"}
print(unique_chars) # 输出: {'h', 'e', 'l', 'o', ' ', 'r', 'w', 'd'}
```
### 案例
计算列表中每个值的平方,自带去重功能
```python
l = [1,2,3,4,1,-1,-2,3]
squared = {x**2 for x in l}
print(squared)
# Output:
{16, 1, 4, 9}
```
## 字典推导式
同上,我们可以使用字典推导式生成一个新的字典
```python
new_dict = {key_expression: value_expression for item in iterable if condition}
```
### 示例
```python
# 生成平方数的字典
squares_dict = {x: x ** 2 for x in range(10)}
print(squares_dict) # 输出: {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}
# 生成字符及其 ascii 值的字典
ascii_dict = {char: ord(char) for char in "abc"}
print(ascii_dict) # 输出: {'a': 97, 'b': 98, 'c': 99}
```
### 案例
1. 将一个字典的key和value对调
```python
dic1 = {'a':1,'b':2}
dic2 = {dic1[k]: k for k in dic1}
print(dic2)
```
2. 合并大小写对应的value值将k统一成小写
```python
dic1 = {'a':1,'b':2,'y':1, 'A':4,'Y':9}
dic2 = {k.lower():dic1.get(k.lower(),0) + dic1.get(k.upper(),0) for k in dic1.keys()}
print(dic2)
```
## 生成器表达式
把列表推导式的的`[]`换成`()`就变成了生成器表达式,相比于列表表达式,生成器表达式不会直接产生值,而是需要我们手动去迭代它
### 案例
```python
gen_expr = (x ** 2 for x in range(5))
print(gen_expr)
```
**手动迭代:**
```python
for value in gen_expr:
print(value)
```
# 小练一下
使用推导式打印出九九乘法口诀表(仅使用一行代码)
```python
print('\n'.join(['\t'.join([f'{i}x{j}={i * j}' for i in range(1,j+1)]) for j in range(1, 10)]))
```

View File

@@ -0,0 +1,672 @@
# 内置函数
截止到python版本3.9.22python一共为我们提供了**69个内置函数。**
https://docs.python.org/zh-cn/3.9/library/functions.html
## 作用域相关
### `locals()`
函数会以字典的类型返回当前位置的全部局部变量。
### `globals()`
函数以字典的类型返回全部全局变量。
**示例**
```python
a = 1
b = 2
print(locals())
print(globals())
# 这两个一样,因为是在全局执行的
def func(argv):
c = 2
print(locals())
print(globals())
func(3)
```
## 字符串类型代码执行
### `eval()`
计算指定表达式的值,并返回最终结果
```python
ret = eval('2 + 2')
print(ret)
n = 20
ret = eval('n + 23')
print(ret)
eval('print("Hello world")')
```
### `exec()`
执行字符串类型的代码
```python
s = '''
for i in range(5):
print(i)
'''
exec(s)
```
### `compile()`
用于将源代码编译成字节码,从而可以在后续执行中使用。这个函数的主要作用是将字符串形式的代码转换为可以通过 `exec()``eval()` 执行的代码对象。
**语法**
```python
compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)
```
**参数**
- **source**:要编译的源代码,可以是字符串或 AST抽象语法树对象。
- **filename**:表示代码的文件名(通常为字符串),用于错误消息的显示。可以是任意字符串。
- **mode**:指定编译模式
- `'exec'` :用于编译多条语句(如模块或函数)。
- `'eval'` :用于编译单个表达式。
- `'single'` :用于编译单个交互式语句。
**返回值**
返回一个代码对象,可以使用 `exec()``eval()` 执行
**示例**
```python
# 流程语句使用exec
code1 = 'for i in range(5): print(i)'
compile1 = compile(code1,'','exec')
exec(compile1)
# 简单求值表达式用eval
code2 = '1 + 2 + 3'
compile2 = compile(code2,'','eval')
eval(compile2)
# 交互语句用single
code3 = 'name = input("please input you name: ")'
compile3 = compile(code3,'','single')
exec(compile3)
print(name)
```
有返回值的字符串形式的代码用 eval没有返回值的字符串形式的代码用 exec一般不用 compile。
## 输入/输出相关
### `input()`
函数接受一个标准输入数据,返回为 string 类型。
### `print()`
打印输出。
```python
''' 源码分析
def print(self, *args, sep=' ', end='\n', file=None): # known special case of print
"""
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
file: 默认是输出到屏幕,如果设置为文件句柄,输出到文件
sep: 打印多个值之间的分隔符,默认为空格
end: 每一次打印的结尾,默认为换行符
flush: 立即把内容输出到流文件,不作缓存
"""
'''
print(11,22,33,sep='*')
print(11,22,33,end='')
print(44,55)
with open('log','w',encoding='utf-8') as f:
print('写入文件',file=f,flush=True)
```
## 内存相关
### `hash()`
获取一个对象可哈希对象intstrbooltuple的哈希值。
```python
print(hash(12322))
print(hash('123'))
print(hash('arg'))
print(hash('aaron'))
print(hash(True))
print(hash(False))
print(hash((1,2,3)))
```
### `id()`
用于获取对象的内存地址
```python
print(id('abc'))
print(id('123'))
```
## 文件操作相关
### `open()`
用于打开一个文件,创建一个 file 对象
### `read()`
通过文件对象调用,读取文件的内容
### `write()`
文件文件对象调用,往文件里下入内容
.......
## 帮助文档
### `help()`
函数用于查看函数或模块用途的详细说明。
```python
print(help(print))
#结果
print(...)
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
```
## 调用相关
### `callable()`
用于检查一个对象是否是可调用的。如果返回 Trueobject 仍然可能调用失败;但如果返回 False调用对象 ojbect 绝对不会成功。
```python
print(callable(0))
print(callable('hello'))
# 自定义函数
def demo1(a, b):
return a + b
print(callable(demo1))
# 自定义类
class Demo2:
def test1(self):
return 0
print(callable(Demo2))
# 实例化对象
a = Demo2()
print(callable(a))
```
## 查看内置属性
### `dir()`
函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表。如果参数包含方法 `__dir__()` ,该方法将被调用。如果参数不包含 `__dir__()` ,该方法将最大限度地收集参数信息。
```python
import time
print(dir(time)) # 查看某个模块的属性和方法
print(dir([])) # 查看列表的属性和方法
```
## 迭代器生成器相关
### `range()`
函数可创建一个整数对象,一般用在 for 循环中。
### `next()`
内部实际使用了 `__next__` 方法,返回迭代器的下一个项目。
### `iter()`
函数用来生成迭代器(通过一个可迭代对象生成迭代器)。
```python
from collections import Iterator
# 首先获得Iterator对象:
l = [1,2,3,4]
l1 = iter(l)
print(isinstance(l1,Iterable))
print(isinstance(l1,Iterator))
# 循环
while True:
try:
# 获得下一个值
x = next(l1)
print(x)
except StopIteration: # 遇到StopIteration就退出循环
break
```
## 数据类型相关
### `bool()`
将给定参数转换为布尔类型,如果没有参数,返回 False
### `int()`
将一个字符串或数字转换为整型
### `float()`
将整数和字符串转换成浮点数。
### `complex()`
将创建复数complex numbers。复数是由实部和虚部组成的通常写作 `a + bj` 的形式,其中 `a` 是实部,`b` 是虚部,`j` 是虚数单位。
```python
print(int())
print(int('12'))
print(int(3.6))
print(int('0100',base=2)) # 将2进制的 0100 转化成十进制。结果为 4
z1 = complex(2, 3) # 实部为 2虚部为 3
print(z1) # 输出: (2+3j)
```
## 进制转换相关
### `bin()`
将十进制转换成二进制并返回。
### `ocb()`
将十进制转化成八进制字符串并返回。
### `hex()`
将十进制转化成十六进制字符串并返回。
**示例**
```python
print(bin(10),type(bin(10)))
print(oct(10),type(oct(10)))
print(hex(10),type(hex(10)))
```
## 数学运算相关
### `abs()`
返回数字的绝对值。
### `divmod()`
计算除数与被除数的结果,返回一个包含商和余数的元组 `(a // b, a % b)`
### `round()`
保留浮点数的小数位数,默认保留整数。
### `pow()`
函数是计算 x 的 y 次方,如果 z 存在,则再对结果进行取模,其结果等效于 `(pow(x,y) % z)`
**示例**
```python
print(abs(-5)) # 5
print(divmod(7,2)) # (3, 1)
print(round(7/3,2)) # 2.33
print(round(7/3)) # 2
print(round(3.32567,3)) # 3.326
print(pow(2,3)) # 8
print(pow(2,3,3)) # 2
```
### `sum()`
对可迭代对象进行求和计算(可设置初始值)。
### `min()`
返回可迭代对象的最小值(可加 keykey 为函数名,通过函数的规则,返回最小值)。
### `max()`
返回可迭代对象的最大值(可加 keykey 为函数名,通过函数的规则,返回最大值)。
**示例**
```python
print(sum([1,2,3]))
print(sum([1,2,3],100))
print(min([1,2,3]))
ret = min([1,2,3,-10],key=abs)
print(ret)
print(max([1,2,3]))
ret = max([1,2,3,-10],key=abs)
print(ret)
```
## 数据结构相关
### `list()`
将一个可迭代对象转化成列表(如果是字典,默认将 key 作为列表的元素)。
### `tuple()`
将一个可迭代对象转化成元祖(如果是字典,默认将 key 作为元祖的元素)。
### `dict()`
创建一个字典。
### `set()`
创建一个集合。
### `frozenset()`
创建一个冻结的集合。
```python
l = list((1,2,3))
print(l)
l = list({1,2,3})
print(l)
l = list({'k1':1,'k2':2})
print(l)
tu = tuple((1,2,3))
print(tu)
tu = tuple([1,2,3])
print(tu)
tu = tuple({'k1':1,'k2':2})
print(tu)
```
## 字符串相关
### `str()`
将数据转化成字符串。
### `format()`
具体数据相关,用于计算各种小数,精算等。
```python
# 字符串可以提供的参数,指定对齐方式,<是左对齐, >是右对齐,^是居中对齐
print(format('test','<20'))
print(format('test','>20'))
print(format('test','^20'))
# 整形数值可以提供的参数有 'b' 'c' 'd' 'o' 'x' 'X' 'n' None
print(format(192,'b')) # 转换为二进制
print(format(97,'c')) # 转换unicode成字符
print(format(11,'d')) # 转换成10进制
print(format(11,'o')) # 转换为8进制
print(format(11,'x')) # 转换为16进制小写字母表示
print(format(11,'X')) # 转换为16进制大写字母表示
print(format(11,'n')) # 和d一样
print(format(11)) # 和d一样
# 浮点数可以提供的参数有 'e' 'E' 'f' 'F' 'g' 'G' 'n' '%' None
print(format(314159265,'e')) # 科学计数法默认保留6位小数
print(format(314159265,'0.2e')) # 科学计数法保留2位小数
print(format(314159265,'0.2E')) # 科学计数法保留2位小数,大写E
print(format(3.14159265,'f')) # 小数点计数法默认保留6位小数
print(format(3.14159265,'0.10f')) # 小数点计数法保留10位小数
print(format(3.14e+10000,'F')) # 小数点计数法,无穷大转换成大小字母
# g的格式化比较特殊假设p为格式中指定的保留小数位数先尝试采用科学计数法格式化得到幂指数exp如果-4<=exp<p则采用小数计数法并保留p-1-exp位小数否则按小数计数法计数并按p-1保留小数位数
print(format(0.00003141566,'.1g'))
# p=1,exp=-5 ==》 -4<=exp<p不成立按科学计数法计数保留0位小数点
print(format(0.00003141566,'.2g'))
# p=2,exp=-5 ==》 -4<=exp<p不成立按科学计数法计数保留1位小数点
print(format(3.1415926777,'.1g'))
# p=1,exp=0 ==》 -4<=exp<p成立按小数计数法计数保留0位小数点
print(format(3.1415926777,'.2g'))
# p=2,exp=0 ==》 -4<=exp<p成立按小数计数法计数保留1位小数点
print(format(3141.5926777,'.2g'))
# p=2,exp=3 ==》 -4<=exp<p不成立按科学计数法计数保留1位小数点
print(format(0.00003141566,'.1n')) # 和g相同
print(format(0.00003141566)) # 和g相同
```
### `repr()`
返回一个对象的 string 形式
```python
name = 'EaglesLab'
print('Hello %r' % name)
str1 = '{"name":"EaglesLab"}'
print(repr(str1))
print(str1)
```
## 数据结构操作相关
### `reversed()`
将一个序列翻转,并返回此翻转序列的迭代器。
### `slice()`
构造一个切片对象,用于列表的切片。
```python
ite = reversed(['a',2,4,'f',12,6])
for i in ite:
print(i)
l = ['a','b','c','d','e','f','g']
sli = slice(3)
print(l[sli])
sli = slice(0,7,2) # 也可以加上步长
print(l[sli])
```
### `len()`
返回一个对象中元素的个数。
### `sorted()`
对所有可迭代的对象进行排序操作。
```python
numbers = [5, 2, 9, 1, 5, 6] # 升序
sorted_numbers = sorted(numbers)
print(sorted_numbers) # 输出: [1, 2, 5, 5, 6, 9]
numbers = [5, 2, 9, 1, 5, 6] # 降序
sorted_numbers_desc = sorted(numbers, reverse=True)
print(sorted_numbers_desc) # 输出: [9, 6, 5, 5, 2, 1]
# 可以使用 key 参数指定一个函数,用于从元素中提取用于排序的比较键
words = ['banana', 'apple', 'cherry']
sorted_words = sorted(words, key=len)
print(sorted_words) # 输出: ['apple', 'banana', 'cherry']
```
### `enumerate()`
将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
```python
print(enumerate([1,2,3]))
for i in enumerate([1,2,3]):
print(i)
for i in enumerate([1,2,3],100):
print(i)
```
### `all()`
可迭代对象中全都是True才是True。
### `any()`
可迭代对象中有一个True 就是True
```python
print(all([1,2,True,0]))
print(any([1,'',0]))
```
### `zip()`
将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同。
```python
names = ['zhangsan', 'lisi', 'wangwu']
ages = [25, 30, 35, 40]
zipped = zip(names, ages)
print(list(zipped))
```
### `filter()`
过滤序列中不符合条件的元素,返回由符合元素组成的新的迭代器。
```python
def func(x):
return x % 2 == 0
ret = filter(func,[1,2,3,4,5,6,7,8,9,10])
print(ret)
for i in ret:
print(i)
# 过滤出大于 5 的数字
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]
filtered_numbers = filter(lambda x: x > 5, numbers)
print(list(filtered_numbers)) # 输出: [6, 7, 8, 9]
```
### `map()`
将指定函数应用于可迭代对象中的每个元素,并返回一个迭代器。这个函数非常有用,可以简化对数据的转换和处理。
```python
def square(x):
return x**2
list1 = [1,2,3,4,5,6,7,8]
ret = map(square,list1)
print(ret)
for i in ret:
print(i)
```
# 匿名函数
在 Python 中,匿名函数是指没有名称的函数,通常使用 `lambda` 关键字定义。匿名函数可以用于需要函数作为参数的场合,比如在 `map()``filter()``sorted()` 等函数中。
也可以理解为,为了解决那些功能很简单的需求而设计的一句话函数。
## 语法
```python
lambda 参数: 表达式
```
- 参数可以有多个,用逗号隔开
- 匿名函数不管逻辑多复杂,只能写一行,且逻辑执行结束后的内容就是返回值
- 返回值和正常的函数一样可以是任意数据类型
示例:
```python
# 自定义函数的方法
def func1(n):
return n ** n
print(func1(10))
# 换成匿名函数
func2 = lambda n: n ** n
print(func2(10))
```
## 案例
1. 计算两个数的和
```python
add = lambda x, y: x + y
print(add(2, 3)) # 输出: 5
```
2. 配合 `map()` 使用
```python
list1 = [1,2,3,4,5,6,7,8]
ret = map(lambda x: x ** 2,list1)
print(ret)
for i in ret:
print(i)
```
3. 配合 `filter()` 使用
```python
ret = filter(lambda x: x % 2 == 0,[1,2,3,4,5,6,7,8,9,10])
print(ret)
for i in ret:
print(i)
```

View File

@@ -0,0 +1,363 @@
# 递归算法
## 什么是递归
在计算机中程序调用自身的编程技巧我们称之为递归算法。那么再通俗一点来讲就是在某个python文件中有一个函数这个函数可以在自己的函数体内根据条件自己调用自己的函数那么这样自身调用自身的过程或者说行为我们称之为递归。
再简单的讲,就是在一个函数里再次调用该函数本身
为了防止递归无限进行,通常我们还会指定一个退出条件
## 案例切入
我们可以用 **求和** 这样一个更简单的例子来展示从迭代到递归的过渡。假设我们要求从 1 到 `n` 的整数和。这个问题既可以用迭代方式解决,也可以用递归方式解决。
- 用迭代的方式解决这个问题
```python
def sum(n):
sum_total = 0
for i in range(1,n+1):
sum_total += i
return sum_total
print(sum(100))
# Output:
5050
```
- 转换为迭代的思想解决
递归的思想是将问题分解为更小的子问题。对求和的递归定义如下:
-`n = 1` 时,返回 1递归的基础情况
- 否则,`sum(n)` 可以表示为 `n + sum(n - 1)`,逐步缩小问题规模。
```python
def sum(n):
if n == 1:
return 1
else:
return n + sum(n-1)
print(sum(100))
# Output:
5050
```
**迭代方式**:通过循环从 1 到 `n` 累加。
**递归方式**:通过将问题分解为 `n + sum(n - 1)`,直到到达递归的基础情况 `n == 1`
## 递归的核心思想
递归可以理解为如果我们做一件很多步骤的事情,但是每个步骤都有固定的规律。那我们就可以将这个规律写成一个代码,反复调用这个代码,就可以实现完成整件事情
比如,小明的妈妈让他去把客厅的书搬到书房,但是由于书太多了,一次性搬不完。于是小明想到了一个聪明的办法。
“我可以一次搬走一本,然后身下的书由该怎么搬呢?”
转换一下思维,把他变成”我们每次都搬走最上面的书,然后第二次的时候,下面的书就变成的最上面的书,我们还是按照同样的办法搬走最上面的书“
**递归的核心思想:**把一个复杂的问题分解为一个小问题,并且这个小问题的解决方法和原问题完全一样。直到问题变得足够简单,可以直接解决。
**用python代码实现小明搬书的过程**
```python
def move_books(n):
if n == 1:
print("搬走第 1 本书")
else:
print(f"搬走第 {n} 本书")
move_books(n - 1)
# 假设有 5 本书
move_books(5)
# Output:
搬走第 5 本书
搬走第 4 本书
搬走第 3 本书
搬走第 2 本书
搬走第 1 本书
```
## 递归的最大深度
在Python中为了防止程序占用过多的内存和资源会有一个递归最大深度的限制一般情况下是997
```python
def foo(n):
print(n)
n += 1
foo(n)
foo(1)
```
当然我们也可以手动调整这个递归的最大深度限制这里我们调整为2000
```python
import sys
print(sys.setrecursionlimit(2000))
def foo(n):
print(n)
n += 1
foo(n)
foo(1)
```
然而我们并不推荐修改这个默认的递归深度如果用997层递归都没有解决这个问题就要考虑一下这个问题是不是适合使用递归来解决了。
## 汉诺塔问题
汉诺塔是一个源于印度古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定在小圆盘上不能放大圆盘在三根柱子之间一次只能移动一个圆盘。
- [点击开始汉诺塔游戏](http://file.eagleslab.com:8889/%E8%AF%BE%E7%A8%8B%E7%9B%B8%E5%85%B3%E8%BD%AF%E4%BB%B6/%E4%BA%91%E8%AE%A1%E7%AE%97%E8%AF%BE%E7%A8%8B/Python%E7%9B%B8%E5%85%B3/hanoi/)
<img src="06.初识算法/汉诺塔.png" alt="image-20241009104523929" style="zoom:80%;" />
如果层数较多的话只靠人力是很难完成的。据统计如果按照神话故事里的64片来计算假设每移动一步需要1秒那么如果64片全部移动完成大概需要18446744073709551615秒转换为年大概是5845.42亿年。然而地球存在也不过45亿年....
如果用代码实现呢?我们可以尝试以下
```python
def move(n,A,B,C):
'''
n代表层数
A代表原来的柱子
B代表空闲的柱子
C代表目的柱子
'''
if n == 1:
# 如果只有一层的话那么直接从A移动到C就结束了
print(A,'-->',C)
else:
# 将n-1个盘子从A-->B
move(n-1,A,C,B)
# 再将最后一个盘子从A-->C
print(A,'-->',C)
move(n-1,B,A,C)
n = int(input('请输入汉诺塔的层数:'))
move(n,'A','B','C')
```
## 二分查找算法
<img src="06.初识算法/二分查找.png" alt="image-20241009161706993" style="zoom:80%;" />
接下来我们使用不同的查找方式,来查找某个元素,看一下需要查找多少次才能找到
### 使用循环的方式去查找
```python
l = [2,3,5,10,15,16,18,22,26,30,32,35,41,42,43,55,56,66,67,69,72,76,82,83,88]
num = 0
for i in l:
num += 1
if i ==66:
print('66在索引为:',l.index(i),'的位置')
break
print('查找次数:',num)
# Output:
66在索引为: 17 的位置
查找次数: 18
```
使用循环的方式查找66这个元素一共需要查找18次
### 递归方法
```python
l = [2,3,5,10,15,16,18,22,26,30,32,35,41,42,43,55,56,66,67,69,72,76,82,83,88]
num = 0
def func(l,aim):
global num
num += 1
mid = (len(l)-1)//2
if l:
if aim > l[mid]:
func(l[mid+1:],aim)
elif aim < l[mid]:
func(l[:mid],aim)
elif aim == l[mid]:
print(f'查找次数:{num}次')
else:
print('没有找到')
func(l,66)
```
这里使用二分法查找,` mid = (len(l)-1)//2`是用来找到整个列表长度的中间位置,然后如果要找的数字大于中间位置(索引)的数字,那么就把整个列表截断,拿着后面的列表再次调用该函数进行查找,以此往复。最后找到我们要查找的哪个数字。
# 排序算法
## 选择排序
### 思路:
选择排序的基本思想是:每一次从未排序的部分中找到最小(或最大)的元素,并将其放到已排序部分的末尾。
### 步骤:
1. 从未排序的部分中找到最小的元素。
2. 将这个最小元素与未排序部分的第一个元素交换位置。
3. 重复这个过程,直到数组完全排序。
```python
def selection_sort(arr):
n = len(arr)
for i in range(n):
# 假设当前第 i 个元素是最小的
min_index = i
# 找到从 i 到 n-1 之间最小的元素
for j in range(i + 1, n):
if arr[j] < arr[min_index]:
min_index = j
# 交换当前元素和找到的最小元素
arr[i], arr[min_index] = arr[min_index], arr[i]
# 测试
arr = [64, 25, 12, 22, 11]
selection_sort(arr)
print("选择排序结果:", arr)
```
### 解释:
- 每一轮循环找到剩余元素中最小的元素,并将其放在已排序部分的末尾。
- 例如,对于 `[64, 25, 12, 22, 11]`,第一轮找到 `11`,和 `64` 交换。第二轮找到 `12`,和 `25` 交换,依此类推。
## 冒泡排序
### 思路:
冒泡排序的基本思想是:通过相邻元素的比较,不断将较大的元素“冒泡”到数组的末尾。
### 步骤:
1. 从数组的开头开始,比较每一对相邻的元素。如果顺序不对,就交换它们。
2. 一轮比较结束后,最大的元素会被放在最后。
3. 重复这个过程,直到数组完全排序。
```python
def bubble_sort(arr):
n = len(arr)
for i in range(n):
# 每次冒泡会把最大的数放到最后,所以每次可以少比较一个元素
for j in range(0, n - i - 1):
if arr[j] > arr[j + 1]:
# 交换相邻元素
arr[j], arr[j + 1] = arr[j + 1], arr[j]
# 测试
arr = [64, 34, 25, 12, 22, 11, 90]
bubble_sort(arr)
print("冒泡排序结果:", arr)
```
### 解释:
- 每一轮循环会将最大的元素移动到数组的末尾。通过交换相邻的元素,较大的元素会逐渐“冒泡”到数组的右侧。
- 例如,对于 `[64, 34, 25, 12, 22, 11, 90]`,第一轮会将 `90` 放到最后,第二轮会将 `64` 放到倒数第二位,依此类推。
## 快速排序
### 思路:
快速排序是一种“分而治之”的排序算法。它通过选择一个**基准**pivot将数组分成两部分一部分比基准小另一部分比基准大。然后递归地对这两部分进行排序。
### 步骤:
1. 选择一个基准元素pivot
2. 将数组分成两部分,一部分所有元素比基准小,另一部分所有元素比基准大。
3. 递归地对这两部分进行快速排序。
4. 合并结果。
```python
def quick_sort(arr):
# 基础情况:当数组长度为 0 或 1 时,直接返回
if len(arr) <= 1:
return arr
# 选择基准元素
pivot = arr[len(arr) // 2]
# 分割数组
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
# 递归排序并合并
return quick_sort(left) + middle + quick_sort(right)
# 测试
arr = [10, 7, 8, 9, 1, 5]
sorted_arr = quick_sort(arr)
print("快速排序结果:", sorted_arr)
```
### 解释:
- 快速排序通过选择一个基准元素(这里我们选择数组中间的元素),然后将数组分成三部分:小于基准的部分、等于基准的部分和大于基准的部分。
- 递归地对小于和大于基准的部分进行排序,最终将所有部分合并起来,形成有序数组。
- 例如,对于 `[10, 7, 8, 9, 1, 5]`,选择 `8` 作为基准,分成 `[7, 1, 5]``[8]``[10, 9]`,然后分别排序并合并。
## 插入排序
### 思路:
插入排序的基本思想是:将数组分为已排序和未排序两部分。每次从未排序部分取出一个元素,将其插入到已排序部分的正确位置。
### 步骤:
1. 从第二个元素开始,将其与前面的元素比较,插入到正确的位置。
2. 重复这个过程,直到整个数组有序。
```python
def insertion_sort(arr):
# 从第二个元素开始,因为第一个元素默认是已排序的
for i in range(1, len(arr)):
key = arr[i]
# 将 key 插入到前面已排序部分的正确位置
j = i - 1
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j] # 把比 key 大的元素向后移动
j -= 1
arr[j + 1] = key # 插入 key 到正确的位置
# 测试
arr = [12, 11, 13, 5, 6]
insertion_sort(arr)
print("插入排序结果:", arr)
```
### 解释:
- 插入排序模拟了我们整理手中扑克牌的过程。我们从前往后遍历数组,将每个元素插入到前面已排序部分的正确位置。
- 例如,对于 `[12, 11, 13, 5, 6]`,我们从 `11` 开始,将其插入到 `12` 前面。然后处理 `13`,再处理 `5`,最后处理 `6`,直到整个数组有序。
## 各个算法的比较:
| 排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 |
| -------- | --------------------------- | ---------------- | ----------------------- | ------ |
| 选择排序 | O(n2)O(n^2)O(n2) | O(n2)O(n^2)O(n2) | O(1)O(1)O(1) | 不稳定 |
| 冒泡排序 | O(n2)O(n^2)O(n2) | O(n2)O(n^2)O(n2) | O(1)O(1)O(1) | 稳定 |
| 快速排序 | O(nlogn)O(n \log n)O(nlogn) | O(n2)O(n^2)O(n2) | O(logn)O(\log n)O(logn) | 不稳定 |
| 插入排序 | O(n2)O(n^2)O(n2) | O(n2)O(n^2)O(n2) | O(1)O(1)O(1) | 稳定 |
- **选择排序**:每次选择未排序部分中最小的元素放到已排序部分末尾。适合数据量较小的情况。
- **冒泡排序**:通过相邻元素的比较交换,逐渐将较大的元素“冒泡”到数组末尾。
- **快速排序**:效率较高的一种排序算法,通过“分而治之”的方法将问题递归分解为更小的部分。
- **插入排序**:将每个元素插入到前面已排序部分的正确位置,适合数据量较小且基本有序的数组。
# 扩展阅读
从现在开始坚持刷算法题,日积月累,你终将成为算法大佬。如果你现在觉得还早,那么......
[力扣](https://leetcode.cn/)

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -0,0 +1,862 @@
# 序列化相关模块
将原本的字典、列表等内容转换成一个字符串的过程就叫做**序列化**
**序列化的目的**
1. 以某种存储形式使自定义对象持久化。
2. 将对象从一个地方传递到另一个地方。
3. 使程序更具维护性。
<img src="常用模块/序列化.png" alt="序列化" style="zoom: 80%;" />
Python 可序列化的数据类型,序列化出来之后的结果如下:
| Python | JSON |
| ---------- | ------ |
| dict | object |
| list,tuple | array |
| str | string |
| int,float | number |
| True | true |
| False | false |
| None | null |
## json 模块
Python 的 `json` 模块用于处理 JSONJavaScript Object Notation数据格式。JSON 是一种轻量级的数据交换格式,易于人类阅读和编写,同时也易于机器解析和生成。`json` 模块提供了简单的方法来编码序列化和解码反序列化JSON 数据。
### 常用功能
1. **序列化**:将 Python 数据类型转换为 JSON 格式。
2. **反序列化**:将 JSON 格式的数据转换为 Python 数据类型。
### 基本用法
**序列化 & 反序列化**
```python
import json
data = {
"name": "EaglesLab",
"age": 30,
"is_student": False,
"courses": ["SRE", "Python"]
}
json_string = json.dumps(data) # 将 Python 对象转换为 JSON 字符串
print(json_string)
data = json.loads(json_string) # 将 JSON 字符串转换为 Python 对象
print(data)
print(data['name']) # 访问字典中的值
```
**文件内容相关**
```python
with open('json_data.json', 'r') as file:
data = json.load(file) # 将文件中 JSON 数据转换为 Python 对象
print(data)
data = {
"name": "Bob",
"age": 25,
"is_student": True,
"courses": ["English", "History"]
}
with open('output.json', 'w') as file: # 将 Python 对象转换为文件中 JSON 数据
json.dump(data, file)
```
### 序列化相关参数
| 参数 | 作用 |
| :----------- | :----------------------------------------------------------- |
| skipkeys | 用于控制对字典键类型的严格性检查,默认为 False当字典的键不是 JSON 规范支持的基本类型时,直接抛出 `TypeError` 异常,反之,跳过这些键,不引发错误,但会导致数据丢失。 |
| indent | 控制 JSON 字符串的格式化缩进,默认值 None 会生成最紧凑的 JSON 字符串,无缩进和多余空格。 |
| ensure_ascii | 控制非 ASCII 字符转义行为,默认为 True中文会被转义为 Unicode 转义序列。 |
| separators | 自定义 JSON 字符串中元素间的分隔符和键值对的分隔符。 |
| sort_keys | 是否将数据根据 key 进行排序 |
## pickle 模块
Python 的 `pickle` 模块用于对象的序列化(将对象转换为字节流)和反序列化(将字节流转换回对象)。这使得在程序之间传递对象或将对象保存到文件中变得非常方便。
不同的是 json 模块序列化出来的是通用格式,其它编程语言都认识,就是普通的字符串,而 pickle 模块序列化出来的只有python 可以认识,其他编程语言不认识的,表现为乱码。
### 基本用法
**序列化 & 反序列化**
```python
import pickle
data = {
'name': 'EaglesLab',
'age': 30,
'is_student': False,
'courses': ['Math', 'Science']
}
print(pickle.dumps(data)) # 序列化
print(pickle.loads(pickle.dumps(data))) # 反序列化
```
**文件内容相关**
```python
import pickle
with open('./data.pkl', "wb") as f:
pickle.dump(data, f) # 序列化
pickle.dump([1,2,3], f) # 多次调用处理多个对象
with open("./data.pkl", "rb") as f:
print(pickle.load(f)) # 反序列化
print(pickle.load(f)) # 多次调用处理多个对象
```
## shelve 模块
Python 的 `shelve` 模块提供了一种简单的持久化存储方式,类似于字典,但它可以将数据持久化到文件中。`shelve` 模块允许将 Python 对象存储在文件中,以便在后续的程序运行中重新加载。
### 基本用法
```python
import shelve
shelf = shelve.open('my_shelf') # 打开文件
shelf['name'] = 'EaglesLab' # 存储数据
shelf['age'] = 30
shelf['courses'] = ['Math', 'Science']
print(shelf['name']) # 读取数据
shelf['age'] = 11 # 更新数据
del shelf['courses'] # 删除数据
shelf.close() # 关闭文件
```
## hashlib 模块
Python 的 `hashlib` 模块提供了多种安全哈希和消息摘要算法的接口。这些算法用于生成数据的唯一哈希值,广泛应用于数据完整性校验、密码存储和数字签名等领域。
什么是摘要算法呢摘要算法又称哈希算法、散列算法。它通过一个函数把任意长度的数据转换为一个长度固定的数据串通常用16进制的字符串表示
摘要算法就是通过摘要函数对任意长度的数据计算出固定长度的摘要,目的是为了发现原始数据是否被人篡改过。
摘要算法之所以能指出数据是否被篡改过,就是因为摘要函数是一个单向函数,计算摘要很容易,但通过摘要反推数据却非常困难。而且,对原始数据做一个 bit 的修改,都会导致计算出的摘要完全不同。
`hashlib` 支持多种哈希算法,包括:
- MD5
- SHA-1
- SHA-224
- SHA-256
- SHA-384
- SHA-512
- BLAKE2
### 基本用法
```python
import hashlib
# sha256_hash = hashlib.sha256(b'xxxx')
sha256_hash = hashlib.sha256() # 创建一个 SHA-256 哈希对象
md5_hash = hashlib.md5()
data = b"123456"
sha256_hash.update(data) # 更新哈希对象
sha256_digest = sha256_hash.hexdigest() # 获取哈希值
print("SHA-256:", sha256_digest)
```
### 加盐
```python
import hashlib
import os
salt = os.urandom(16) # 生成随机盐
password = b"123456"
hashed = hashlib.pbkdf2_hmac('sha256', password, salt, 10) # 使用 sha256 和 10次迭代
print(hashed)
import binascii
print(binascii.hexlify(stored_hash).decode('utf-8')) # 转成十六进制表示
```
### 注意事项
1. **不可逆性**:哈希函数是不可逆的,意味着无法从哈希值恢复原始数据。
2. **碰撞**:不同的输入可能生成相同的哈希值(称为碰撞),但现代的哈希算法力求使碰撞的概率尽量低。
3. **安全性**:对于密码存储,建议使用更安全的哈希算法(如 SHA-256 或更高版本和适当的盐值salt来增强安全性。
### 使用场景
- **数据完整性**:用于验证文件或数据在传输过程中未被篡改。
- **密码存储**:将用户密码的哈希值存储在数据库中,而不是明文密码。
- **数字签名**:用于创建数字签名,确保数据来源的可靠性。
### 案例:密码验证
```python
import hashlib
import os
salt = os.urandom(16)
password = b"123456"
stored_hash = hashlib.pbkdf2_hmac('sha256', password, salt, 10)
def verify_password(stored_hash, stored_salt, input_password):
new_hash = hashlib.pbkdf2_hmac('sha256', input_password, stored_salt, 10)
return new_hash == stored_hash
pwd = bytes(input('input passwrod: '), encoding='utf-8')
print(verify_password(stored_hash, salt, pwd))
```
# collections 模块
在内置数据类型dict、list、set、tuple的基础上collections 模块还提供了几个额外的数据类型Counter、deque、defaultdict、namedtuple 和 OrderedDict 等。
- namedtuple: 生成可以使用名字来访问元素内容的 tuple
- deque: 双端队列,可以快速的从另外一侧追加和推出对象
- Counter: 计数器,主要用来计数
- OrderedDict: 有序字典
- defaultdict: 带有默认值的字典
## namedtuple
```python
from collections import namedtuple
point = namedtuple('point',['x','y'])
p = point(1,2)
print(p.x) # 通过名字访问 tuple 元素
```
## deque
使用 list 存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为 list 是线性存储,数据量大的时候,插入和删除效率很低。
deque 是为了高效实现插入和删除操作的双向列表,适合用于队列和栈。
```python
from collections import deque
q = deque(['a','b','c'])
q.append('x') # 尾部添加
print(q)
q.pop() # 尾部删除
q.appendleft('y') # 头部添加
print(q)
q.popleft() # 头部删除
```
## OrderedDict
OrderedDict会按照插入的顺序排列不是 key 本身排序。Python 3.6 之前普通字典完全无序,键值顺序由哈希表存储方式决定。
```python
d1 = {'a':1, 'b':2}
d2 = {'b':2, 'a':1}
print(d1 == d2) # 输出True内容相同即视为相等
from collections import OrderedDict
od1 = OrderedDict([('a',1), ('b',2)])
od2 = OrderedDict([('b',2), ('a',1)])
print(od1 == od2) # 输出False有序字典比较顺序
```
## defaultdict
当键不存在时,自动生成默认值,普通 dict 抛出 KeyError 异常,需要频繁处理缺失键。
**基本用法**
```python
from collections import defaultdict
defaultdict(list) # 值类型为列表(分组)
defaultdict(set) # 值类型为集合(去重)
defaultdict(int) # 值类型为整数(计数)
```
**案例1计数器实现**
```python
# 以前写法
count = {}
for char in "hello":
if char not in count:
count[char] = 0
count[char] += 1
# defaultdict 实现
from collections import defaultdict
count = defaultdict(int)
for char in "hello":
count[char] += 1 # 首次访问自动初始化为0
```
**案例2统计单词首字母分组**
```python
from collections import defaultdict
words = ["apple", "banana", "avocado", "blueberry"]
index = defaultdict(list) # 初始化
for word in words:
first_char = word[0]
index[first_char].append(word) # 无需判断键是否存在
print(index['a']) # 输出:['apple', 'avocado']
```
## counter
Counter 类的目的是用来跟踪值出现的次数。它是一个无序的容器类型,以字典的键值对形式存储,其中元素作为 key其计数作为 value。
```python
from collections import Counter
c = Counter('qazxswqazxswqazxswsxaqwsxaqws')
print(c)
```
# 时间相关的模块
## time 模块
常用方法
- `time.sleep(secs)`(线程)推迟指定的时间运行。单位为秒。
- `time.time()`:获取当前时间戳。
在 Python 中,通常有这三种方式来表示时间:时间戳、结构化时间和格式化时间:
- 时间戳通常来说时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量。如 `1747114142.79492`
- 格式化时间:如 `1999-12-06`
- 结构化时间:如 `time.struct_time(tm_year=2025, tm_mon=5, tm_mday=13, tm_hour=13, tm_min=29, tm_sec=54, tm_wday=1, tm_yday=133, tm_isdst=0)`
```python
import time
# 时间戳
print(time.time())
# 格式化时间
print(time.strftime('%Y-%m-%d %X'))
print(time.strftime('%Y-%m-%d %H-%M-%S'))
# 结构化时间
print(time.localtime())
```
**格式化时间**
常见符号含义
| 符号 | 含义 |
| :---- | :---------------------------------------- |
| %y | 两位数的年份表示00-99 |
| %Y | 四位数的年份表示000-9999 |
| %m | 月份01-12 |
| %d | 月内中的一天0-31 |
| %H | 24小时制小时数0-23 |
| %I | 12小时制小时数01-12 |
| %M | 分钟数00=59 |
| %S | 秒00-59 |
| %a | 本地简化星期名称 |
| %A | 本地完整星期名称 |
| %b | 本地简化的月份名称 |
| %B | 本地完整的月份名称 |
| %c | 本地相应的日期表示和时间表示 |
| %j | 年内的一天001-366 |
| %p | 本地A.M.或P.M.的等价符 |
| %U | 一年中的星期数00-53星期天为星期的开始 |
| %w | 星期0-6星期天为星期的开始 |
| %W | 一年中的星期数00-53星期一为星期的开始 |
| %x | 本地相应的日期表示 |
| %X | 本地相应的时间表示 |
| %Z | 当前时区的名称 |
| %% | %号本身 |
**结构化时间**
常见索引、属性和值
| 索引 | 属性 | 值 |
| :------------ | :------------------------ | :----------------- |
| 0 | tm_year | 比如2011 |
| 1 | tm_mon | 1月12日 |
| 2 | tm_mday | 1月31日 |
| 3 | tm_hour | 0 - 23 |
| 4 | tm_min | 0 - 59 |
| 5 | tm_sec | 0 - 60 |
| 6 | tm_wdayweekday | 0 - 60表示周一 |
| 7 | tm_yday一年中的第几天 | 1 - 366 |
| 8 | tm_isdst是否是夏令时 | 默认为0 |
**格式之间的转换**
<img src="常用模块/时间格式转化1.png" alt="时间格式转化1" style="zoom:80%;" />
```python
import time
# 时间戳 <---> 结构化时间
timestamp = time.time()
struct_time = time.localtime(timestamp)
new_timestamp = time.mktime(struct_time)
# 结构化时间 <---> 格式化时间
## struct_time -> format_time
ft = time.strftime("%Y/%m/%d %H:%M:%S", time.localtime(1550312090))
## format_time -> struct_time
st = time.strptime("2024/05/14 11:57:30", "%Y/%m/%d %H:%M:%S")
```
<img src="常用模块/时间格式转化2.png" alt="时间格式转化2" style="zoom:80%;" />
```python
import time
# 结构化时间 --> %a %b %d %H:%M:%S %Y 字符串
# time.asctime(结构化时间) 如果不传参数,直接返回当前时间的格式化串
print(time.asctime(time.localtime(1550312090.4021888)))
# 时间戳 --> %a %d %d %H:%M:%S %Y 字符串
# time.ctime(时间戳) 如果不传参数,直接返回当前时间的格式化串
print(time.ctime(1550312090.4021888))
```
计算时间差
```python
import time
start_time = time.mktime(time.strptime('2017-09-11 08:30:00','%Y-%m-%d %H:%M:%S'))
end_time = time.mktime(time.strptime('2024-10-12 11:00:50','%Y-%m-%d %H:%M:%S'))
dif_time = end_time - start_time
struct_time = time.gmtime(dif_time)
print('过去了%d%d%d%d小时%d分钟%d秒' % (struct_time.tm_year-1970,struct_time.tm_mon-1,
struct_time.tm_mday-1,struct_time.tm_hour,
struct_time.tm_min,struct_time.tm_sec))
```
## datatime 模块
某些情况下,我们需要写一个定时的任务,比如几分钟后,几分钟前,这种情况下,用 time 模块就不太好操作。这个时候我们需要 datatime 模块来完成这个操作。
```python
# datatime模块
import datetime
current_time = datetime.datetime.now() # 当前时间
# 只能调整的字段weeks days hours minutes seconds
print(datetime.datetime.now() + datetime.timedelta(weeks=3)) # 三周后
print(datetime.datetime.now() + datetime.timedelta(weeks=-3)) # 三周前
print(datetime.datetime.now() + datetime.timedelta(days=-3)) # 三天前
print(datetime.datetime.now() + datetime.timedelta(days=3)) # 三天后
print(datetime.datetime.now() + datetime.timedelta(hours=5)) # 5小时后
print(datetime.datetime.now() + datetime.timedelta(hours=-5)) # 5小时前
print(datetime.datetime.now() + datetime.timedelta(minutes=-15)) # 15分钟前
print(datetime.datetime.now() + datetime.timedelta(minutes=15)) # 15分钟后
print(datetime.datetime.now() + datetime.timedelta(seconds=-70)) # 70秒前
print(datetime.datetime.now() + datetime.timedelta(seconds=70)) # 70秒后
current_time = datetime.datetime.now()
# 可直接调整到指定的 年 月 日 时 分 秒 等
print(current_time.replace(year=1977)) # 直接调整到1977年
print(current_time.replace(month=1)) # 直接调整到1月份
print(current_time.replace(year=1989,month=4,day=25)) # 1989-04-25 18:49:05.898601
# 时间戳 ---> 格式化时间
print(datetime.date.fromtimestamp(1232132131)) # 2009-01-17
```
# random 模块
用来生成随机数模块
```python
import random
print(random.random()) # 大于0且小于1之间的小数
print(random.uniform(1,3)) # 大于1小于3的小数
print(random.randint(1,5)) # 大于等于1且小于等于5之间的整数
print(random.randrange(1,10,2)) # 大于等于1且小于10之间的奇数
ret = random.choice([1,'23',[4,5]]) # 1或者23或者[4,5]
print(ret)
a,b = random.sample([1,'23',[4,5]],2) # 列表元素任意2个组合
print(a,b)
item = [1,3,5,7,9]
random.shuffle(item) # 打乱次序
print(item)
```
生成随机验证码
```python
import random
def v_code():
code = ''
for i in range(5):
num = random.randint(0,9)
alf = chr(random.randint(65,90)) # 大写字母
add = random.choice([num,alf])
code = "".join([code,str(add)])
return code
print(v_code())
```
# os 模块
操作系统交互接口
**工作路径相关**
| 方法 | 含义 |
| :------------------ | :----------------------------------------------- |
| `os.getcwd()` | 获取当前工作目录 |
| `os.chdir("dirname")` | 改变当前脚本工作目录 |
| `os.curdir` | 返回当前目录 `.` |
| `os.pardir` | 获取当前目录的父目录字符串名 `..` |
**文件夹相关**
| 方法 | 含义 |
| :------------------------------- | :----------------------------------------------------------- |
| `os.makedirs("dirname1/dirname2")` | 可生成多层递归目录 |
| `os.removedirs("dirname1/dirname2")` | 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推 |
| `os.mkdir("dirname")` | 生成单级目录 |
| `os.rmdir("dirname")` | 删除单级空目录,若目录不为空则无法删除,报错|
| `os.listdir("dirname")` | 列出指定目录下的所有文件和子目录|
**文件相关**
| 方法 | 含义 |
| :----------------------------- | :---------------- |
| `os.remove("path/to/filename")` | 删除一个文件 |
| `os.rename("oldname","newname")` | 重命名文件/目录 |
| `os.stat('path/to/filename')` | 获取文件/目录信息 |
**操作系统差异相关**
| 方法 | 含义 |
| :--------- | :------------------------------------------------------ |
| `os.sep` | 输出操作系统特定的路径分隔符|
| `os.linesep` | 输出当前平台使用的行终止符|
| `os.pathsep` | 输出用于分割文件路径的字符串|
| `os.name` | 输出字符串指示当前使用平台 |
**执行系统命令相关**
| 方法 | 含义 |
| :----------------------------- | :-------------------------- |
| `os.system(command)` | 运行命令,直接输出执行结果 |
| `os.popen(command).read()` | 运行命令,获取执行结果 |
| `os.environ` | 获取系统环境变量 |
**路径相关**
| 方法 | 含义 |
| :---------------------------------- | :----------------------------------------------------------- |
| `os.path.abspath(path)` | 返回 path 规范化的绝对路径 |
| `os.path.split(path)` | 将 path 分割成目录和文件名二元组返回 |
| `os.path.dirname(path)` | 返回 path 的目录 |
| `os.path.basename(path)` | 返回 path 最后的文件名 |
| `os.path.exists(path)` | 如果 path 存在,返回 True如果 path 不存在,返回 False |
| `os.path.isabs(path)` | 如果 path 是绝对路径,返回 True |
| `os.path.isfile(path)` | 如果 path 是一个存在的文件,返回 True。否则返回 False |
| `os.path.isdir(path)` | 如果 path 是一个存在的目录,则返回 True。否则返回 False |
| `os.path.join(path1[, path2[, ...]])` | 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略 |
| `os.path.getatime(path)` | 返回 path 所指向的文件或者目录的最后访问时间 |
| `os.path.getmtime(path)` | 返回 path 所指向的文件或者目录的最后修改时间 |
| `os.path.getsize(path)` | 返回 path 的大小 |
**文件属相关**
| 属性 | 含义 |
| :------- | :----------------------------------------------------------- |
| st_mode | inode 保护模式 |
| st_ino | inode 节点号 |
| st_dev | inode 驻留的设备 |
| st_nlink | inode 的链接数 |
| st_uid | 所有者的用户ID |
| st_gid | 所有者的组ID |
| st_size | 普通文件以字节为单位的大小;包含等待某些特殊文件的数据 |
| st_atime | 上次访问的时间 |
| st_mtime | 最后一次修改的时间 |
| st_ctime | 由操作系统报告的"ctime"。在某些系统上如Unix是最新的元数据更改的时间在其它系统上如Windows是创建时间详细信息参见平台的文档 |
# sys 模块
sys 模块是与 python 解释器交互的一个接口
| 方法 | 作用 |
| :----------- | :----------------------------------------------------- |
| `sys.argv` | 命令行参数 List第一个元素是程序本身路径 |
| `sys.exit(n)` | 退出程序,正常退出时 exit(0),错误退出 sys.exit(1) |
| `sys.version` | 获取 Python 解释程序的版本信息 |
| `sys.path` | 返回模块的搜索路径,初始化时使用 PYTHONPATH 环境变量的值 |
| `sys.platform` | 返回操作系统平台名称 |
# re 模块
## 正则表达式
正则表达式Regular Expression简称Regex是处理字符串的核心工具之一它通过预定义的模式规则实现文本的快速检索、匹配和替换。在 Python 中,正则表达式通过内置的 re 模块实现,广泛应用于数据清洗、表单验证、日志分析等领域。
| 元字符 | 匹配内容 |
| :----- | :----------------------------------------------------------- |
| \w | 匹配字母(包含中文)或数字或下划线 |
| \W | 匹配非字母(包含中文)或数字或下划线 |
| \s | 匹配任意的空白符 |
| \S | 匹配任意非空白符 |
| \d | 匹配数字 |
| \D | 匹配非数字 |
| \A | 匹配字符串的起始,严格匹配字符串的绝对开始 |
| \Z | 匹配字符串的结尾,严格匹配字符串的绝对末尾 |
| \n | 匹配一个换行符 |
| \t | 匹配一个制表符 |
| ^ | 匹配字符串的起始re.MULTILINE 匹配每一行的起始 |
| $ | 匹配字符串的结尾末尾或末尾的换行符前的位置re.MULTILINE 匹配每一行的末尾 |
| . | 匹配任意字符,除了换行符,当 re.DOTALL 标记被指定时,则可以匹配包括换行符的任意字符 |
| [...] | 匹配字符组中的字符 |
| [^...] | 匹配除了字符组中的字符的所有字符 |
| * | 匹配0个或者多个左边的字符。 |
| + | 匹配一个或者多个左边的字符。 |
| | 匹配0个或者1个左边的字符非贪婪方式。 |
| {n} | 精准匹配n个前面的表达式。 |
| {n,m} | 匹配n到m次由前面的正则表达式定义的片段贪婪方式 |
| a | b |
| () | 匹配括号内的表达式,也表示一个组 |
### 单字符匹配
```python
import re
# re.findall() 匹配所有返回列表
print(re.findall('\w','上大人123asdfg%^&*(_ \t \n)'))
print(re.findall('\W','上大人123asdfg%^&*(_ \t \n)'))
print(re.findall('\s','上大人123asdfg%^&*(_ \t \n)'))
print(re.findall('\S','上大人123asdfg%^&*(_ \t \n)'))
print(re.findall('\d','上大人123asdfg%^&*(_ \t \n)'))
print(re.findall('\D','上大人123asdfg%^&*(_ \t \n)'))
print(re.findall('\A上大','上大人123asdfg%^&*(_ \t \n)'))
print(re.findall('^上大','上大人123asdfg%^&*(_ \t \n)'))
print(re.findall('\A上大','上大人123asdfg%^&*(_ \t \n上大人456', re.MULTILINE))
print(re.findall('^上大','上大人123asdfg%^&*(_ \t \n上大人456', re.MULTILINE))
print(re.findall('666\Z','上大人123asdfg%^&*(_ \t \n)666'))
print(re.findall('666$','上大人123asdfg%^&*(_ \t \n)666'))
print(re.findall('666\Z','上大人123asdfg%^&*(_ \t \n)666\n'))
print(re.findall('666$','上大人123asdfg%^&*(_ \t \n)666\n'))
print(re.findall('666\Z','上大人123asdfg%^&*(_ \t 666\n)666', re.MULTILINE))
print(re.findall('666$','上大人123asdfg%^&*(_ \t \n)666\n666', re.MULTILINE))
print(re.findall('\n','上大人123asdfg%^&*(_ \t \n)'))
print(re.findall('\t','上大人123asdfg%^&*(_ \t \n)'))
```
### 重复匹配
```python
import re
print(re.findall('a.b', 'ab aab a*b a2b a牛b a\nb'))
print(re.findall('a.b', 'ab aab a*b a2b a牛b a\nb',re.DOTALL))
print(re.findall('a?b', 'ab aab abb aaaab a牛b aba**b'))
print(re.findall('a+b', 'ab aab aaab abbb'))
print(re.findall('a*b', 'ab aab aaab abbb'))
print(re.findall('ab*', 'ab aab aaab abbbbb'))
print(re.findall('a{2,4}b', 'ab aab aaab aaaaabb'))
# .*? 非贪婪匹配
print(re.findall('a.*b', 'ab aab a*()b'))
print(re.findall('a.*?b', 'ab a1b a*()b, aaaaaab'))
# []: 任意一个字符
print(re.findall('a[abc]b', 'aab abb acb adb afb a_b'))
# - 在[]中表示范围
print(re.findall('a[0-9]b', 'a1b a3b aeb a*b arb a_b'))
print(re.findall('a[a-z]b', 'a1b a3b aeb a*b arb a_b'))
print(re.findall('a[a-zA-Z]b', 'aAb aWb aeb a*b arb a_b'))
print(re.findall('a[0-9][0-9]b', 'a11b a12b a34b a*b arb a_b'))
print(re.findall('a[*-+]b','a-b a*b a+b a/b a6b'))
# ^ 在[]中表示取反
print(re.findall('a[^a-z]b', 'acb adb a3b a*b'))
# 分组() 制定一个规则,将满足规则的结果匹配出来
print(re.findall('(.*?)_66', 'cs_66 zhao_66 日天_66'))
print(re.findall('href="(.*?)"','<a href="http://www.baidu.com">点击</a>'))
# 分组() 中加入?:表示将整体匹配出来而不只是()里面的内容
print(re.findall('compan(y|ies)','Too many companies have gone bankrupt, and the next one is my company'))
print(re.findall('compan(?:y|ies)','Too many companies have gone bankrupt, and the next one is my company'))
```
### 常用方法举例
```python
import re
# findall 全部找到返回一个列表
print(re.findall('a','aghjmnbghagjmnbafgv'))
# search 扫描​​整个字符串​​,找到第一个符合模式的子串即返回匹配对象。
print(re.search('Eagle', 'welcome to Eagleslab'))
print(re.search('Eagle', 'welcome to Eagleslab').group())
# match 仅从字符串的起始位置开始匹配若起始位置不满足模式则直接返回None。
print(re.match('chensong', 'chenong 66 66 demon 日天'))
print(re.match('chensong', 'chensong 66 66 barry 日天').group())
# split 可按照任意分割符进行分割
print(re.split('[:,;]','1;3,c,a3'))
# sub 替换
print(re.sub('镇江','英格科技','欢迎来到镇江'))
# complie 根据包含的正则表达式的字符串创建模式对象。可以实现更有效率的匹配。
obj = re.compile('\d{2}')
print(obj.search('abc123eeee').group())
print(obj.findall('1231232aasd'))
# finditer 返回一个存放匹配结果的迭代器
ret = re.finditer('\d','asd123affess32432')
print(ret)
print(next(ret).group())
print(next(ret).group())
print([i.group() for i in ret])
```
### 命名分组举例
```python
import re
# 为分组命名,通过分组名获取对应值
ret = re.search("<(?P<tag_name>\w+)>\w+</(?P=tag_name)>","<h1>hello</h1>")
print(ret.group('tag_name'))
print(ret.group())
# 使用序号替换命名,通过分组序号获取对应值
ret = re.search(r"<(\w+)>\w+</\1>","<h1>hello</h1>")
print(ret.group(1))
print(ret.group())
```
# shutil 模块
`shutil` 是 Python 的标准库之一,提供了许多高级文件操作,例如复制和移动文件,以及创建和提取压缩文件
可以理解为高级的文件、文件夹、压缩包处理模块
## 常用方法
### 拷贝内容
```python
import shutil
# 拷贝内容
shutil.copyfileobj(open('a.txt','r'),open('a.txt.new','w'))
# 拷贝文件
shutil.copyfile('file.txt','file1.txt')
# 拷贝文件信息
shutil.copystat('file.txt','file1.txt')
# 移动文件或目录
shutil.move(src_path, dst_path)
# 删除整个目录树
shutil.rmtree(directory_path)
# 删除单个文件
shutil.remove(file_path)
# 创建单个目录
shutil.mkdir(directory_path)
# 创建嵌套目录
shutil.makedirs(directory_path)
```
### 压缩和解压缩文件
调用 ZipFile 和 TarFile 两个模块来进行
```python
import zipfile
# 压缩
z = zipfile.ZipFile('ab.zip', 'w')
z.write('a.txt')
z.write('b.txt')
z.close()
# 解压
z = zipfile.ZipFile('ab.zip', 'r')
z.extractall(path=r'C:\Users\Atopos\Desktop')
z.close()
```
```python
import tarfile
# 压缩文件
t = tarfile.open('/tmp/egon.tar','w')
t.add('/test1/a.py',arcname='a.bak')
t.add('/test1/b.py',arcname='b.bak')
t.close()
# 解压缩文件
t = tarfile.open('/tmp/egon.tar','r')
t.extractall('/egon')
t.close()
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@@ -0,0 +1,286 @@
# 异常和错误
## 语法错误
python 解释器的语法检测不通过,必须在程序执行前就改正。
```python
#语法错误示范一
if
#语法错误示范二
def test:
pass
#语法错误示范三
print(haha
```
## 逻辑错误
```python
#用户输入不完整(比如输入为空)或者输入非法(输入不是数字)
num=input(">>: ")
res1 = int(num)
#无法完成计算
res1=1/0
res2=1+'str'
```
## 异常
异常就是程序运行时发生错误的信号,然后程序异常退出。
![img](异常处理/程序异常.png)
## 异常种类
在 python 中不同的异常可以用不同的类型(类)去标识,不同的类对象标识不同的异常,一个异常标识一种错误。
```python
# 触发IndexError
l=['eagle','aa']
l[3]
# 触发KeyError
dic={'name':'eagle'}
dic['age']
# 触发ValueError
s='hello'
int(s)
```
**常见异常**
| 异常类型 | 说明
| :---------------- | :----------------------------------------------------------- |
| AttributeError | 试图访问一个对象没有的属性,比如 foo.x但是 foo 没有属性 x |
| IOError | 输入/输出异常;基本上是无法打开文件 |
| ImportError | 无法引入模块或包;基本上是路径问题或名称错误 |
| IndentationError | 语法错误(的子类) ;代码没有正确对齐 |
| IndexError | 下标索引超出序列边界比如当x只有三个元素却试图访问 x[5] |
| KeyError | 试图访问字典里不存在的键 |
| KeyboardInterrupt | Ctrl + C 被按下 |
| NameError | 使用一个还未被赋予对象的变量 |
| SyntaxError | Python 代码非法,代码不能编译 |
| TypeError | 传入对象类型与要求的不符合 |
| UnboundLocalError | 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,导致你以为正在访问它 |
| ValueError | 传入一个调用者不期望的值,即使值的类型是正确的 |
**其他异常**
| 异常类型 | 说明 | 常见使用场景 |
|:--|:--|:--|
| ArithmeticError | 所有数值计算错误的基类 | 数学运算出现异常时的通用捕获 |
| AssertionError | 断言语句assert失败 | 程序内部的条件检查失败时 |
| AttributeError | 尝试访问对象不存在的属性 | 使用未定义的对象属性或方法时 |
| BaseException | 所有内置异常的基类 | 通常不直接使用,用于自定义异常继承 |
| BufferError | 与缓冲区相关的操作错误 | 在使用缓冲区对象时出现错误 |
| BytesWarning | 字节相关的警告 | 字节操作的潜在问题警告 |
| DeprecationWarning | 关于使用已弃用功能的警告 | 使用即将被移除的特性时 |
| EnvironmentError | 操作系统错误的基类 | 系统相关操作异常的通用捕获 |
| EOFError | 到达文件末尾,无法读取 | 文件读取操作遇到意外的EOF |
| Exception | 常规错误的基类 | 捕获所有非系统退出的异常 |
| FloatingPointError | 浮点计算错误 | 浮点数运算出现特殊情况 |
| FutureWarning | 关于未来特性改变的警告 | 代码可能在未来版本不兼容 |
| GeneratorExit | 生成器被关闭 | 生成器的 close() 方法被调用 |
| ImportError | 导入模块失败 | 模块导入路径错误或模块不存在 |
| ImportWarning | 导入模块时的警告 | 模块导入可能存在问题 |
| IndentationError | 缩进错误 | Python 代码缩进不正确 |
| IndexError | 序列中没有此索引 | 访问列表等序列的越界索引 |
| IOError | 输入/输出操作失败 | 文件操作、网络请求等 IO 操作失败 |
| KeyboardInterrupt | 用户中断执行 | 程序被 Ctrl+C 中断 |
| KeyError | 映射中没有这个键 | 字典中不存在的键访问 |
| MemoryError | 内存溢出 | 程序耗尽可用内存 |
| NameError | 未声明/初始化对象 | 使用未定义的变量 |
| NotImplementedError | 尚未实现的方法 | 抽象基类方法需要子类实现 |
| OSError | 操作系统相关的错误 | 文件权限、系统调用等错误 |
| OverflowError | 数值运算超出最大限制 | 数值计算结果超出表示范围 |
| ReferenceError | 弱引用试图访问已经垃圾回收了的对象 | 使用弱引用时的对象访问 |
| RuntimeError | 一般的运行时错误 | 不适合其他类别的运行时错误 |
| RuntimeWarning | 可疑的运行时行为警告 | 运行时可能存在的问题警告 |
| StopIteration | 迭代器没有更多的值 | 迭代器遍历完成 |
| SyntaxError | Python 语法错误 | 代码语法错误,无法编译 |
| SyntaxWarning | 可疑的语法警告 | 语法虽然正确但可能存在问题 |
| SystemError | 一般的解释器系统错误 | Python 解释器内部错误 |
| SystemExit | 解释器请求退出 | 调用 `sys.exit()` 请求退出程序 |
| TabError | Tab和空格混用 | 混合使用 Tab 和空格导致的错误 |
| TypeError | 对类型无效的操作 | 操作或函数应用于不适当类型 |
| UnboundLocalError | 访问未初始化的本地变量 | 在赋值前引用局部变量 |
| UnicodeError | Unicode 相关的错误 | Unicode编码/解码错误的基类 |
| UnicodeDecodeError | Unicode 解码错误 | 无法将字节解码为 Unicode |
| UnicodeEncodeError | Unicode 编码错误 | 无法将 Unicode 编码为字节 |
| UnicodeTranslateError | Unicode 转换错误 | Unicode 转换过程中的错误 |
| UnicodeWarning | Unicode 相关警告 | Unicode 操作的潜在问题警告 |
| UserWarning | 用户代码生成的警告 | 用户自定义的警告信息 |
| ValueError | 传入无效的参数 | 值虽然类型正确但不合适 |
| Warning | 警告的基类 | 所有警告类型的基类 |
| ZeroDivisionError | 除数为零 | 数值除以零或取模零
# 异常处理
- python 解释器检测到错误,触发异常(也允许程序员自己触发异常)
- 程序员编写特定的代码,专门用来捕捉这个异常(这段代码与程序逻辑无关,与异常处理有关)
- 如果捕捉成功则进入另外一个处理分支,执行你为其定制的逻辑,使程序不会崩溃,这就是异常处理
- 异常是由程序的错误引起的,语法上的错误跟异常处理无关,必须在程序运行前就修正
```python
# 错误写法:
num1 = input('>>: ')
if num1.isdigit():
int(num1)
elif num1.isspace():
print('输入的是空格,就执行我这里的逻辑')
elif len(num1) == 0:
print('输入的是空,就执行我这里的逻辑')
else:
print('其他情情况,执行我这里的逻辑')
'''
问题:
1. 使用 if 的方式我们只为第一段代码加上了异常处理,但这些 if跟你的代码逻辑并无关系这样你的代码会因为可读性差而不容易被看懂
2. 这只是我们代码中的一个小逻辑,如果类似的逻辑多,那么每一次都需要判断这些内容,就会我们的代码特别冗长。
'''
# 正确写法:
try:
num = input("<<:")
int(num)
except:
print('你输入的是非数字')
finally:
print('程序结束')
```
## 基本语法
```python
try:
被检测的代码块
except 异常类型
try 被检测的代码块就执行这个位置的逻辑
...
else:
正常执行逻辑
finally:
扫尾工作
```
## 常见示例
**示例**:处理迭代器获取时 `StopIteration` 异常
```python
iterator01 = ( i** 2 for i in range(3) )
while True:
try:
print(iterator01.__next__())
except StopIteration as e:
print(e)
break
```
**示例**:处理数据类型转换时 `ValueError` 异常
```python
s1 = 'hello'
try:
int(s1)
except ValueError as e:
print(e)
# Output:
invalid literal for int() with base 10: 'hello'
```
**示例**:多分支异常处理
```python
s1 = 'hello'
try:
int(s1)
except IndexError as e:
print(e)
except KeyError as e:
print(e)
except ValueError as e:
print(e)
```
**Exception**
```python
s1 = 'hello'
try:
int(s1)
except Exception as e:
print(e)
```
多分支加万能异常
```python
s1 = 'hello'
try:
int(s1)
except IndexError as e:
print(e)
except KeyError as e:
print(e)
except ValueError as e:
print(e)
except Exception as e:
print(e)
```
**其他异常情况**
```python
s1 = '10'
try:
int(s1)
except IndexError as e:
print(e)
except KeyError as e:
print(e)
except ValueError as e:
print(e)
except Exception as e:
print(e)
else:
print('try内代码块没有异常则执行我')
finally:
print('无论异常与否,都会执行该模块,通常是进行清理工作')
```
## 主动触发异常
```python
try:
raise TypeError('类型错误')
except Exception as e:
print(e)
```
## 自定义异常
```python
class EvaException(BaseException):
def __init__(self,msg):
self.msg=msg
def __str__(self):
return self.msg
try:
raise EvaException('类型错误')
except EvaException as e:
print(e)
```
**总结**:代码更健壮,更易组织,更清晰。

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

View File

@@ -0,0 +1,311 @@
# 模块
**模块module** 是一个包含 Python 代码的文件,通常以 `.py` 结尾。模块可以包含变量、函数、类,甚至其他模块。通过模块,我们可以将代码组织成不同的文件,更好地管理和复用代码。
模块可以分为以下几类:
- **自定义模块**:用户自己编写的模块。
- **标准库模块**Python 自带的模块,如 `math``os``sys` 等。
- **第三方模块**:由其他开发者编写的模块,通常可以通过 `pip` 安装。
# 自定义模块
自定义模块是 Python 中一个重要的代码组织方式,它允许开发者将相关的代码(包括函数、类和变量)封装到一个 `.py` 文件中。通过自定义模块,我们可以实现代码的重用和模块化开发,避免代码重复,提高代码的可维护性。自定义模块可以被其他 Python 程序通过 import 语句导入使用,每个模块都有自己独立的命名空间,这样可以避免不同模块之间的命名冲突。
**示例1自定义模块**
```python
# my_module.py
print("This is my moudle....")
def greet(name):
return f"Hello, {name}!"
def add(a, b):
return a + b
PI = 3.14159
# test.py
import my_module
# 使用 my_module 中的函数和变量
print(my_module.PI)
print(my_module.greet("EaglesLab"))
print(my_module.add(5, 3))
# 模块别名
import my_module as mm
print(mm.greet("EaglesLab"))
```
**示例2根据用户输入导入不同自定义模块**
```python
# mysql.py
def sqlparse():
print('from mysql sqlparse')
# oracle.py
def sqlparse():
print('from oracle sqlparse')
# test.py
db_type = input('>>: ')
if db_type == 'mysql':
import mysql as db
elif db_type == 'oracle':
import oracle as db
db.sqlparse()
```
# 模块导入
```python
# 导入多个模块1
import sys, os, re
# 导入多个模块2
import sys
import os
import re
# 通过 from 导入模块
# from filename import function
from my_module import greet, add
# 覆盖导入模块中的同名函数
def add():
pass
# 导入所有不是以下划线(_)开头的函数,可读性较差不推荐
from my_module import *
# 设定 `from my_module import *` 导入哪些模块
## my_module.py
__all__ = ['greet','add']
```
python 全局变量 `__name__`,用来控制 `.py` 文件在不同的应用场景下执行不同的逻辑
- 当文件被当做脚本执行时:`__name__ 等于'__main__'`
- 当文件被当做模块导入时:`__name__等于模块名`
```python
def fib(n):
a, b = 0, 1
while b < n:
print(b, end=',')
a, b = b, a+b
print()
if __name__ == "__main__":
print(__name__)
num = input('num :')
fib(int(num))
print(globals())
```
# 模块的搜索路径
Python 在导入模块时会按照一定的顺序搜索模块所在的位置。主要的搜索路径包括:
1. **当前目录**Python 首先在当前目录下查找要导入的模块
2. **PYTHONPATH 环境变量**:包含一系列目录名,可以通过设置此环境变量来添加额外的模块搜索路径
3. **标准库目录**Python 安装时自带的标准库所在的目录
4. **site-packages 目录**:第三方模块安装的默认位置
可以通过以下方式查看和修改模块的搜索路径:
```python
import sys
# 查看当前的模块搜索路径
print(sys.path)
# 添加自定义搜索路径
sys.path.append('/path/to/your/modules')
# 在搜索路径开头添加目录(优先级更高)
sys.path.insert(0, '/path/to/your/modules')
```
**设置PYTHONPATH环境变量**
```bash
# Linux/macOS
export PYTHONPATH=/path/to/your/modules:$PYTHONPATH
# Windows
set PYTHONPATH=C:\path\to\your\modules;%PYTHONPATH%
```
注意事项:
1. 不建议在代码中直接修改 `sys.path`,最好通过环境变量或安装包的方式管理模块路径
2. 搜索路径的优先级按照 `sys.path` 列表中的顺序,越靠前优先级越高
3. 在导入模块时,一旦在某个路径下找到了对应的模块,就会停止继续搜索
# 编译python文件
为了提高加载模块的速度python 解释器会在`__pycache__`目录中下缓存每个模块编译后的版本格式为module.version.pyc。通常会包含 python 版本号。例如,在 CPython3.3 版本下my_module.py 模块会被缓存成`__pycache__/my_module.cpython-33.pyc`。这种命名规范保证了编译后的结果多版本共存。
# 包
Python 包Package是一个包含 `__init__.py` 文件的目录它用于组织和管理相关的Python模块。
包的主要作用是提供一种命名空间的层次结构使得大型项目中的模块组织更加清晰。在Python3中虽然__init__.py文件不是必需的但为了保持兼容性和明确目录是一个包建议始终创建这个文件。包可以包含子包和模块通过包的层次结构可以避免命名冲突提高代码的可维护性和重用性。
## 包的使用
示例文件
```python
glance/ # Top-level package
├── __init__.py # Initialize the glance package
├── api # Subpackage for api
├── __init__.py
├── policy.py
└── versions.py
├── cmd # Subpackage for cmd
├── __init__.py
└── manage.py
└── db # Subpackage for db
├── __init__.py
└── models.py
```
文件内容
```python
#文件内容
# policy.py
def get():
print('from policy.py')
# versions.py
def create_resource(conf):
print('from version.py: ',conf)
# manage.py
def main():
print('from manage.py')
# models.py
def register_models(engine):
print('from models.py: ',engine)
```
## 使用 import 导入包
```python
import glance.db.models
glance.db.models.register_models('mysql')
```
单独导入包名称时不会导入包中所有包含的所有子模块
```python
import glance
# 在导入glance的时候会执行glance下的__init__.py中的代码
glance.cmd.manage.main()
```
解决方法
```python
# glance/__init__.py
from . import cmd
# glance/cmd/__init__.py
from . import manage
```
## 使用 from 导入包
需要注意的是 from 后 import 导入的模块,必须是明确的一个不能带点,否则会有语法错误,如:`from a import b.c`是错误语法
```python
from glance.db import models
from glance.db.models import register_models
models.register_models('mysql')
register_models('mysql')
```
`from glance.api import *`
想从包api中导入所有实际上该语句只会导入包api下 `__init__.py` 文件中定义的名字,我们可以在这个文件中定义`__all__`
```python
x = 10
def func():
print('from api.__init.py')
__all__= ['x','func','policy']
```
```python
from glance.api import *
func()
print(x)
policy.get()
```
# 绝对导入和相对导入
**绝对导入**:以执行文件的 sys.path 为起始点开始导入。
1. 优点:执行文件与被导入的模块中都可以使用
2. 缺点:所有导入都是以 sys.path 为起始点,导入麻烦
**相对导入**:参照当前所在文件的文件夹为起始开始查找,称之为相对导入
1. 符号:`.`代表当前所在文件的文件加,`..`代表上一级文件夹,`...`代表上一级的上一级文件夹
2. 优点:导入更加简单
3. 缺点:只能在导入包中的模块时才能使用
# 注意事项
1. **包的命名规范**
- 包名应该简短、描述性强,全小写字母
- 避免使用Python保留字和标准库模块名
- 如果包名包含多个单词,建议使用下划线连接
2. **循环导入问题**
- 避免模块之间的相互导入,这可能导致导入死锁
- 如果必须相互引用,可以考虑以下解决方案:
- 将导入语句移到函数内部(延迟导入)
- 重构代码结构,消除循环依赖
- 使用依赖注入模式
3. **包的初始化顺序**
- `__init__.py` 文件在导入包时首先执行
- 避免在 `__init__.py` 中放置过多代码,保持简洁
- 初始化代码应该是幂等的(多次执行结果相同)
4. **版本兼容性**
- 明确指定包的Python版本要求
- 使用条件导入处理不同Python版本的特性
-`setup.py` 中声明依赖包的版本范围
5. **包的安装和分发**
- 创建 `setup.py``pyproject.toml` 文件
- 包含必要的元数据(作者、版本、依赖等)
- 使用虚拟环境进行开发和测试
- 确保包的文档和示例代码完整
6. **导入优化**
- 避免使用 `from module import *`,这会污染命名空间
- 将频繁使用的模块导入放在文件顶部
- 使用相对导入(.)时要注意包的层级关系
7. **错误处理**
- 在导入时使用 try-except 处理可能的导入错误
- 为可选功能提供优雅的降级方案
- 提供清晰的错误信息和解决建议

View File

@@ -0,0 +1,349 @@
# Python 装饰器
在 Python 中,装饰器是一个非常强大的功能,可以在不修改函数代码的情况下,动态地修改函数/方法的行为。装饰器本质上是一个函数,它接受一个函数作为参数并返回一个新的函数。
应用场景:比如插入日志,性能测试,事务处理,缓存等等场景。
## 案例切入
已知有一个函数 `func1()`,作用是输出一句话。现在我们想要给他增加额外的功能。但是为了保障已有功能的稳定,不能更改原函数。
```python
def func1():
print("in func1")
# 新的需求,能够打印如下内容...
# hello world
# in func1
# hello python
# 实现
def func2(func):
def inner():
print("hello world")
func()
print("hello python")
return inner
func1 = func2(func1)
func1()
```
## 装饰器形成的过程
如果我想测试某个函数的执行时间
```python
import time
def func1():
print('in func1')
def timer(func):
def inner():
start = time.time()
func()
print(time.time() - start)
return inner
func1 = timer(func1) # 将函数本身做为参数传递进去
func1()
```
如果有很多个函数都需要测试它们的执行时间,每次都需要 `func1 = timer(func1)` 是比较麻烦的,而且不利于代码的可读性和后期维护。这里我们可以使用 python 中的一种特殊的语法结构**语法糖**,来更为简便的使用装饰器。
我们将上述代码修改如下:
```python
import time
def timer(func):
def inner():
start = time.time()
func()
print(time.time() - start)
return inner
@timer
"""
当我们在某个函数上方使用 @my_decorator 的时候python 会自动将下面定义的函数做为参数传递给 my_decorator。
func1 = timer(func1)
"""
def func1():
time.sleep(1)
print('in func1')
func1()
```
## 装饰带参数的函数
装饰一个带参数的函数与装饰一个不带参数的函数类似,但需要在装饰器中处理传递给被装饰函数的参数。
**示例:**
```python
import time
def timer(func):
def inner(a):
start = time.time()
func(a)
print(time.time() - start)
return inner
@timer
"""
func1 = timer(func1)
"""
def func1(a):
time.sleep(1)
print(a)
func1('hello world')
```
## 装饰带多个参数的函数
这里我们利用了函数里面的动态参数进行传参
```python
def my_decorator(func):
def wrapper(*args, **kwargs):
# 打印传入的参数
print(f"调用 {func.__name__} 函数,参数: {args}, 关键字参数: {kwargs}")
# 调用原始函数并获取结果
result = func(*args, **kwargs)
# 打印返回结果
print(f"{func.__name__} 函数返回: {result}")
return result
return wrapper
@my_decorator
def add(x, y):
"""返回两个数的和"""
return x + y
# 测试
result = add(5, 3)
print(f"最终结果: {result}")
```
# wraps装饰器
回到我们最开始的案例
```python
import time
def func1():
print('in func1')
def timer(func):
def inner():
start = time.time()
func()
print(time.time() - start)
return inner
func1 = timer(func1) # 将函数本身做为参数传递进去
func1()
```
思考一个问题:这里虽然我们最后还是执行 `func1` 函数,但是这里的 `func1` 函数还是我们最初的`func1`函数吗?
......
我们先来看一下最后的 `func1` 他的函数名
```python
import time
def func1():
print('in func1')
def timer(func):
def inner():
start = time.time()
func()
print(time.time() - start)
return inner
func1 = timer(func1) # 将函数本身做为参数传递进去
print(func1.__name__) # 查看函数的名称
```
## 导入wraps装饰器
`wraps` 装饰器,用于帮助创建装饰器时保留被装饰函数的元数据(如名称、文档字符串等)。使用 `@wraps` 可以确保装饰后的函数看起来像原始函数,这样有助于调试和文档生成。
我们将上方的案例使用 wraps 装饰器装饰
```python
from functools import wraps
import time
def func1():
print('in func1')
def timer(func):
@wraps(func)
def inner():
start = time.time()
func()
print(time.time() - start)
return inner
func1 = timer(func1) # 将函数本身做为参数传递进去
print(func1.__name__) # 查看函数的名称
```
# 带参数的装饰器
带参数的装饰器允许你在装饰器中接受参数,从而增强装饰器的灵活性和功能性。实现带参数的装饰器通常需要使用嵌套函数。
我们将创建一个装饰器,它接受一个参数,用于指定**是否打印函数的执行时间**。
```python
import time
from functools import wraps
def timing_decorator(print_time=True):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time() # 记录开始时间
result = func(*args, **kwargs) # 调用原始函数
end_time = time.time() # 记录结束时间
if print_time:
execution_time = end_time - start_time
print(f"{func.__name__} 执行时间: {execution_time:.4f}秒")
return result
return wrapper
return decorator
@timing_decorator(print_time=True)
"""
add = timing_decorator(print_time=True)(add)
"""
def add(x, y):
"""返回两个数的和"""
time.sleep(1) # 模拟耗时操作
return x + y
@timing_decorator(print_time=False)
def multiply(x, y):
"""返回两个数的积"""
time.sleep(1) # 模拟耗时操作
return x * y
# 测试
result_add = add(5, 3)
print(f"加法结果: {result_add}")
result_multiply = multiply(5, 3)
print(f"乘法结果: {result_multiply}")
```
# 多个装饰器装饰一个函数
可以将多个装饰器应用于同一个函数。这种情况下,装饰器会按照从内到外的顺序依次应用。
```python
def wrapper1(func):
def inner1():
print('第一个装饰器,在程序运行之前')
func()
print('第一个装饰器,在程序运行之后')
return inner1
def wrapper2(func):
def inner2():
print('第二个装饰器,在程序运行之前')
func()
print('第二个装饰器,在程序运行之后')
return inner2
@wrapper1
@wrapper2
def f():
print('Hello')
f()
"""
执行过程分析:
1. f = wrapper2(f) -> func = f, return inner2
2. f = wrapper1(f) -> func = inner2, return inner1
3. f() = inner1() -> inner2() -> f() -> inner2() -> inner1()
"""
```
## 示例:多个装饰器
创建两个装饰器,一个用于打印函数的执行时间,另一个用于打印调用的参数。
```python
import time
from functools import wraps
# 装饰器 1打印执行时间
def timing_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
execution_time = end_time - start_time
print(f"{func.__name__} 执行时间: {execution_time:.4f}秒")
return result
return wrapper
# 装饰器 2打印函数参数
def logging_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__} 函数,参数: {args}, 关键字参数: {kwargs}")
return func(*args, **kwargs)
return wrapper
@timing_decorator
@logging_decorator
def add(x, y):
"""返回两个数的和"""
time.sleep(1) # 模拟耗时操作
return x + y
# 测试
result = add(5, 3)
print(f"加法结果: {result}")
```
# 装饰器的固定结构
```python
from functools import wraps
def decorator_with_args(param):
def actual_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"装饰器参数: {param}")
return func(*args, **kwargs)
return wrapper
return actual_decorator
@decorator_with_args("配置参数")
def my_function():
pass
```

View File

@@ -0,0 +1,606 @@
# 函数
在 Python 中,函数是一段组织好的、可重复使用的代码,用于执行特定的任务。函数可以接受输入(称为参数),并可以返回输出(称为返回值)。使用函数可以帮助我们组织代码,提高可读性和重用性。
# Python函数式编程
## 函数定义
在 Python 中,函数的定义使用 `def` 关键字,后面跟函数名和参数列表。函数体由缩进的代码块组成。
**语法**
```python
def function_name(parameters):
"""函数的文档字符串(可选)"""
# 函数体
# 执行任务的代码
```
## 函数调用
调用函数时,需要使用函数名后跟括号,并传入必要的参数。
## 函数返回值
函数可以使用 `return` 语句拥有返回值。如果没有 `return`,函数默认返回 `None`
**return 关键字的作用**
- return 是一个关键字, return 后面的值叫“返回值”
- 不写 return 的情况下,会默认返回一个 None
- 一旦遇到 return结束整个函数
- 返回多个值会被组织成元组被返回,也可以用多个值来接收
- 使用多个值接收(值的数量必须跟返回值数量对应)
**示例**
```python
# 1. 无返回值的函数默认返回None
def greet():
print("Hello!")
result = greet()
print(f"无返回值函数的返回值: {result}") # 输出: None
# 2. 单返回值的函数
def calculate_square(number):
return number ** 2
square = calculate_square(5)
print(f"5的平方是: {square}") # 输出: 25
# 3. 多返回值的函数
def get_user_info():
name = "张三"
age = 25
city = "北京"
return name, age, city
# 使用多个变量接收返回值
user_name, user_age, user_city = get_user_info()
print(f"用户信息: {user_name}, {user_age}岁, 来自{user_city}")
# 使用单个变量接收(返回值会被组织成元组)
user_info = get_user_info()
print(f"用户信息(元组): {user_info}")
# 4. 条件返回的函数
def check_number(num):
if num > 0:
return "正数"
elif num < 0:
return "负数"
else:
return "零"
print(check_number(10)) # 输出: 正数
print(check_number(-5)) # 输出: 负数
print(check_number(0)) # 输出: 零
```
## 函数参数
在 Python 中函数的参数是用于向函数传递输入数据的变量。通过参数函数可以根据不同的输入执行不同的操作。Python 支持多种类型的参数,每种参数类型都有其特定的用途和特点。
### 形参
在函数定义时用来占位置的字符,可以是任何字符,用来表示函数需要有一个参数传递进来……
- 形参是在函数定义时指定的参数。它们用于接收函数调用时传递的值。
- 形参在函数体内作为变量存在,作用域仅限于函数内部。
### 实参
在函数调用的时候实际传递进去的参数,是一个具体的值,参与函数内部的处理……
- 实参是在函数调用时传入的实际值。实参可以是常量、变量、表达式或其他函数的返回值。
- 实参的值被传递给形参。
### 位置参数
位置参数是最常用的参数类型,调用时根据位置传递给函数。参数的数量和顺序必须与函数定义时一致。
### 默认参数
默认参数允许为函数的参数设置默认值。调用函数时,如果没有提供该参数的值,则使用默认值。
### 关键字参数
按照形参的参数名进行精确传参,使用关键字参数的时候可以不用考虑形参的具体位置和顺序
**示例:**
```python
# 形参 & 实参
def greet01(name):
print(f"Hello, {name}!")
greet01("EaglesLab")
greet01("英格科技")
# 位置参数
def greet02(name, age):
print(f"Hello, {name}! Age: {age}")
greet01("EaglesLab", 11)
# 默认参数 & 关键字参数 & 可选参数
def greet03(name, site="cloud.eagleslab.com", address="zj"):
print(f"Site: {site}, Address: {address}")
greet03(address="nj", site="ncloud.eagleslab.com", name="EagelsLab")
```
### 动态参数
动态参数是指在函数定义时允许接受可变数量的参数。这种特性使得函数可以处理不同数量的输入而不需要在定义时明确列出所有参数。Python 提供了两种主要方式来实现动态参数:`*args``**kwargs`
`*args` 允许函数接受任意数量的位置参数,这些参数会被收集到一个元组中。
```python
def sum_numbers(*args):
total = sum(args) # args 是一个元组
return total
result = sum_numbers(1, 2, 3, 4, 5) # 可以传入任意数量的参数
print(result) # 输出: 15
```
`**kwargs` 允许函数接受任意数量的关键字参数,这些参数会被收集到一个字典中。
```python
def print_info(**kwargs):
for key, value in kwargs.items(): # kwargs 是一个字典
print(f"{key}: {value}")
print_info(name="EaglesLab", age=10, city="China")
# Output:
name: EaglesLab
age: 30
city: China
```
可以在同一个函数中同时使用 `*args``**kwargs`,但 `*args` 必须在 `**kwargs` 之前。
```python
def display_info(*args, **kwargs):
print("位置参数:", args) # args 是一个元组
print("关键字参数:", kwargs) # kwargs 是一个字典
display_info(1, 2, 3, name="Alice", age=30)
# Output:
位置参数: (1, 2, 3)
关键字参数: {'name': 'Alice', 'age' : 18}
```
## 案例1简单计算器实现
```python
def calculate(operation, num1 = 10.24, num2):
"""简单计算器实现"""
if operation == 'add':
return num1 + num2
elif operation == 'subtract':
return num1 - num2
elif operation == 'multiply':
return num1 * num2
elif operation == 'divide':
if num2 == 0:
return "错误:除数不能为零!"
return num1 / num2
else:
return "错误:未知操作!"
# 用户输入
user_operation = input("请输入操作add, subtract, multiply, divide: ")
user_num1 = float(input("请输入第一个数字: "))
user_num2 = float(input("请输入第二个数字: "))
# 调用函数
result = calculate(user_operation, user_num1, user_num2)
print(f"结果: {result}")
```
## 案例2书籍信息管理
```python
def add_book(book_title, *authors, **details):
"""添加书籍信息并打印"""
print(f"书籍标题: {book_title}")
print("作者:", ", ".join(authors))
for key, value in details.items():
print(f"{key}: {value}")
print("---")
# 添加书籍
add_book("红楼梦", "曹雪芹", "高鹗")
add_book("三国演义", "罗贯中", year=1522)
add_book("水浒传", "施耐庵", "罗贯中", year=1373, publisher="元末明初刻本")
add_book("西游记", "吴承恩", year=1592, publisher="明代世德堂刻本", genre="神魔小说")
```
# 命名空间和作用域
在 Python 中,**命名空间**Namespace和**作用域**Scope是两个重要的概念它们帮助我们理解变量的可见性和生命周期。下面将详细解释这两个概念并展示它们之间的关系。
## 命名空间
命名空间是一个容器,用于存储变量名(或标识符)与对象(值)之间的映射关系。命名空间确保了变量名的唯一性,不同的命名空间可以包含同名的变量而不会产生冲突。
### 类型
- **内置命名空间**:包含 Python 内置的函数和对象,如 `len()``print()` 等。
- **全局命名空间**:模块级别的命名空间,包含模块中定义的变量和函数。
- **局部命名空间**:函数内部的命名空间,包含函数内部定义的变量。
### 示例
```python
def my_function():
x = 10 # 局部命名空间中的 x
print("局部 x:", x)
x = 5 # 全局命名空间中的 x
my_function()
print("全局 x:", x)
```
## 作用域
作用域定义了变量的可访问性或可见性。它决定了在程序的某一部分可以访问哪些变量。
### 类型
- **局部作用域**:函数内部定义的变量,仅在函数内部可见。
- **全局作用域**:模块级别定义的变量,在整个模块内可见。
- **内置作用域** Python 内置的名字,始终可用。
### 示例
```python
def outer_function():
outer_var = "我是外部变量"
def inner_function():
inner_var = "我是内部变量"
print(inner_var) # 访问内部变量
print(outer_var) # 访问外部变量
inner_function()
# print(inner_var) # 会导致错误,因为 inner_var 在外部不可见
outer_function()
# print(outer_var) # 会导致错误,因为 outer_var 在外部不可见
```
## 命名空间与作用域的关系
- **命名空间**提供了一个上下文,使得变量名可以在不同的上下文中存在,而不会发生冲突。
- **作用域**决定了这些命名空间的可见性和可访问性。
## globals 和 locals 方法
`globals()``locals()` 是两个内置函数,用于访问全局命名空间和当前所在的命名空间。
```python
x = 10
y = 20
print(globals())
print(locals())
def func():
a = 12
b = 20
print(globals())
print(locals())
func()
```
区别在于`globals()`不管再什么地方,都是访问全局命名空间的变量,而`locals()`是访问当前(所在命名空间)的变量
## global 和 nonlocal 关键字
`global` 关键字用于在函数内部声明一个变量是全局变量,从而允许你在函数内部对其进行修改。
```python
x = 10 # 全局变量
def modify_global():
global x # 声明 x 为全局变量
x = 20 # 修改全局变量
modify_global()
print(x) # 输出: 20
```
如果不使用 `global`,在函数内部直接赋值会创建一个新的局部变量,而不会影响全局变量。
```python
def try_modify():
x = 30 # 创建一个局部变量 x未声明为 global
print(x) # 输出: 30
try_modify()
print(x) # 输出: 20仍然是全局变量的值
```
对可变数据类型listdictset可以直接引用不用通过 global
```python
li = [1,2,3]
dic = {'name':'aaron'}
def change():
li.append(4)
dic['age'] = 18
print(dic)
print(li)
change()
print(dic)
print(li)
```
`nonlocal` 关键字用于在嵌套函数中声明一个变量是外层函数的局部变量。它允许内层函数修改外层函数的变量。
在局部作用域中,对父级作用域(或者更外层作用域非全局作用域)的变量进行引用和修改,并且引用的哪层,从那层及以下此变量全部发生改变。
```python
def outer_function():
x = 10 # 外层函数的局部变量
def inner_function():
nonlocal x # 声明 x 为外层函数的局部变量
x = 20 # 修改外层函数的变量
inner_function()
print(x) # 输出: 20
outer_function()
```
**注意:** `nonlocal` 仅在嵌套函数中有效,不能用于声明全局变量。
# 函数的嵌套和作用域链
**函数的嵌套**是指在一个函数内部定义另一个函数。内层函数可以访问外层函数的变量,这种结构在 Python 中非常常见。
## 嵌套声明
```python
def f1():
print('in f1...')
def f2():
print('in f2...')
f2()
f1()
```
## 嵌套调用
嵌套调用指的是在一个函数内部调用另一个函数,从而使用另一个函数的功能
```python
# 先写一个两个数字比较大小并且返回较大数字的函数
def max_num(x,y):
if x > y:
return x
else:
return y
def number(a,b,c,d): # 有四个数据比较通过多次调用max_num找出最大的数字
res1 = max_num(a,b)
res2 = max_num(res1,c)
res3 = max_num(res2,d)
return res3
ret = number(10,200,-20,40)
print(ret)
# Output:
200
```
## 作用域链
**作用域链**是指在查找变量时Python 按照一定的顺序查找变量的规则。它遵循 LEGB 规则:
1. **L**ocal (局部作用域):当前函数内定义的变量。
2. **E**nclosing (包围作用域):外层函数中的局部变量。
3. **G**lobal (全局作用域):模块级别定义的变量。
4. **B**uilt-in (内置作用域)Python 内置的名字,如 `len()``print()` 等。
```python
x = "全局变量"
def outer_function():
x = "外层变量"
def inner_function():
x = "内层变量"
print("内层函数中的 x:", x) # 访问内层变量
inner_function()
print("外层函数中的 x:", x) # 访问外层变量
outer_function()
print("全局变量 x:", x) # 访问全局变量
# Output:
内层函数中的 x: 内层变量
外层函数中的 x: 外层变量
全局变量 x: 全局变量
```
# 函数名的本质
**函数名**的本质可以理解为一个指向函数对象的引用。也可以理解为该函数的**内存地址**
**函数名**实际上是一个标签,用来指向函数对象。你可以用这个标签来调用函数,但它本身并不是函数的本体。所以可以被赋值给变量、作为参数传递给其他函数,以及从函数中返回。
- 可以被赋值给变量
```python
def greet():
return "Hello, World!"
# 将函数赋值给变量
greeting = greet
print(greeting())
```
- 可以被当作容器类型的元素
```python
def f1():
print('f1')
def f2():
print('f2')
def f3():
print('f3')
l = [f1,f2,f3]
d = {'f1':f1,'f2':f2,'f3':f3}
#调用
l[0]()
d['f2']()
```
- 可以当作函数的参数和返回值
```python
def f1():
print("in f1")
def func(argv):
argv()
return argv
f = func(f1)
f()
```
# 闭包
内部函数包含对外部作用域而非全剧作用域变量的引用,该内部函数称为闭包函数
## 闭包的特点
1. **嵌套函数**:闭包函数通常是在另一个函数内部定义的。
2. **持有外部变量**:闭包可以访问其外部函数的局部变量,即使外部函数已经执行完毕。
3. **状态保持**:闭包允许你在多个函数调用之间保持状态。
```python
def func():
name = '张三'
def inner():
print(name)
return inner
f = func()
f()
```
下面是一个简单的闭包函数示例,演示如何使用闭包来保持一个计数器的状态。
```python
def make_counter():
count = 0 # 外部变量
def counter(): # 嵌套函数
nonlocal count # 声明使用外层变量
count += 1
return count
return counter # 返回嵌套函数
# 创建一个计数器
my_counter = make_counter()
# 测试计数器
print(my_counter()) # 输出: 1
print(my_counter()) # 输出: 2
print(my_counter()) # 输出: 3
```
## 判断闭包函数
判断闭包函数的方法 `__closure__` 或者查看函数的 `__code__.co_freevars` 属性
```python
def func():
name = 'EaglesLab'
def inner():
print(name)
return inner
f = func()
print(f.__code__.co_freevars) # 可以通过f.__code__.co_freevars属性来查看到该函数是否应用了外部作用域的变量
print(f.__closure__) # 如果打印出的内容非空,说明该函数是闭包函数
name = 'EaglesLab'
def func():
def inner():
print(name)
return inner
f = func()
print(f.__code__.co_freevars)
print(f.__closure__) # 如果答应出的内容是None说明该函数没有应用外部作用域的变量不满足闭包函数的条件
```
## 案例:获取网页内容
假定我们需要多次访问一个静态网站,但是由于该网站访问较慢,所以导致每次访问都需要等待,比较浪费时间。这里我们可以使用闭包函数将第一次访问到的网页内容封装到外部作用域的变量中,以后我们只需要调用该变量就可以了...
**普通写法:**
```python
from urllib.request import urlopen
def func():
content = urlopen('https://myip.ipip.net').read().decode('utf-8')
print(content)
for i in range(5):
func()
# Output:
当前 IP114.229.63.127 来自于中国 江苏 镇江 电信
当前 IP114.229.63.127 来自于中国 江苏 镇江 电信
当前 IP114.229.63.127 来自于中国 江苏 镇江 电信
当前 IP114.229.63.127 来自于中国 江苏 镇江 电信
当前 IP114.229.63.127 来自于中国 江苏 镇江 电信
```
会发现每循环一次,都会去访问一下`https://myip.ipip.net`这个网站来获取IP地址。
**如果用闭包的方式改写如下**
```python
from urllib.request import urlopen
def func():
content = urlopen('https://myip.ipip.net').read().decode('utf-8')
def inner():
print(content)
return inner
f = func()
for i in range(5):
f()
print(f.__code__.co_freevars)
```
# 课后作业
将之前写过的用户登录注册的功能用函数的方式呈现

View File

@@ -0,0 +1,232 @@
# 可迭代对象
## 迭代
使用 `for i in xxx` 对字符串、列表、元组、字典、集合进行循环取值的过程称为遍历,也叫做迭代。
```python
li = [1, 2, 3, 'a', 'b', 'c']
for i in li:
print(i)
str = "hello,world"
for j in str:
print(j)
dic = {'name': 'nls', 'age': 18, 'job': 'teacher'}
for k,v in dic.items():
print(k,v)
```
## 定义
可迭代对象是能够通过迭代逐一返回其元素的对象。它必须满足以下条件之一:
- 实现 `__iter__()` 方法,返回一个可迭代对象
- 实现 `__getitem__()` 方法支持从索引0开始的顺序访问
## 常见可迭代对象类型
## 判断是否为可迭代对象
```python
from collections.abc import Iterable
l = [1, 2, 3, 4]
t = (1, 2, 3, 4)
d = {1: 2, 3: 4}
s = {1, 2, 3, 4}
a = 100
print(isinstance(l, Iterable))
print(isinstance(t, Iterable))
print(isinstance(d, Iterable))
print(isinstance(s, Iterable))
print(isinstance(a, Iterable))
```
# 迭代器
迭代器是一种用于遍历可迭代对象的对象。它可以记录遍历的位置,
可迭代对象可以通过 `__iter__` 方法返回一个迭代器,我们在迭代一个可迭代对象的时候,实际上就是先获取该对象提供的一个迭代器,然后通过这个迭代器来依次获取对象中的每一个数据。
上面讲到迭代是访问集合元素的一种方式。而迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束,迭代器只能往前不会后退。
```python
l = [1, 2, 3, 4]
t = (1, 2, 3, 4)
d = {1: 2, 3: 4}
s = {1, 2, 3, 4}
print(dir(l))
print(dir(t))
print(dir(d))
print(dir(s))
```
## 迭代器的本质
我们分析对可迭代对象进行迭代使用的过程,发现每迭代一次(即在 `for...in...` 中每循环一次)都会返回对象中的下一条数据,一直向后读取数据直到迭代了所有数据后结束。那么,在这个过程中就应该有一个“人”去记录每次访问到了第几条数据,以便每次迭代都可以返回下一条数据。我们把这个能帮助我们进行数据迭代的“人”称为**迭代器(Iterator)**。
## 迭代器协议
迭代器遵循迭代协议,内部主要定义了 `__iter__()``__next__()` 两个方法
- `__iter__()` 方法用于初始化一个迭代器,返回迭代器本身
- `__next__()` 方法用于迭代下一个数据。当没有元素可返回时,抛出 `StopIteration` 异常。
## 初始化迭代器
```python
list1 = [1,2,3,'a','b','c']
list_iter = list1.__iter__() # list 是可迭代对象,这里我们调用 iter 方法初始化一个迭代器l ist_iter
item = list_iter.__next__() # 这里通过 next 方法来获取下一个数据
print(item)
item = list_iter.__next__()
print(item)
item = list_iter.__next__()
print(item)
item = list_iter.__next__()
print(item)
item = list_iter.__next__()
print(item)
item = list_iter.__next__()
print(item)
```
如果超出迭代范围,会触发 **StopIteration** 异常。我们可以加上异常处理,取完值后自动停止
```python
list1 = [1,2,3,'a','b','c']
list_iter = list1.__iter__()
while True:
try:
print(next(list_iter)) # 这里是 next 方法的另一种写法
except StopIteration:
print('迭代完成')
break
```
## 如何判断一个对象是迭代器
我们同样可以用内置的 `isinstance()` 方法来判断某个对象是否是**Iterator 对象(迭代器)**
```python
from collections.abc import Iterator
list1 = [1,2,3,'a','b','c']
list_iter = list1.__iter__()
print(isinstance(list1, Iterator))
print(isinstance(list_iter, Iterator))
print(isinstance(iter(list1), Iterator)) # 初始化迭代器的另一种方法
```
## for 循环的本质
我们常用的 for 循环其实本质上就是迭代器协议的一种具体实现,为我们提供了一个遍历的迭代元素的方法。
**工作原理:**
当你使用 `for` 循环遍历一个可迭代对象时,实际上发生了以下几个步骤:
1. 调用 `__iter__()`
- `for` 循环首先调用对象的 `__iter__()` 方法,获取一个迭代器对象。
2. 调用 `__next__()`
- 然后,`for` 循环在迭代器上反复调用 `__next__()` 方法,以获取下一个元素。
3. 处理 `StopIteration`
- 一旦 `__next__()` 抛出 `StopIteration` 异常,`for` 循环停止迭代。
# 生成器
生成器是 Python 中一种特殊的迭代器,允许你以一种简单而高效的方式生成序列。
## 生成器的原理
状态保持:生成器通过 `yield` 语句保存函数的状态。在每次调用生成器时,函数会从上一个 `yield` 语句的下一行继续执行,而不仅仅是从函数的开始处执行。
迭代器接口:生成器实现了迭代器协议,因此可以使用 `for` 循环进行遍历。
## 生成器函数
生成器也是一个函数,但与普通函数不同的是,它使用 `yield` 关键字来返回值。每次调用生成器函数时,它会从上次 `yield` 的地方继续执行,直到遇到下一个 `yield` 或函数结束。
调用生成器函数不会得到返回的具体的值,而是得到一个迭代器。每一次获取这个迭代器值,就能推动函数的执行,获取新的返回值。直到函数执行结束。
```python
def numbers(n):
"""生成从 1 到 n 的自然数"""
for i in range(1,n+1):
yield i
for i in numbers(10):
print(i)
```
## 惰性求值
生成器在需要时生成值,而不是一次性计算和返回所有值。这可以节省内存,特别是处理大型数据集时。
### 案例:重生之我在早餐的卖包子
我重生了,重生在了高考的前一天,由于上一世我参加了高考最后只能上个大专,毕业了一事无成。这一生,我要成为商业巨头……
一抬头,有一个卖包子的店铺正在转让,我决定从这里开始我的梦幻人生……
言归正传如果卖包子那么我一下子生成100笼包子没地方放的同时还容易坏。我们可不可以等到有顾客下单的时候再去生成
```python
def produce():
# 生产包子
for i in range(1,100):
yield f'生产了第{i}笼包子'
produce_g = produce()
print(produce_g.__next__())
print(produce_g.__next__())
print(produce_g.__next__())
# 顾客下单了需要5笼包子
for i in range(5):
print(produce_g.__next__())
# Output:
生产了第1笼包子
生产了第2笼包子
生产了第3笼包子
生产了第4笼包子
生产了第5笼包子
生产了第6笼包子
生产了第7笼包子
生产了第8笼包子
```
## send
- send 获取下一个值的效果和next基本一致
- 在获取下一个值的时候,给上一个 yield 的位置传递一个数据
- 使用 send 的注意事项
- 第一次使用生成器的时候 是用 nex t获取下一个值
- 最后一个 yield 不能接受外部的值
```python
def generator():
print(123)
content = yield 1
print('欢迎来到',content)
print(456)
yield 2
g = generator()
ret = g.__next__()
print('***',ret)
ret = g.send('英格科技')
print('***',ret)
```

View File

@@ -0,0 +1,443 @@
# 面向过程
面向过程编程Procedural Programming是一种以**​步骤和过程**​​为核心的编程范式,其核心思想是将复杂问题分解为一系列可顺序执行的函数或过程,通过逐步调用来实现整体功能
## 核心思想
- **​步骤分解**​​:将问题拆解为多个子任务,每个子任务由独立的**​​函数/过程​**​实现。例如,处理学生早上的活动可分解为“起床→穿衣→洗漱→去学校”等步骤,每个步骤对应一个函数。
- **顺序执行**​​:程序按代码的书写顺序从上到下执行,通过**​​条件语句​**如if**循环结构**如for控制流程
- **数据与操作分离​**​:数据存储在全局或局部变量中,函数通过参数接收数据并处理,结果通过返回值或修改变量传递。
## 典型特征
- **模块化函数**​​:功能封装为函数,例如计算两数之和的函数 `add()`,通过调用实现代码复用。
- **​​线性流程​**​:程序逻辑清晰,易于调试。例如读取文件数据→处理数据→输出结果的流程。
- **高效性​**​:适用于简单任务或对性能要求高的场景,因无需对象创建开销。
# 面向对象
面向对象编程Object-Oriented ProgrammingOOP是一种以**​对象​**​为核心的编程范式,通过模拟现实世界中事物的交互逻辑来构建程序。其核心思想是将数据与操作数据的方法封装成独立的对象,通过对象之间的协作实现复杂功能
## 核心概念
1. **类Class**:定义对象的模板,描述一类事物的​**​共性特征​**​(如属性)和​**​行为**​​(如方法)。例如,"汽车"类包含属性"颜色"和方法"加速"。
2. **对象Object**:类的具体实例,拥有独立的​**​状态**​​(属性值)和**​​行为**​​。例如,一辆红色汽车是"汽车"类的对象
3. **封装Encapsulation**:将数据和方法捆绑在对象内部,仅通过暴露的接口与外界交互,保护数据安全并简化使用。例如,银行账户的余额只能通过特定方法修改。
4. **继承Inheritance**:子类可复用父类的属性和方法,并扩展新功能,实现代码复用和逻辑分层。例如,"电动车"类继承自"汽车"类,新增"充电"方法。
5. **多态Polymorphism**:同一方法在不同对象中呈现不同行为,增强代码灵活性。例如,"动物"类的"发声"方法在"狗"和"猫"对象中分别输出"汪汪"和"喵喵"。
6. **抽象Abstraction**:提取共性特征形成接口或抽象类,隐藏复杂实现细节,例如定义"图形"类的抽象方法"计算面积"。
## 核心优势
- **​可维护性​**​:对象间低耦合,修改某部分代码不影响整体系统。
- **​可扩展性​**​:通过继承和多态灵活扩展功能,无需重写现有代码。
- **​复用性​**​:封装后的类可跨项目重复使用,减少冗余代码。
- **​逻辑直观**​​:以现实世界模型组织代码,更符合人类认知。
# 两者对比
## 面向过程—怎么做
1. 把完成某一个需求的 `所有步骤` `从头到尾` 逐步实现
2. 根据开发需求,将某些功能独立的代码封装成一个又一个函数
3. 最后完成的代码,就是顺序地调用不同的函数
注重步骤与过程,不注重职责和分工,如果需求比较复杂,虽然有函数封装,但是还是会导致代码比较臃肿。开发起来比较复杂。
<img src="初识面向对象/面向过程.png" alt="img-面向过程" style="zoom: 50%;" />
## 面向对象—谁来做
相比较函数,面向对象是更大的封装,根据职责在一个对象中封装多个方法
1. 在完成某一个需求前,首先确定职责 —— 要做的事情(方法)
2. 根据职责确定不同的对象,在对象内部封装不同的方法(多个)
3. 最后完成的代码,就是顺序地让不同的对象调用不同的方法
**特点**
1. 注重对象和职责,不同的对象承担不同的职责。
2. 更加适合应对复杂的需求变化,是专门应对复杂项目开发,提供的固定套路。
3. 需要在面向过程基础上,再学习一些面向对象的语法。
<img src="初识面向对象/植物大战僵尸.png" alt="img-植物大战僵尸" style="zoom:80%;" />
<img src="初识面向对象/植物大战僵尸类.png" alt="img-植物大战僵尸类" style="zoom:80%;" />
# 类与对象
在面向对象中,类与对象是两个核心的概念
## 类
- 类是对一群具有相同特征或者行为的事物的一个统称,是抽象的,不能直接使用
- **特征** 被称为 **属性**
- **行为** 被称为 **方法**
- 类就相当于制造飞机时的图纸,是一个模板,是负责创建对象的。
<img src="初识面向对象/飞机设计图纸.png" alt="img-飞机设计图纸" style="zoom:80%;" />
## 对象
- 对象是由类创建出来的一个具体存在,可以直接使用。
- 由哪一个类创建出来的对象,就拥有在哪一个类中定义的:属性 & 方法。
- 对象就相当于用图纸制造的飞机。
<img src="初识面向对象/飞机对象.png" alt="img-飞机对象" style="zoom: 80%;" />
## 类与对象的关系
- 类是模板,对象是根据类这个模板创建出来的,应该先有类,再有对象。
- 类只有一个,而对象可以有很多个:不同的对象之间属性可能会各不相同。
- 类中定义了什么属性和方法,对象中就有什么属性和方法,不可能多,也不可能少。
## 类的设计
在使用面相对象开发前,应该首先分析需求,确定一下,程序中需要包含哪些类
<img src="初识面向对象/植物大战僵尸类.png" alt="img-植物大战僵尸类" style="zoom:80%;" />
在程序开发中,要设计一个类,通常需要满足一下三个要素:
1. **类名** 这类事物的名字,**满足大驼峰命名法**
2. **属性** 这类事物具有什么样的特征
3. **方法** 这类事物具有什么样的行为
**大驼峰命名法**
```python
CapWords
```
**类名的确定**
通常类名的选择,我们应该根据整个业务流程来提取,或者从大的角度来选择
**属性和方法的确定**
- 对对象的特征描述,通常可以定义成属性
- 对象具有的行为,通常可以定义成方法
## 类的定义
类的基本定于语法如下:
```python
class Human:
'''
这里可以写上对于这个类的说明
'''
变量 = xxxx # 这里是类的静态属性,也可以理解为该类共有的特性
dic = {}
l1 = []
def __init__(self,xxx,xxx): # 初始化方法
pass
def func(self): # 方法,动态属性
pass
```
**示例**
定义一个人"类",人类有思想,并且具备姓名,年龄,身高。人类还会吃饭,还会跑步等
<img src="初识面向对象/人类.png" alt="img-人类" style="zoom:80%;" />
```python
class Human(object): # 默认继承自 object
"""
此类用来构造人类
"""
mind = "思考问题.."
def __init__(self,name,age,height):
# 在__init__中通过self给对象封装属性
self.name = name
self.age = age
self.height = height
def run(self):
print('高高兴兴的跑步')
def eat(self):
print('大口大口的吃饭')
```
这里的 object 和 self 的解释如下:
`object` 是 Python 中所有类的基类。它是所有用户自定义类和内置类的顶层父类。可以理解为所有的类都继承自 object 类,所以才能具备类的初始化等许多基础特性。
`self` 是一个约定俗成的参数名,用于引用类的实例。它代表当前对象的实例,使我们能够访问实例的属性和方法。
在实例方法中,第一个参数通常命名为 `self`,但你可以使用其他名称(虽然不推荐)。
## 实例化对象
通过类生成具体的对象的过程,我们称之为实例化。
```python
class Human(object): # 默认继承自 object
"""
此类用来构造人类
"""
mind = "思考问题.."
def __init__(self,name,age,height):
self.name = name
self.age = age
self.height = height
def run(self):
print('高高兴兴的跑步')
def eat(self):
print('大口大口的吃饭')
# 实例化对象
xiaoming = Human('小明',18, 173.5)
xiaohong = Human('小红',20, 165)
```
这里的小明和小红就是我们通过 Human 这个类实例化出来的具体的对象
其实实例化一个对象总共发生了三件事:
1. 在内存中开辟了一个对象空间。
2. 自动执行类中的 `__init__` 方法,并将这个对象空间(内存地址)传给了 `__init__` 方法的第一个位置参数 self。
3.`__init__` 方法中通过 self 给对象空间添加属性。
**对象访问类中的属性和方法**
```python
# 访问静态属性
print(xiaoming.mind)
print(xiaohong.name)
print(xiaohong.age)
# 访问动态方法
xiaohong.run()
xiaoming.eat()
```
**查看对象的所有属性**
我们可以通过 object 基类中提供的 `__dict__` 方法来查看某个对象的属性。
```python
# 通过对象查看类中所有的属性
print(xiaoming.__dict__)
```
# 从类名的角度研究类
## 类名操作静态属性
查看类中所有的内容,用 `类名.__dict__`
```python
class Human(object): # 默认继承自 object
"""
此类用来构造人类
"""
mind = "思考问题.."
# 在__init__中通过self给对象封装属性
def __init__(self,name,age,height):
self.name = name
self.age = age
self.height = height
def run(self):
print('高高兴兴的跑步')
def eat(self):
print('大口大口的吃饭')
print(Human.__dict__)
print(Human.__dict__['mind'])
Human.__dict__['mind'] = '高智慧'
# 通过这种方式只能查询,不能增删改
print(Human.__dict__)
```
**万能的点 `.`**
在面向对象中,我们更多的是使用 `.` 来获取类或者对象的属性或方法
```python
class Human(object): # 默认继承自 object
"""
此类用来构造人类
"""
mind = "思考问题.."
def __init__(self, name, age, height):
# 在__init__中通过self给对象封装属性
self.name = name
self.age = age
self.height = height
def run(self):
print('高高兴兴的跑步')
def eat(self):
print('大口大口的吃饭')
print(Human.mind)
Human.mind = '高智慧'
print(Human.mind)
del Human.mind
Human.run = '慢慢悠悠的走路'
print(Human.run)
# 通过万能的点 可以增删改查类中的单个属性
print('大口大口的吃饭')
```
总结:如果想要查看类的内容,我们可以使用`__dict__`方法,如果想要操作类中的某个属性,可以使用`.`
## 类名操作动态方法
```python
class Human(object): # 默认继承自 object
"""
此类用来构造人类
"""
mind = "思考问题.."
# 在__init__中通过self给对象封装属性
def __init__(self, name, age, height):
self.name = name
self.age = age
self.height = height
def run(self):
print(self,'高高兴兴的跑步')
def eat(self):
print(self,'大口大口的吃饭')
# 可以直接通过human调用动态方法也可以通过dict为类内部方法传递实参
Human.eat('小明')
Human.__dict__['run']('小红')
```
# 从对象的角度研究类
## 对象操作对象属性
对象也可以通过 `__dict__` 查看对象的所有属性
```python
class Human(object): # 默认继承自 object
"""
此类用来构造人类
"""
mind = "思考问题.."
def __init__(self, name, age, height):
self.name = name
self.age = age
self.height = height
def run(self):
print('高高兴兴的跑步')
def eat(self):
print('大口大口的吃饭')
# 实例化出一个具体的对象
xiaoming = Human('小明', 18, 173.5)
xiaohong = Human('小红', 20, 165)
print(xiaoming.__dict__)
```
同样也可以使用万能的点操作对象属性
```python
class Human(object): # 默认继承自 object
"""
此类用来构造人类
"""
mind = "思考问题.."
def __init__(self, name, age, height):
self.name = name
self.age = age
self.height = height
def run(self):
print('高高兴兴的跑步')
def eat(self):
print('大口大口的吃饭')
# 实例化出一个具体的对象
xiaoming = Human('小明', 18, 173.5)
xiaohong = Human('小红', 20, 165)
# 修改属性
xiaoming.name = "小小明"
# 增加属性
xiaoming.sex = "男"
print(xiaoming.sex)
# 删除属性
del xiaoming.height
# 查看属性
print(xiaoming.__dict__)
```
## 对象查看类的属性
```python
class Human(object): # 默认继承自 object
"""
此类用来构造人类
"""
mind = "思考问题.."
def __init__(self, name, age, height):
self.name = name
self.age = age
self.height = height
def run(self):
print('高高兴兴的跑步')
def eat(self):
print('大口大口的吃饭')
# 实例化出一个具体的对象
xiaoming = Human('小明', 18, 173.5)
xiaohong = Human('小红', 20, 165)
print(xiaoming.mind)
```
# 类的内置方法
| 方法名 | 作用 |
|:-------- | :------------------------------------------- |
| `__new__` | 创建对象时,会被自动调用 |
| `__init__`| 对象被初始化时,会被自动调用 |
| `__del__` | 对象被从内存中销毁前,会被自动调用 |
| `__str__` | 返回对象的描述信息,`print` 函数输出使用 |
|`__dir__` | 查看对象内所有属性以及方法 |
# 总结
1. 现有类才有对象,类是模版,对象是通过类实例化出来的。
2. 一个类可以实例化很多个对象。
3. 类中包含静态属性和动态方法,包括内置方法,自定义方法。
4. 实例化对象的时候,会自动调用 `__init__` 来初始化,我们可以在 `__init__` 中定义对象初始化的属性
5. 实例化好的对象可以调用类中的方法或者是静态属性

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 969 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View File

@@ -0,0 +1,345 @@
# 反射
Python 中反射Reflection是一种能力使得程序能够在运行时查看和修改自身的结构和行为。通过反射您可以动态地访问和操作类的属性和方法而无需在编写代码时确定它们的确切名称。这在某些情况下非常有用例如在框架、库或插件系统中。
## 对对象的反射
```python
class Foo:
f = "类的静态变量"
def __init__(self, name, age):
self.name = name
self.age = age
def say_hi(self):
print("Hi %s" % self.name)
obj = Foo("EaglesLab", 18)
# 检测是否含有某属性
print(hasattr(obj, "name"))
print(hasattr(obj, "say_hi"))
# 获取属性
print(getattr(obj, "name"))
func = getattr(obj, "say_hi")
func()
print(getattr(obj, "job", "不存在啊")) # 报错
# 设置属性
setattr(obj, "job", "teacher")
setattr(obj, "show_name", lambda self: self.name + " 真帅")
print(obj.__dict__)
print(obj.show_name(obj))
# 删除属性
delattr(obj, "age")
delattr(obj, "show_name")
# delattr(obj,'show_name111') # 不存在,则报错
print(obj.__dict__)
```
## 对类的反射
```python
class Foo(object):
staticField = "test"
def __init__(self):
self.name = "陈松"
def func(self):
return "func"
@staticmethod
def bar():
return "bar"
print(getattr(Foo, "staticField"))
print(getattr(Foo, "func"))
print(getattr(Foo, "bar"))
```
## 案例:基于反射的用户管理
使用反射前
```python
class User:
def login(self):
print('欢迎来到登录页面')
def register(self):
print('欢迎来到注册页面')
def save(self):
print('欢迎来到存储页面')
user = User()
while 1:
choose = input('>>>').strip()
if choose == 'login':
user.login()
elif choose == 'register':
user.register()
elif choose == 'save':
user.save()
```
用了反射之后
```python
class User:
def login(self):
print('欢迎来到登录页面')
def register(self):
print('欢迎来到注册页面')
def save(self):
print('欢迎来到存储页面')
user = User()
while 1:
choose = input('>>>').strip()
if hasattr(user, choose):
func = getattr(user, choose)
func()
else:
print('输入错误...')
```
# 函数 vs 方法
## 通过打印函数(方法)名确定
```python
def func():
pass
print(func)
class A:
def func(self):
pass
print(A.func)
obj = A()
print(obj.func)
```
## 通过 types 模块验证
```python
from types import FunctionType
from types import MethodType
def func():
pass
class A:
def func(self):
pass
obj = A()
print(isinstance(func,FunctionType))
print(isinstance(A.func,FunctionType))
print(isinstance(obj.func,FunctionType))
print(isinstance(obj.func,MethodType))
```
## 静态方法是函数
```python
from types import FunctionType
from types import MethodType
class A:
def func(self):
pass
@classmethod
def func1(self):
pass
@staticmethod
def func2(self):
pass
obj = A()
# 静态方法其实是函数
print(isinstance(A.func2,FunctionType))
print(isinstance(obj.func2,FunctionType))
```
## 函数与方法的区别
那么,函数和方法除了上述的不同之处,我们还总结了一下几点区别。
1. 函数的是显式传递数据的。如我们要指明为len()函数传递一些要处理数据。
2. 函数则跟对象无关。
3. 方法中的数据则是隐式传递的。
4. 方法可以操作类内部的数据。
5. 方法跟对象是关联的。如我们在用 strip() 方法是,是不是都是要通过 str 对象调用,比如我们有字符串 s,然后 s.strip() 这样调用。是的strip()方法属于str对象。
我们或许在日常中会口语化称呼函数和方法时不严谨,但是我们心中要知道二者之间的区别。
在其他语言中,如 Java 中只有方法C 中只有函数C++ 则取决于是否在类中。
# 双下方法
## `__init__`
用于初始化类的实例,接收参数并设置实例属性。
```python
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
```
## `__len__`
```python
class B:
def __len__(self):
return 666
b = B()
print(len(b)) # len一个对象就会触发 __len__方法。
class A:
def __init__(self):
self.a = 1
self.b = 2
def __len__(self):
return len(self.__dict__)
a = A()
print(len(a))
```
## `__hash__`
```python
class A:
def __init__(self):
self.a = 1
self.b = 2
def __hash__(self):
return hash(str(self.a)+str(self.b))
a = A()
print(hash(a))
```
## `__str__`
如果一个类中定义了 `__str__` 方法,那么在 `print(obj)` 时,默认输出该方法的返回值。
```python
class A:
def __init__(self):
pass
def __str__(self):
return '陈松'
a = A()
print(a)
print('%s' % a)
```
## `__repr__`
如果一个类中定义了 `__repr__` 方法,那么在` repr(obj)` 时,默认输出该方法的返回值。
```python
class A:
def __init__(self):
pass
def __repr__(self):
return '陈松'
a = A()
print(repr(a))
print('%r'%a)
```
## `__call__`
通过 ` obj() ` 触发执行。
```python
class Foo:
def __init__(self):
print('__init__')
def __call__(self, *args, **kwargs):
print('__call__')
obj = Foo() # 执行 __init__
obj() # 执行 __call__
```
## `__eq__`
通过 `==` 触发类中的 `__eq__` 方法
```python
class A:
def __init__(self):
self.a = 1
self.b = 2
def __eq__(self,obj):
if self.a == obj.a and self.b == obj.b:
return True
a = A()
b = A()
print(a == b)
```
## `__del__`
析构方法,当对象在内存中被释放时,自动触发执行。
注:此方法一般无须定义,因为 Python 是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给 Python 解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
## `__new__`
在 Python 中,`__new__` 是一个控制对象创建过程的魔术方法,它比 `__init__` 更早执行。
- 优先级:`__new__` 是对象创建的第一步,负责生成实例;`__init__` 是第二步,负责初始化实例。
- 返回值:`__new__` 必须返回实例对象(否则 `__init__` 不会执行),而 `__init__` 无返回值。
- 静态方法:`__new__` 隐式作为静态方法存在,第一个参数是类本身 `cls`,而非实例 `self`
- ​​继承链​​:若未重写 `__new__`Python 会沿继承链调用父类的 `__new__`,直至 `object.__new__`
```python
class A:
def __new__(cls, *args, **kwargs):
print("in new function")
return object.__new__(A, *args, **kwargs) # 调用父类的 __new__ 创建实例并返回实例
def __init__(self):
self.x = 1
print("in init function")
a = A()
print(a.x)
```

View File

@@ -0,0 +1,299 @@
# 封装
将数据和方法捆绑在对象内部,仅通过暴露的接口与外界交互,保护数据安全并简化使用。
# 案例解析
第一步:将内容封装到类中,并且实例化对象
```python
class Foo:
def __init__(self,name,age):
self.name = name
self.age = age
def detail(self):
print(self.name)
print(self.age)
obj1 = Foo('xiaohong',18)
obj2 = Foo('xiaoming',16)
```
第二步:通过对象调用被封装的内容
```python
class Foo:
def __init__(self,name,age):
self.name = name
self.age = age
def detail(self):
print(self.name)
print(self.age)
obj1 = Foo('chensong',18)
obj2 = Foo('aaron',16)
# 通过对象直接调用被封装的内容
print(obj1.name)
print(obj2.age)
# 通过 self 间接调用被封装的内容
obj1.detail()
obj2.detail()
```
## 案例一:摆放家具
**需求**
1. 房子House有户型、总面积和家具名称列表
2. 家具HouseItem有名字和占地面积其中
-bed占地 `4` 平米
- 衣柜chest占地 `2` 平米
- 餐桌table 占地 `1.5` 平米
3. 将以上三件家具添加到房子中
4. 打印房子时,要求输出:户型、总面积、剩余面积、家具名称列表
<img src="封装/摆放家具类图.png" alt="img-摆放家具类图" style="zoom:80%;" />
**剩余面积**
1. 在创建房子对象时,定义一个 **剩余面积的属性****初始值和总面积相等**
2. 当调用 `add_item` 方法,向房间 **添加家具** 时,让 **剩余面积** -= **家具面积**
**思考**:应该先开发哪一个类?**家具类**
1. 家具简单
2. 房子要使用到家具,**被使用的类**,通常应该先开发
**第一步:创建家具类并且实例化家具对象**
```python
class HouseItem:
def __init__(self, name, area):
"""
:param name: 家具名称
:param area: 占地面积
"""
self.name = name
self.area = area
def __str__(self):
return "[%s] 占地面积 %.2f" % (self.name, self.area)
# 1. 创建家具
bed = HouseItem("床", 4)
chest = HouseItem("衣柜", 2)
table = HouseItem("餐桌", 1.5)
print(bed)
print(chest)
print(table)
```
**第二步:创建房间类并且实例化房间对象**
```python
class House:
def __init__(self, house_type, area):
"""
house_type: 户型
area: 总面积
"""
self.house_type = house_type
self.area = area
# 剩余面积默认和总面积一致
self.free_area = area
# 默认没有任何的家具
self.item_list = []
def __str__(self):
# Python 能够自动的将一对括号内部的代码连接在一起
return ("户型:%s\n总面积:%.2f[剩余:%.2f]\n家具:%s"
% (self.house_type, self.area,
self.free_area, self.item_list))
def add_item(self, item):
print("要添加 %s" % item)
# 2. 创建房子对象
my_home = House("汤成一品两室一厅", 60)
print(my_home)
```
**第三步在House中完善添加家具的方法**
```python
def add_item(self, item):
print("要添加 %s" % item)
# 1. 判断家具面积是否大于剩余面积
if item.area > self.free_area:
print("%s 的面积太大,不能添加到房子中" % item.name)
return
# 2. 将家具的名称追加到名称列表中
self.item_list.append(item.name)
# 3. 计算剩余面积
self.free_area -= item.area
```
**完整案例:**
```python
class HouseItem:
def __init__(self, name, area):
"""
:param name: 家具名称
:param area: 占地面积
"""
self.name = name
self.area = area
def __str__(self):
return "[%s] 占地面积 %.2f" % (self.name, self.area)
# 1. 创建家具
bed = HouseItem("席梦思", 4)
chest = HouseItem("衣柜", 2)
table = HouseItem("餐桌", 1.5)
class House:
def __init__(self, house_type, area):
"""
house_type: 户型
area: 总面积
"""
self.house_type = house_type
self.area = area
# 剩余面积默认和总面积一致
self.free_area = area
# 默认没有任何的家具
self.item_list = []
def __str__(self):
# Python 能够自动的将一对括号内部的代码连接在一起
return ("户型:%s\n总面积:%.2f[剩余:%.2f]\n家具:%s"
% (self.house_type, self.area,
self.free_area, self.item_list))
def add_item(self, item):
print("要添加 %s" % item)
# 1. 判断家具面积是否大于剩余面积
if item.area > self.free_area:
print("%s 的面积太大,不能添加到房子中" % item.name)
return
# 2. 将家具的名称追加到名称列表中
self.item_list.append(item.name)
# 3. 计算剩余面积
self.free_area -= item.area
# 2. 创建房子对象
my_home = House("汤成一品两室一厅", 60)
my_home.add_item(bed)
my_home.add_item(chest)
my_home.add_item(table)
print(my_home)
```
**小结**
- 主程序只负责创建房子对象和家具对象
- 让房子对象调用 `add_item` 方法将家具添加到房子中
- 面积计算、剩余面积、家具列表等处理都被封装到房子类的内部
## 案例二:士兵突击
**需求**
1. 士兵许三多有一把AK47
2. 士兵可以开火
3. 枪能够发射子弹
4. 枪装填装填子弹
<img src="封装/士兵突击类图.png" alt="img-士兵突击类图" style="zoom:80%;" />
**先开发枪类**
**`shoot` 方法需求**
- 判断是否有子弹,没有子弹无法射击
- 使用 `print` 提示射击,并且输出子弹数量
```python
class Gun:
def __init__(self, model):
# 枪的型号
self.model = model
# 子弹数量,初始为0
self.bullet_count = 0
def add_bullet(self, count):
self.bullet_count += count
def shoot(self):
# 判断是否还有子弹
if self.bullet_count <= 0:
print("没有子弹了...")
return
# 发射一颗子弹
self.bullet_count -= 1
print("%s 发射子弹[%d]...突突突" % (self.model, self.bullet_count))
# 创建枪对象
ak47 = Gun("ak47")
ak47.add_bullet(50)
ak47.shoot()
```
**开发士兵类:**
**`fire` 方法需求**
- 判断是否有枪,没有枪没法冲锋
- 喊一声口号
- 装填子弹
- 射击
```python
class Soldier:
def __init__(self, name, gun=None):
# 姓名
self.name = name
# 枪,士兵初始没有枪 None 关键字表示什么都没有
self.gun = gun
def fire(self):
# 1. 判断士兵是否有枪
if self.gun is None:
print("[%s] 还没有枪..." % self.name)
else:
# 2. 高喊口号
print("冲啊...[%s]" % self.name)
if self.gun.bullet_count <= 0:
print("没子弹了,快换弹夹...")
# 3. 让枪装填子弹
self.gun.add_bullet(50)
# 4. 让枪发射子弹
self.gun.shoot()
xsd = Soldier("xsd",ak47)
xsd.fire()
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -0,0 +1,256 @@
# 类的组成成员分类
Python 类的组成成员可以分为三大类:字段(变量)、​​​方法(函数)和​​​​属性(特殊方法),它们的定义方式、作用域和调用规则各有不同。
## 字段(变量)
字段分为​​实例变量​​和​​类变量​​,核心区别在于存储位置和作用对象:
- 实例变量:每个对象独立拥有一份实例变量,修改不影响其他对象。
- 类变量:通过类名或实例均可访问,但通过实例修改会创建同名实例变量,覆盖类变量。
**示例:**
```python
class Dog:
species = "Canis lupus" # 类变量
def __init__(self, name):
self.name = name # 实例变量
d = Dog("Buddy")
print(d.name)
d1 = Dog()
print(Dog.species)
d1.species = "Mutt"
print(Dog.species)
```
## 方法(函数)
方法分为实例方法​​、​​类方法​​和​静态方法​​,区别在于参数和调用方式:
- 实例方法:第一个参数为 self指向调用该方法的实例必须通过对象调用可访问实例变量和类变量。
- ​​类方法:使用 @classmethod 装饰器,参数为 cls指向类本身操作类变量或实现工厂模式创建实例
- ​静态方法:使用 @staticmethod 装饰器,无默认参数,不依赖类或实例状态;执行与类相关的工具函数。
**示例:**
```python
class Dog:
species = "Canis lupus" # 类变量
def __init__(self, name, age=0):
self.name = name # 实例变量
self.age = age
def bark(self):
print(f"{self.name} is barking!")
@classmethod
def create_from_string(cls, s):
name, age = s.split(",")
return cls(name, int(age)) # 创建实例时调用__init__方法
@classmethod
def get_species(cls):
return cls.species
@staticmethod
def describe():
return "Dogs are domesticated animals."
d = Dog("Buddy", 11)
d.bark() # Buddy is barking!
d1 = Dog.create_from_string("Max,5") # 通过类方法创建实例
d2 = Dog("Bella", 3) # 通过类方法创建实例
print(d2.name)
print(d1.age)
print(Dog.get_species()) # 类方法调用
d3 = Dog("Charlie", 2)
print(d3.get_species()) # 实例调用
print(Dog.describe()) # 静态方法调用
```
## 属性Property
属性是​​伪装成字段的方法​​,通过 @property 装饰器实现,用于封装逻辑。
```python
class BankAccount:
def __init__(self, balance):
self.__balance = balance
@property
def balance(self):
return self.__balance
@balance.setter
def balance(self, value):
if value >= 0:
self.__balance = value
@balance.deleter
def balance(self):
del self.__balance
account = BankAccount(100)
print(account.balance) # 调用getter方法
account.balance = 200 # 调用setter方法
del account.balance # 删除属性
print(account.balance)
```
## 其他特殊成员
- 魔术方法:如 `__init__`(构造函数)、`__str__`(字符串表示)等,用于自定义类的行为。
- 私有成员:通过双下划线前缀(如 __variable实现封装仅在类内部访问。
**示例**
```python
class MyClass:
def __init__(self, value):
self.value = value # 公有属性
self.__value = value # 私有属性
def public_method(self):
return f'这是类的公有方法, value: {self.value}'
def __private_method(self):
return f'这是类的私有方法, value: {self.__value}'
def get_value(self):
return self.__value # 通过公有方法访问私有属性
obj = MyClass(10)
print(obj.value) # 访问公有属性
print(obj.public_method()) # 调用公有方法
print(obj.__value) # 访问公有属性
print(obj.__private_method()) # 调用私有方法
```
# 案例
**需求**
- 设计一个 `Game`
- 属性:
- 定义一个类属性 `top_score` 记录游戏的历史最高分
- 定义一个实例属性 `player_name` 记录当前游戏的玩家姓名
- 方法:
- 静态方法 `show_help` 显示游戏帮助信息
- 类方法 `show_top_score` 显示历史最高分
- 实例方法 `start_game` 开始当前玩家的游戏
- 主程序步骤
1. 查看帮助信息
2. 查看历史最高分
3. 创建游戏对象,开始游戏
![img-方法综合案例](类的成员/方法综合案例.png)
```python
class Game(object):
# 游戏最高分,类属性
top_score = 0
@staticmethod
def show_help():
print("帮助信息:让僵尸走进房间")
@classmethod
def show_top_score(cls):
print("游戏最高分是 %d" % cls.top_score)
def __init__(self, player_name):
self.player_name = player_name
def start_game(self):
print("[%s] 开始游戏..." % self.player_name)
# 使用类名.修改历史最高分
Game.top_score = 999
# 1. 查看游戏帮助
Game.show_help()
# 2. 查看游戏最高分
Game.show_top_score()
# 3. 创建游戏对象,开始游戏
game = Game("小明")
game.start_game()
# 4. 游戏结束,查看游戏最高分
Game.show_top_score()
```
# 总结
| 特性 | 普通方法 | 类方法 | 静态方法 |
| :---------- | :------------------ | :---------------- | :-------------------- |
| 定义方式 | 不需要装饰器 | `@classmethod` | `@staticmethod` |
| 第一个参数 | `self` | `cls` | 无 |
| 访问权限 | 访问实例属性和方法 | 访问类属性和方法 | 无法访问类和实例属性 |
| 调用方式 | 通过实例调用 | 通过类或实例调用 | 通过类或实例调用 |
| 适用场景 | 实例相关操作 | 与类相关的操作 | 与类无关的操作 |
# isinstace 与 issubclass
isinstance(a,b)判断a是否是b类或者b类的派生类实例化的对象
```python
class A:
pass
class B(A):
pass
obj = B()
print(isinstance(obj,B))
print(isinstance(obj,A))
```
issubclass(a,b) 判断a类是否是b类或者b的派生类的派生类
```python
class A:
pass
class B(A):
pass
class C(B):
pass
print(issubclass(B,A))
print(issubclass(C,A))
```
思考:那么 list str tuple dict等这些类与 Iterble类 的关系是什么?
```python
from collections import Iterable
print(isinstance([1,2,3], list)) # True
print(isinstance([1,2,3], Iterable)) # True
print(issubclass(list, Iterable)) # True
# 由上面的例子可得这些可迭代对象list str tuple dict等 都是 Iterable 的子类。
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -0,0 +1,383 @@
# 类的空间问题
## 添加类和对象的属性
在 Python 中,**对象属性**(实例属性)和**类属性**的添加可以分别在类的内部(定义类时)和外部(运行时)进行。
### 对象属性
- **内部添加**:在类的 `__init__` 方法中定义的属性,属于对象(实例),每个实例有自己独立的属性。
- **外部添加**:可以在实例化对象后,动态地为某个对象添加属性。
### 类属性
- **内部添加**:在类的定义中直接定义的属性,属于类本身,所有实例共享。
- **外部添加**:可以在类定义后,动态地添加类属性。
```python
class MyClass:
# 1. 类属性:内部添加
class_attr = "I am a class attribute"
def __init__(self, name):
# 2. 对象属性:内部添加
self.name = name
def add_instance_attr(self, age):
# 3. 对象属性:内部添加(通过方法动态添加)
self.age = age
# 创建实例
obj1 = MyClass("Object 1")
# 访问类属性和对象属性
print(obj1.name) # 输出: Object 1
print(MyClass.class_attr) # 输出: I am a class attribute
# 4. 对象属性:外部添加
obj1.gender = "Male" # 动态给 obj1 添加 gender 属性
print(obj1.gender) # 输出: Male
# 5. 类属性:外部添加
MyClass.new_class_attr = "I am a new class attribute"
print(MyClass.new_class_attr) # 输出: I am a new class attribute
# 创建另一个实例,验证类属性的共享性
obj2 = MyClass("Object 2")
print(obj2.name) # 输出: Object 2
print(obj2.new_class_attr) # 输出: I am a new class attribute
# 6. 在内部通过方法添加对象属性
obj1.add_instance_attr(25)
print(obj1.age) # 输出: 25
# 注意obj2 没有 age 属性,因为 age 是通过 obj1 的方法动态添加的
# print(obj2.age) # 访问时会报错,因为 obj2 没有 age 属性
```
## 对象如何找到类的属性
**对象查找属性的顺序**
1. 先从对象空间找
2. 类空间找
3. 父类空间找
4. 基类空间
**类名查找属性的顺序**
1. 先从本类空间找
2. 父类空间找
3. 基类空间
上面的顺序都是单向不可逆,类名不可能找到对象的属性。
# 类之间关系
类与类中存在以下关系:
- 依赖关系
- 关联关系
- 组合关系
- 聚合关系
- 实现关系
- 继承关系(类的三大特性之一:继承)
## 依赖关系
例:将大象装进冰箱,需要两个类, ⼀个是⼤象类, ⼀个是冰箱类
```python
class Elphant:
def __init__(self, name):
self.name = name
def open(self):
'''
开门
'''
pass
def go(self):
# 大象进入冰箱
pass
def close(self):
'''
关门
'''
pass
class Refrigerator:
def open_door(self):
print('冰箱门打开了')
def close_door(self):
print('冰箱门关上了')
```
将大象类和冰箱类进行依赖
```python
class Elphant:
def __init__(self,name):
self.name = name
def open(self,obj):
print(self.name + '开门')
obj.open_door()
def go(self):
print(f'{self.name}进到冰箱里')
def close(self,obj):
print(self.name + '关门')
obj.close_door()
class Refrigerator:
def __init__(self,name):
self.name = name
def open_door(self):
print(self.name + '门被打开了')
def close_door(self):
print(self.name+'门被关上了')
elphant = Elphant('小飞象')
refrigerator = Refrigerator('格力冰箱')
elphant.open(refrigerator)
elphant.go()
elphant.close(refrigerator)
```
## 关联-聚合-组合关系
其实这三个在代码上写法是⼀样的,但是从含义上是不⼀样的:
1. 关联关系:两种事物必须是互相关联的,但是在某些特殊情况下是可以更改和更换的。
2. 聚合关系属于关联关系中的⼀种特例侧重点是xxx和xxx聚合成xxx各⾃有各⾃的声明周期比如电脑电脑⾥有 CPU, 硬盘, 内存等等。电脑挂了, CPU 还是好的,还是完整的个体。
3. 组合关系:属于关联关系中的⼀种特例,写法上差不多,组合关系比聚合还要紧密。比如⼈的⼤脑,⼼脏,各个器官,这些器官组合成⼀个⼈。这时,⼈如果挂了,其他的东⻄也跟着挂了。
**关联关系**
```python
class Boy:
def __init__(self,name, girlfriend = None):
self.name = name
self.girlfriend = girlfriend
def dinner(self):
if self.girlfriend:
print('%s%s 一起共进晚餐' % (self.name, self.girlfriend.name))
else:
print('连女朋友都没有,还有心情吃饭')
class Girl:
def __init__(self, name):
self.name = name
boy = Boy('张三')
boy.dinner()
girl = Girl('如花')
boy2 = Boy('李四', girl)
boy2.dinner()
```
注意, 此时 Boy 和 Girl 两个类之间就是关联关系,两个类的对象紧密联系着,其中⼀个没有了,另⼀个就孤单的不得了,关联关系,其实就是,我需要你,你也属于我。
学校和老师之间的关系
```python
class School:
def __init__(self,name,address):
self.name = name
self.address = address
class Teacher:
def __init__(self,name,school):
self.name = name
self.school = school
s1 = School('镇江校区','北京')
s2 = School('常州校区','上海')
s3 = School('南京校区','深圳')
t1 = Teacher('T1',s1)
t2 = Teacher('T2',s2)
t3 = Teacher('T3',s3)
print(t1.school.name)
print(t2.school.name)
print(t3.school.name)
```
但是学校也是依赖于老师的,所以老师学校应该互相依赖。
```python
class School:
def __init__(self,name,address):
self.name = name
self.address = address
self.teacher_list = []
def append_teacher(self,teacher):
self.teacher_list.append(teacher)
class Teacher:
def __init__(self,name,school):
self.name = name
self.school = school
s1 = School('北京校区','北京')
s2 = School('上海校区','上海')
s3 = School('深圳校区','深圳')
t1 = Teacher('T1',s1)
t2 = Teacher('T2',s2)
t3 = Teacher('T3',s3)
s1.append_teacher(t1.name)
s1.append_teacher(t2.name)
s1.append_teacher(t3.name)
print(s1.teacher_list)
```
**组合:将一个类的对象封装到另一个类的对象的属性中,就叫组合。**
例:设计一个游戏,让游戏里面的人物互殴,加上一个武器类,让人使用武器攻击。
```python
class Gamerole:
def __init__(self,name,ad,hp,wea=None):
self.name = name
self.ad = ad
self.hp = hp
self.wea = wea
def attack(self,p1):
p1.hp -= self.ad
print('%s攻击%s,%s掉了%s血,还剩%s'%(self.name,p1.name,p1.name,self.ad,p1.hp))
def equip_weapon(self,wea):
self.wea = wea
wea.ad += self.ad
wea.owner_name = self.name
class Weapon:
def __init__(self,name,ad,owner_name = None):
self.name = name
self.owner_name = owner_name
self.ad = ad
def weapon_attack(self,p2):
p2.hp = p2.hp - self.ad
print('%s利用%s攻击了%s%s还剩%s血'%(self.owner_name,self.name,p2.name,p2.name,p2.hp))
man = Gamerole('人',10,100)
dog = Gamerole('狗',50,100)
stick = Weapon('木棍',40)
man.equip_weapon(stick)
man.wea.weapon_attack(dog)
# 人利用木棍攻击了狗狗还剩50血
```
## 案例王者荣耀3V3
```python
import time
import random
class Gamerole:
def __init__(self,name,ad,hp):
self.name = name
self.ad = ad
self.hp = hp
def attack(self,p1):
p1.hp -= self.ad
print('%s攻击%s,%s掉了%s血,还剩%s'%(self.name,p1.name,p1.name,self.ad,p1.hp))
def equip_weapon(self,wea):
self.wea = wea
wea.ad += self.ad
wea.owner_name = self.name
class Weapon:
def __init__(self,name,ad,owner_name = None):
self.name = name
self.owner_name = owner_name
self.ad = ad
def weapon_attack(self,p2):
p2.hp = p2.hp - self.ad
print('%s利用%s攻击了%s%s还剩%s血'%(self.owner_name,self.name,p2.name,p2.name,p2.hp))
sunwukong = Gamerole("孙悟空", 20, 500)
caocao = Gamerole("曹操", 20, 100)
anqila = Gamerole("安琪拉", 50, 80)
zhaoyun = Gamerole("赵云", 30, 450)
guanyu = Gamerole("关羽", 80, 200)
diaochan = Gamerole("貂蝉", 60, 150)
blue_list = [sunwukong, caocao, anqila]
red_list = [zhaoyun, guanyu, diaochan]
if __name__ == '__main__':
print("游戏开始加载")
# 打印一个菜单
for i in range(0, 101, 2):
time.sleep(0.1)
char_num = i // 2
per_str = '\r%s%% : %s\n' % (i, '*' * char_num) \
if i == 100 else '\r%s%% : %s' % (i, '*' * char_num)
print(per_str, end='', flush=True)
info = input("游戏加载完毕,输入任意字符开始!")
# 输出东邪吸毒阵营里的任务角色
print("蓝方阵营".center(20, '*'))
for i in blue_list:
print(i.name.center(20))
print("红方阵营".center(20, '*'))
for i in red_list:
print(i.name.center(20))
while True:
# 判断游戏结束的条件是某一方全部阵亡
if len(blue_list) == 0:
print("红方阵营胜利!!!")
break
if len(red_list) == 0:
print("蓝方阵营胜利!")
break
# 游戏逻辑,每次随机选择一名角色出击
index1 = random.randint(0, len(blue_list) - 1)
index2 = random.randint(0, len(red_list) - 1)
# 开始攻击
time.sleep(1)
role1 = blue_list[index1]
time.sleep(1)
role2 = red_list[index2]
time.sleep(1)
role1.attack(role2)
role2.attack(role1)
# 判断是否有英雄阵亡
if role1.hp <= 0:
print("%s阵亡!" % role1.name)
blue_list.remove(role1)
if role2.hp <= 0:
print("%s阵亡!" % role2.name)
red_list.remove(role2)
```

View File

@@ -0,0 +1,789 @@
# 继承
子类可复用父类的属性和方法,并扩展新功能,实现代码复用和逻辑分层。例如,"电动车"类继承自"汽车"类,新增"充电"方法。
不用继承创建对象
```python
class Person:
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
class Cat:
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
class Dog:
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
```
使用继承的方式
```python
class Aniaml(object):
def __init__(self,name, age):
self.name = name
self.age = age
def eat(self):
print(f"{self.name}吃东西..")
class Dog(Aniaml):
pass
xiaotianquan = Dog("哮天犬",5)
xiaotianquan.eat()
```
继承概念:子类拥有父类的所有方法和属性。
<img src="继承与多态/继承对比图示.png" alt="img-继承对比图示" style="zoom:80%;" />
**继承的优点**
1. 增加了类的耦合性(耦合性不宜多,宜精)。
2. 减少了重复代码。
3. 使得代码更加规范化,合理化。
# 继承分类
上面的那个例子,涉及到的专业术语:
- `Dog` 类是 `Animal` 类的**子类**`Animal` 类是 `Dog` 类的**父类**`Dog` 类从 `Animal` 类**继承**
- `Dog` 类是 `Animal` 类的**派生类**`Animal` 类是 `Dog` 类的**基类**`Dog` 类从 `Animal` 类**派生**
继承:可以分**单继承,多继承**。
这里需要补充一下 python 中类的种类(继承需要):
在 python 2 版本中存在两种类.
- ⼀个叫**经典类**. 在 Python2 中,经典类是指没有显式继承自 `object` 的类。它们使用旧的类定义方式。
- ⼀个叫**新式类**. 新式类是指显式继承自 `object` 或其他新式类的类。新式类在 Python 2.2 中引入,并在 Python 3 中成为默认。
# 单继承
## 对象执行父类方法
```python
class Aniaml(object):
type_name = '动物类'
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print('吃',self)
class Person(Aniaml):
pass
class Cat(Aniaml):
pass
class Dog(Aniaml):
pass
print(Person.type_name)
Person.eat('东西')
print(Person.type_name)
p1 = Person('aaron','男',18)
print(p1.__dict__)
print(p1.type_name)
p1.type_name = '666'
print(p1)
p1.eat()
```
## 执行顺序
```python
class Aniaml(object):
def __init__(self,name, age):
self.name = name
self.age = age
def eat(self):
print(f"{self.name}吃东西..")
class person(Aniaml):
def eat(self):
print('%s 用筷子吃饭' % self.name)
class Dog(Aniaml):
pass
class Cat(Aniaml):
pass
person1 = person('张三',18)
person1.eat()
```
## 同时执行类以及父类方法
方法一:如果想执行父类的 eat() 方法,可以在子类的方法中写上:父类.eat(对象,其他参数)
```python
class Aniaml(object):
type_name = '动物类'
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print('吃东西')
class Person(Aniaml):
def __init__(self,name,sex,age,mind):
Aniaml.__init__(self,name,sex,age)
self.mind = mind
def eat(self):
Aniaml.eat(self)
print('%s 吃饭'%self.name)
class Cat(Aniaml):
pass
class Dog(Aniaml):
pass
p1 = Person('aaron','男',18,'想吃东西')
p1.eat()
```
方法二:利用 `super().func(参数)`
```python
class Aniaml(object):
type_name = '动物类'
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print('吃东西')
class Person(Aniaml):
def __init__(self,name,sex,age,mind):
# super(Person,self).__init__(name,sex,age)
super().__init__(name,sex,age)
self.mind = mind
def eat(self):
super().eat()
print('%s 吃饭' % self.name)
class Cat(Aniaml):
pass
class Dog(Aniaml):
pass
p1 = Person('aaron','男',18,'想吃东西')
p1.eat()
```
## 方法重写
- 如果在开发中,父类的方法实现和子类的方法实现,完全不同,可以使用覆盖的方式,在子类中重新编写父类的方法实现
- 具体的实现方式,就相当于在子类中定义了一个和父类同名的方法并且实现
- 重写之后,在运行时,只会调用子类中重写的方法,而不再会调用父类封装的方法
## 子类中扩展父类方法
- 如果在开发中,子类的方法实现中包含父类的方法实现
- 父类原本封装的方法实现** 是 **子类方法的一部分**
- 就可以使用扩展的方式
1. 在子类中重写父类的方法
2. 在需要的位置使用 `super().父类方法` 来调用父类方法的执行
3. 代码其他的位置针对子类的需求,编写 子类特有的代码实现
**关于 `super`**
-`Python``super` 是一个特殊的类
- `super()` 就是使用 `super` 类创建出来的对象
- 最常使用的场景就是在重写父类方法时,调用在父类中封装的方法实现
## 父类的私有属性和私有方法
子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法,但可以通过父类的公有方法间接访问
- **私有属性、方法** 是对象的隐私,不对外公开,外界以及子类都不能直接访问
- **私有属性、方法** 通常用于做一些内部的事情
![img-父类的私有属性和私有方法](继承与多态/父类的私有属性和私有方法.png)
- `B` 的对象不能直接访问 `__num2` 属性
- `B` 的对象不能在 `demo` 方法内访问 `__num2` 属性
- `B` 的对象可以在 `demo` 方法内,调用父类的 `test` 方法
- 父类的 `test` 方法内部,能够访问 `__num2` 属性和 `__test` 方法
```python
class Animal:
def __init__(self,name):
self.__name = name
def __eat(self):
print(self.__name + "Eating...")
def eat2(self):
self.__eat()
class Dog(Animal):
pass
a = Dog('哮天犬')
print(a.name)
a.__eat()
a.eat2()
```
# 多继承
**概念**
- **子类** 可以拥有 **多个父类**,并且具有 **所有父类****属性****方法**
- 例如:**孩子** 会继承自己 **父亲****母亲****特性**
![img-多继承1](继承与多态/多继承1.png)
**语法**
```python
class 子类名(父类名1, 父类名2...)
pass
```
**问题的提出**
- 如果 **不同的父类** 中存在 **同名的方法****子类对象** 在调用方法时,会调用 **哪一个父类中**的方法呢?
> 提示:**开发时,应该尽量避免这种容易产生混淆的情况!** —— 如果 **父类之间** 存在 **同名的属性或者方法**,应该 **尽量避免** 使用多继承
![img-多继承2](继承与多态/多继承2.png)
```python
class shengxian: # 神仙
def fei(self):
print("神仙会飞")
def eat(self):
print("吃人参果")
class monkey: # 猴
def eat(self):
print("吃桃子")
class songwukong(shengxian,monkey): #孙悟空既是神仙也是猴
def __init__(self):
self.name = "孙悟空"
def eat(self):
print("我是齐天大圣,我不用吃东西")
swk = songwukong()
swk.eat()
```
## 经典类
```python
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
class E:
pass
class F(D, E):
pass
class G(F, D):
pass
class H:
pass
class Foo(H, G):
pass
```
示意图
![img-经典类多继承](继承与多态/经典类多继承.png)
在经典类中采⽤的是深度优先,遍历⽅案:优先遍历继承链的最左侧分支直至顶端,再向右查找其他分支。
类的 MRO (method resolution order): Foo-> H -> G -> F -> E -> D -> B -> A -> C。
## 新式类
C3 算法是 Python 中用于解决多继承场景下方法解析顺序MRO的核心算法其设计目标是保证继承关系的一致性本地优先级单调性
### 算法核心原理
C3 算法的核心是​**​线性化合并规则**通过递归合并父类的MRO列表生成一个无冲突的继承顺序链。
对于类 `C(B1, B2, ..., Bn)` ,其 MRO 计算公式为为:`L[C] = C + merge(L[B1],L[B2],..,L[Bn],[B1,B2,...,Bn])`
其中 `megre` 操作负责合并父类的 MRO 序列。
### `merge` 合并规则
`merge` 操作是 C3 算法的核心步骤,具体执行流程如下:
1. **选取第一个列表的头元素Head** 即列表的第一个元素,记为 `h`
2. **​检查 `h` 的有效性:**
-`h` 不在其他列表的​​尾部​​(即其他列表除首元素外的部分),则将其加入结果序列,并从所有列表中删除 `h`
-`h` 存在于其他列表的尾部,则跳过当前列表,选择下一个列表的头元素重复检查。
3. **​递归执行​​:** 重复步骤1-2直到所有列表为空成功或无法找到有效头元素失败并抛出 `TypeError`
**示例解析**
假设类 `D` 继承自 `B``C`,其父类的 MRO 分别为 `L(B)=[B, A, O]``L(C)=[C, A, O]`,则 `D` 的 MRO 计算如下:
`L(D) = D + merge([B,A,O],[C,A,O],[B,C])`
1. 初始合并列表为 `[[B, A, O], [C, A, O], B, C]`
2. 提取 `B`(不在其他列表尾部),结果序列为 `[D, B]`,剩余列表 `[[A, O], [C, A, O], C]`
3. 提取 `C`(不在其他列表尾部),结果序列扩展为 `[D, B, C]`,剩余列表 `[[A, O], [A, O]]`
4. 合并 `A``O`,最终得到 `L(D)=[D, B, C, A, O]`
### 实践案例
![img-新式类megre](继承与多态/新式类megre.png)
计算 mro(A) 方式:
step1:
L(A) = A + merge(L[B] + L[C], [B, C])
L(B) = B + merge(L[D] + L[E], [D, E])
L(C) = C + merge(L[E] + L[F], [E, F])
L(D) = D + merge(L[O]) = [D, O]
L(E) = E + merge(L[O]) = [E, O]
L[F] = F + merge(L[O]) = [F, O]
step2:
L(B) = B + merge([D, O], [E, O], [D, E])
= [B, D] + merge([O], [E, O], [E]) # 拿出并删除D
= [B, D, E] + merge([O], [O]) # 拿出并删除E
= [B, D, E, O]
L(C) = C + merge([E, O], [F, O], [E, F])
...
= [C, E, F, O]
step3:
L(A) = A + merge([B, D, E, O], [C, E, F, O], [B, C])
= [A, B] + merge([D, E, O], [C, E, F, O], [C]) # h = B 拿出并删除B
= [A, B, D] + merge([E, O], [C, E, F, O], [C]) # h = D 拿出并删除D
= [A, B, D] + merge([E, O], [C, E, F, O], [C]) # h = E 存在于其他列表的尾部则跳过
...
= [A, B, D, C, E, F, O]
**代码**
```python
class D:
pass
class E:
pass
class F:
pass
class B(D,E):
pass
class C(E,F):
pass
class A(B,C):
pass
print(A.__mro__)
```
# 多态
同一方法在不同对象中呈现不同行为,增强代码灵活性。例如,"动物"类的"发声"方法在"狗"和"猫"对象中分别输出"汪汪"和"喵喵"。
- 多态可以增加代码的灵活度
- 以继承和重写父类方法为前提
- 是调用方法的技巧,不会影响到类的内部设计
![img-多态示意图](继承与多态/多态示意图.png)
```python
class human(object):
def work(self):
return "喝杯咖啡,开始工作"
class ps_job(human):
def work(self):
return "开始美工"
class IT_job(human):
def work(self):
return "开始敲代码"
def job(person): # 多态函数
print(person.work())
# 创建不同类型的对象
ps = ps_job()
it = IT_job()
# 调用同一个函数,表现出不同的行为
job(ps)
job(it)
```
**案例:哮天犬**
**需求**
1.`Dog` 类中封装方法 `game`
2. 定义 `XiaoTianDog` 继承自 `Dog` ,并且重写 `game` 方法
3. 定义 `Person` 类,并且封装一个和狗玩的方法
![img-多态](继承与多态/多态.png)
**实现**
```python
class Dog(object):
def __init__(self, name):
self.name = name
def game(self):
print("%s 蹦蹦跳跳的玩耍..." % self.name)
class XiaoTianDog(Dog):
def game(self):
print("%s 飞到天上去玩耍..." % self.name)
class Person(object):
def __init__(self, name):
self.name = name
def game_with_dog(self, dog):
print("%s%s 快乐的玩耍..." % (self.name, dog.name))
# 让狗玩耍
dog.game()
# 1. 创建一个狗对象
wangcai = Dog("旺财")
xiaotianquan = XiaoTianDog("飞天旺财")
# 2. 创建一个小明对象
xiaoming = Person("小明")
# 3. 让小明调用和狗玩的方法
xiaoming.game_with_dog(wangcai)
xiaoming.game_with_dog(xiaotianquan)
```
**案例小结**
- `Person` 类中只需要让狗对象调用 `game` 方法,而不关心具体是什么狗。
- `game` 方法是在 `Dog` 父类中定义的。
- 在程序执行时,传入不同的狗对象实参,就会产生不同的执行效果。
# 鸭子类型
python 中有一句谚语说的好,你看起来像鸭子,那么你就是鸭子。
这句谚语是关于鸭子类型Duck Typing的一种表达方式。鸭子类型是一种动态类型的概念它强调一个对象的特征和行为而不是其具体的类型或继承关系。
在 Python 中,鸭子类型的概念可以简单地表述为:如果一个对象具有像鸭子一样的特征和行为,那么我们可以认为它是一个鸭子。这意味着我们关注对象是否具备特定的方法和属性,而不关心对象的具体类型。
这种思想在 Python 中经常被使用,特别是在函数参数传递和对象的使用上。如果一个函数接受一个参数,并假设该参数具有某些特定的方法或属性,那么只要传递的对象满足这些要求,它就可以正常工作,无论对象的具体类型是什么。
下面是一个简单的示例来说明鸭子类型的概念:
```python
class Duck:
def quack(self):
print("嘎嘎叫!")
def fly(self):
print("扑哧扑哧的飞!")
class Person:
def quack(self):
print("我喜欢跟鸭子一样嘎嘎叫")
def fly(self):
print("我也喜欢跟鸭子一样飞")
def make_it_quack_and_fly(obj):
obj.quack()
obj.fly()
duck = Duck()
person = Person()
make_it_quack_and_fly(duck)
make_it_quack_and_fly(person)
```
在上述示例中,我们定义了一个 `Duck` 类和一个 `Person` 类,它们都具有 `quack``fly` 方法。然后,我们定义了一个函数 `make_it_quack_and_fly`,它接受一个参数 `obj`,并调用 `obj``quack``fly` 方法。
我们可以看到,无论是 `Duck` 对象还是 `Person` 对象,只要它们具有 `quack``fly` 方法,都可以作为参数传递给 `make_it_quack_and_fly` 函数,并成功执行相应的方法。
这正是鸭子类型的思想:如果一个对象具有像鸭子一样的特征和行为(即具有 `quack``fly` 方法),那么我们可以将其视为鸭子,而无需关心对象的具体类型。
# 类的约束
写一个支付功能
```python
class QQpay:
def pay(self,money):
print('使用qq支付%s元' % money)
class Alipay:
def pay(self,money):
print('使用阿里支付%s元' % money)
a = Alipay()
a.pay(100)
b = QQpay()
b.pay(200)
```
统一一下付款方式
```python
class QQpay:
def pay(self,money):
print('使用qq支付%s元' % money)
class Alipay:
def pay(self,money):
print('使用阿里支付%s元' % money)
def pay(obj,money):
obj.pay(money)
a = Alipay()
b = QQpay()
pay(a,100)
pay(b,200)
```
如果后期添加微信支付,但是没有统一标准,换个程序员就可能写成这样。
```python
class QQpay:
def pay(self,money):
print('使用qq支付%s元' % money)
class Alipay:
def pay(self,money):
print('使用阿里支付%s元' % money)
class Wechatpay:
def fuqian(self,money):
print('使用微信支付%s元' % money)
def pay(obj,money):
print("===============")
obj.pay(money)
a = Alipay()
b = QQpay()
pay(a,100)
pay(b,200)
c = Wechatpay()
c.fuqian(300)
```
所以此时我们要用到对类的约束,对类的约束有两种:
1. 提取父类,然后在父类中定义好⽅法,在这个方法中什么都不⽤⼲,就抛⼀个异常就可以了,这样所有的⼦类都必须重写这个⽅法,否则,访问的时候就会报错。
2. 使⽤元类来描述方类,在元类中给出⼀个抽象方法,这样子类就不得不给出抽象方法的具体实现,也可以起到约束的效果。(推荐)
**方法1**
```python
class Payment:
"""
此类什么都不做,就是制定一个标准,谁继承我,必须定义我里面的方法。
"""
def pay(self,money):
raise Exception("你没有实现pay方法")
class QQpay(Payment):
def pay(self,money):
print('使用qq支付%s元' % money)
class Alipay(Payment):
def pay(self,money):
print('使用阿里支付%s元' % money)
class Wechatpay(Payment):
def fuqian(self,money):
print('使用微信支付%s元' % money)
def pay(obj,money):
obj.pay(money)
a = Alipay()
b = QQpay()
c = Wechatpay()
pay(a,100)
pay(b,200)
pay(c,300)
```
**方法2引入抽象类的概念处理
```python
from abc import ABCMeta,abstractmethod
class Payment(metaclass=ABCMeta): # 抽象类 接口类 规范和约束 metaclass 指定的是一个元类
@abstractmethod
def pay(self):pass # 抽象方法
class Alipay(Payment):
def pay(self,money):
print('使用支付宝支付了%s元'%money)
class QQpay(Payment):
def pay(self,money):
print('使用qq支付了%s元'%money)
class Wechatpay(Payment):
# def pay(self,money):
# print('使用微信支付了%s元'%money)
def recharge(self):pass
def pay(a,money):
a.pay(money)
a = Alipay()
a.pay(100)
pay(a,100) # 归一化设计:不管是哪一个类的对象,都调用同一个函数去完成相似的功能
q = QQpay()
q.pay(100)
pay(q,100)
w = Wechatpay()
pay(w,100) # 到用的时候才会报错
# 抽象类和接口类做的事情 :建立规范
# 制定一个类的metaclass是ABCMeta
# 那么这个类就变成了一个抽象类(接口类)
# 这个类的主要功能就是建立一个规范
```
# `super()` 深入了解
super 是严格按照类的继承顺序执行。
**示例1**
```python
class A:
def f1(self):
print('in A f1')
def f2(self):
print('in A f2')
class Foo(A):
def f1(self):
super().f2()
print('in A Foo')
obj = Foo()
obj.f1()
```
**示例2**
```python
class A:
def f1(self):
print('in A')
class Foo(A):
def f1(self):
super().f1()
print('in Foo')
class Bar(A):
def f1(self):
print('in Bar')
class Info(Foo,Bar):
def f1(self):
super().f1()
print('in Info f1')
obj = Info()
obj.f1()
# super() 是严格按照当前类的继承顺序执行的,不会收到过程中其他类的影响
print(Info.mro())
```
**示例3**
```python
class A:
def f1(self):
print('in A')
class Foo(A):
def f1(self):
super().f1()
print('in Foo')
class Bar(A):
def f1(self):
print('in Bar')
class Info(Foo,Bar):
def f1(self):
super(Foo,self).f1() # 这里的意思是绕过Foo从Foo的位置开始寻找下一个
print('in Info f1')
obj = Info()
obj.f1()
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Some files were not shown because too many files have changed in this diff Show More