0%

一. HTML

VScode 快捷键:

ctrl+shift+enter 在此行的上面insert # pycharm 是 ctrl + enter

ctrl+enter 在此行下面insert

ctrl + B 侧边栏显示/隐藏

tab 切换多项输入位置

  • HyperText Mark-up Language 超文本标记语言

1. 标签

  • 结构 标签不区分大小写 但是推荐小写

    • 双标签 <标签> ………..<标签>

      • html 网页的开始
      • head 头
      • title 标题 一般放到 head 里面
      • body 网页页面显示的内容内容
    • 单标签 <标签>

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>网页标题</title>
</head>
<body>
网页显示内容
</body>
</html>
  • 第一行<!DOCTYPE html>是文档声明, 用来指定页面所使用的html的版本, 这里声明的是一个html5的文档
  • html标签是告诉浏览器,整个网页是从这里开始到结束,也就是html文档的开始和结束标签
  • head标签用于定义文档的头部,是负责对网页进行设置标题、编码格式以及引入css和js文件的。
  • body标签是编写网页上显示的内容。

2. 属性

属性用于定义元素的行为和外观,以及与其他元素间的关系

属性名不区分大小写,属性值区分大小写

3. 区块

块级元素(block)通常用于组织和布局页面的主要结构和内容,如段落、标题、列表、表格等。它们用于创建页面的主要部分,将内容分隔成逻辑块

  • 块级元素通常从新行开始,并占据整行,在页面上呈现为一块独立内容块
  • 可以包含其他块级元素和行内元素
  • 常见的有divph1uloltableform

行内元素(inline)通常用于添加文本样式或为文本中一部分应用样式 。它们可以在文本中

  • 行内元素通常在同一行呈现,不会独占一行
  • 只占据其内容需要的宽度,而不是整行宽度
  • 不能包含块级元素,可包含其他行级元素
  • 常见的有spanastrongemimgbrinput
  • div 常用于创建块级容器以便于组织页面的结构和布局
  • span 常用于内联样式化文本,给文本应用样式或标记

4. 表单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>标题</title>
</head>
<body>
<h1>HTML 标签</h1>
<h1>1级标题标签</h1>
<h2>2级标题标签</h2>
<h3>3级标题标签</h3>
<h4>4级标题标签</h4>
<h5>5级标题标签</h5>
<h6>6级标题标签</h6>
<p>段落标签</p>
<p>
更改文本样式: <b>加粗</b> <i>斜体</i> <u>下划线</u> <s>删除线</s>
</p>
<ul>
<li>无序列表</li>
<li>无序列表</li>
<li>无序列表</li>
</ul>
<ol>
<li>有序列表</li>
<li>有序列表</li>
<li>有序列表</li>
</ol>
<h1>table row</h1>
<table border="1">
<tr>
<th>列标题1</th>
<th>列标题2</th>
<th>列标题3</th>
</tr>
<tr>
<td>元素1</td>
<td>元素2</td>
<td>元素3</td>
</tr>
<tr>
<td>元素4</td>
<td>元素5</td>
<td>元素6</td>
</tr>
</table>
<h1>HTML 属性</h1>
<a href="https://www.baidu.com">百度链接</a>
<hr>
<br>
<a href="https://www.baidu.com" target="_self">百度链接</a>
<img src="https://baikebcs.bdimg.com/baike-react/common/logo-baike.svg" alt="替代文本" width="100" height="100">
<img src="https://baikebcs.bdimg.com/baike-react/common/logo-baike.sv" alt="替代文本">

<h1>HTML 区块</h1>
<div class="nav">
<a href="a">链接1</a>
<a href="a">链接2</a>
<a href="a">链接3</a>
</div>
<div class="content">
<h4>文章标题</h4>
<p>文章内容</p>
<p>文章内容</p>
<p>文章内容</p>
</div>

<span>span1</span>
<span>span2</span>
<span>span3</span>

<h1>HTML 表单</h1>
<form action="后端接口">
<!-- for 用于绑定 input 常用于绑定 input 的 id -->
<label for="username">用户名:</label>
<input type="text" id="username" placeholder="请输入内容">
<br>
<label for="">密码:</label>
<input type="password" value="请输入密码">
<br>
<label>性别:</label>
<!-- 加name 从多选变单选 -->
<input type="radio" name="gender"> male
<input type="radio" name="gender"> male
<input type="radio" name="gender"> male
<br>
<label for="">爱好:</label>
<input type="checkbox">
<input type="checkbox">
<input type="checkbox"> rap

<input type="submit">
</form>

</body>
</html>

二. CSS

Cascading Style Sheets 层叠样式表

1. 引入

优先级:内联 > 内部 > 外部

  • 行内式:直接在标签的style属性中添加,方便直观但缺乏可重用性
1
<div style="width:100px; height:100px; background:red ">hello</div>
  • 内嵌式:在head标签中加入style标签,在style标签中写css代码,在一个页面内方便复用和维护,多个页面可复用性不高
1
2
3
4
5
6
7
<head>
<style type="text/css">
h3{
color:red;
}
</style>
</head>
  • 外链式:将css代码写在一个单独的css文件中,在head标签中使用link标签引入到当前页面中
1
<link rel="stylesheet" type="text/css" href="css/main.css">

2. 选择器

选择用css样式的html元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>标题</title>
</head>
<style>
/* 标签选择器 */
p {
color: blue;
font-size: 24px;
background-color: yellow;
}
/* 类选择器 */
.highlight {
background-color: cyan;
}
/* id选择器 */
#idhighlight {
background-color: aquamarine;
}
/* 通用选择器 */
* {
font-family: 'Franklin Gothic Medium';
}
/* 子元素选择器 */
.father > .son {
color:blueviolet;
}
/* 后代选择器 */
.father p {
color:aqua;
}
/* 相邻元素选择器 */
h3 + p {
background-color: red;
}
/* 伪类选择器 */
#element:hover {
background-color: purple;
/* 选中第一个子元素 :first-child
:nth-child()
:active

*/
}
/* 伪元素选择器 创建个虚拟元素并样式化
::after
::before
*/
</style>
<body>
<p>p标签</p>
<p class="highlight">hightlight类</p>
<p id="idhighlight">idhighlight</p>

<div class="father">
<p class="son">子元素</p>
<div>
<p class="grandson">grandson</p>
</div>
</div>

<p>p before h3</p>
<h3>h3</h3>
<p>p next h3</p>
<p>p next p next h3</p>

<h3 id="element">伪类选择</h3>
</body>
</html>

3. 属性

三. JS

一. 基础

1. 数据结构基本概念

随机存取(Random Access)是指能够以均等的时间访问数据结构中任意元素的能力

数据结构三要素

  • 逻辑结构
    • 线性结构
      • 一般线性表
      • 受限线性表:栈、队列、串
      • 线性表推广:数组
    • 非线性结构
      • 集合
      • 树形结构:一般树、二叉树
      • 图状结构:有向图、无向图
  • 存储结构
    • 顺序存储:把逻辑上相邻的元素存储在物理位置上也相邻的存储单元上
      • 优点:可实现随机存取
      • 缺点:只能使用相邻的存储单元,可能产生较多外部碎片
    • 链式存储:不要求逻辑上相邻物理位置也相邻
      • 优点:可以充分利用存储单元
      • 缺点:每个元素因存储指针而占用额外存储空间,且只能实现顺序存取
    • 索引存储:存储元素信息的同时建立附加索引表。索引表中每项称为索引项(一般形式为 (关键字,地址))
      • 优点:检索速度快
      • 缺点:占额外空间,增加和删除数据时也要修改索引表,花费更多时间
    • 散列存储(哈希存储):根据元素关键字直接算出元素的存储地址
      • 优点:检索、增加和删除数据操作快
      • 缺点:如散列函数不好,可能出现元素存储单元冲突,解决冲突会增加时间和空间开销
  • 运算
    • 定义:针对逻辑结构,指出运算的功能
    • 实现:针对存储结构,指出具体操作步骤

1. 逻辑结构

逻辑结构描述了数据元素之间的逻辑关系,通常有以下几种:

  1. 集合结构(Set Structure)(非线性)
    • 数据元素之间仅有“属于同一个集合”的关系,元素之间无顺序关系。
  2. 线性结构(Linear Structure)
    • 数据元素之间是一对一的关系,通常具有顺序性。
    • 典型例子:数组、链表、栈、队列。
  3. 树形结构(Tree Structure)(非线性)
    • 数据元素之间是一对多的层次关系。
    • 典型例子:二叉树、平衡树、B树。
  4. 图形结构(Graph Structure)(非线性)
    • 数据元素之间是多对多的关系,可以用节点和边表示。
    • 典型例子:无向图、有向图、加权图。

2. 存储结构

存储结构是指数据在计算机内存中的存储方式,通常有以下几种:

  1. 顺序存储结构(Sequential Storage Structure)
    • 数据元素按顺序存储在连续的存储单元中,通常通过数组实现。
    • 适用于随机存取。
  2. 链式存储结构(Linked Storage Structure)
    • 数据元素存储在任意存储单元中,通过指针指示数据元素之间的逻辑关系,通常通过链表实现。
    • 适用于顺序存取。
  3. 索引存储结构(Indexed Storage Structure)
    • 在存储数据元素的同时,还附加了索引表,通过索引表可以快速找到数据元素。
    • 常用于数据库和文件系统。
  4. 散列存储结构(Hashed Storage Structure)
    • 通过散列函数将数据元素映射到存储位置,适用于查找操作频繁的应用场景。
    • 典型例子:哈希表。

组合与选择

不同的逻辑结构可以采用不同的存储结构实现。选择哪种存储结构主要取决于操作的效率需求。例如:

  • 数组(线性结构)可以通过顺序存储实现,提供高效的随机访问。
  • 链表(线性结构)通过链式存储实现,插入和删除操作较为高效。
  • (树形结构)可以通过链式存储实现,支持动态插入和删除。
  • (图形结构)可以通过邻接矩阵(顺序存储)或邻接表(链式存储)实现。

举例:

  • 数组:线性结构 + 顺序存储
  • 链表:线性结构 + 链式存储
  • 栈和队列:线性结构 + 顺序存储或链式存储
  • 二叉树:树形结构 + 链式存储
  • 哈希表:集合结构 + 散列存储

Python对应

1. 列表(List)

  • 逻辑结构:线性结构
  • 存储结构:动态数组(顺序存储)
  • 特点:支持随机存取,元素可以通过索引直接访问;动态调整大小;支持高效的插入和删除操作。
1
my_list = [1, 2, 3, 4, 5]

2. 元组(Tuple)

  • 逻辑结构:线性结构
  • 存储结构:动态数组(顺序存储)
  • 特点:类似于列表,但不可变;一旦创建,元素不能修改;支持随机存取。
1
my_tuple = (1, 2, 3, 4, 5)

3. 字典(Dictionary)

  • 逻辑结构:集合结构
  • 存储结构:哈希表(散列存储)
  • 特点:键值对存储,通过键快速查找值;不保证顺序;动态调整大小。

从python3.7开始改为有序

1
my_dict = {'a': 1, 'b': 2, 'c': 3}

4. 集合(Set)

  • 逻辑结构:集合结构
  • 存储结构:哈希表(散列存储)
  • 特点:无序不重复元素集;支持快速查找、插入和删除操作。
1
my_set = {1, 2, 3, 4, 5}

5. 字符串(String)

  • 逻辑结构:线性结构
  • 存储结构:顺序存储
  • 特点:不可变字符序列;支持随机存取。
1
my_string = "hello"

6. 队列(Queue)

Python中没有内置的队列结构,但可以使用collections.deque来实现。

  • 逻辑结构:线性结构
  • 存储结构:链式存储
  • 特点:双端队列,可以在两端快速插入和删除。
1
2
from collections import deque
my_queue = deque([1, 2, 3, 4, 5])

7. 栈(Stack)

Python中没有内置的栈结构,但列表可以用作栈。

  • 逻辑结构:线性结构
  • 存储结构:动态数组(顺序存储)
  • 特点:LIFO(后进先出)结构,使用appendpop操作。
1
2
3
my_stack = [1, 2, 3, 4, 5]
my_stack.append(6) # 入栈
my_stack.pop() # 出栈

8. 链表(Linked List)

Python标准库中没有内置的链表,但可以自定义实现。

  • 逻辑结构:线性结构
  • 存储结构:链式存储
  • 特点:动态调整大小,插入和删除操作高效。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Node:
def __init__(self, value=None):
self.value = value
self.next = None

class LinkedList:
def __init__(self):
self.head = None

def append(self, value):
new_node = Node(value)
if not self.head:
self.head = new_node
else:
current = self.head
while current.next:
current = current.next
current.next = new_node

my_linked_list = LinkedList()
my_linked_list.append(1)
my_linked_list.append(2)

9. 优先队列(Priority Queue)

可以使用heapq模块实现。

  • 逻辑结构:树形结构
  • 存储结构:二叉堆(顺序存储)
  • 特点:支持快速获取和删除最小(或最大)元素。
1
2
3
4
import heapq
my_priority_queue = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0]
heapq.heapify(my_priority_queue) # 转换为堆
heapq.heappop(my_priority_queue) # 删除并返回最小元素

2. 算法与算法评价

算法定义:对特定问题求解步骤的一种描述,是指令的有限序列,每条指令表示一个或多个操作

度量:时间复杂度和空间复杂度

一个语句的频度: 该语句在算法中被重复执行的次数

时间复杂度

  • 计算:取 算法中所有语句的频度之和中增长最快的项,将其系数置为1作为时间复杂度的度量

  • 一般是考虑最坏情况下的时间复杂度,确保算法运行时间不超过它

空间复杂度

  • 算法所消耗的存储空间

二. 线性表

三. 栈、队列和数组

四. 串

五. 树

六. 图

七. 查找

八. 排序

毛坯内孔Φ43改Φ49

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
M08
M03 S500 T101
G99
G0 Z3 X50.5
G1 Z1 F0.2
X46 W-2
Z-85
G0 U-1
G0 Z3
X49
G1 Z-85
G0 U-1
G0 Z200
T303
S300 M03
G0 X45 Z1
Z-63
G75 R0
G75 X54 Z-84 P1000 Q3500 F0.03
G0 Z260
M05
M09
M30

毛坯内孔Φ33改Φ35

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
M08
M03 S500 T101
G99
G0 Z3 X50.5
G1 Z1 F0.2
X35 W-2
Z-80
G0 U-1
G0 Z200
T303
S300 M03
G0 X45 Z1
Z-83
G75 R0
G75 X40 Z-79 P1000 Q3500 F0.03
G0 Z260
M05
M09
M30

一. 基础

  • 编译型语言
    • 定义:在程序运行之前,源代码会先经过编译器将其转换为机器语言的形式,生成可执行文件。在运行时,计算机直接执行该可执行文件,无需再进行翻译或解释。C、C++ 和 Java 等语言属于编译型语言。
    • 白话定义:只有第一次执行的时候需要编译,之后如果没修改源代码就不会再编译了
    • 示例:C++、Go、Java
  • 解释型语言
    • 定义:在程序运行时,源代码逐行解释并执行。解释器将源代码转换为机器语言,并逐行执行代码。解释型语言的代码无需编译,可以直接运行,但由于需要逐行解释执行,相对于编译型语言,解释型语言的执行速度通常较慢。
    • 白话定义:每次运行都会从第一行进行编译,编译一行执行一行
    • 示例:Python、JavaScript、Ruby
  • 静态语言
    • 如果在编译时知道变量的类型, 则为静态类型语言. 优点是编译器可以执行各种检查, 程序没跑起来就能找到一些小错误
    • 示例:C++、Go、Java
  • 动态语言
    • 定义:在运行期间过才去做数据类型检查的语言, 在用动态语言编程时, 不用给变量指定数据类型, 该语言会丰第一次赋值给变量时, 在内部将数据类型记录下来, 不必每次都指定类型
    • 示例:Python, PHP, JavaScript
  • 强类型
    • 定义:为所有变量指定数据类型称之为”强类型”, 不允许两种不同类型的变量相互操作
    • 示例:Python, Go, Java, C#
  • 弱类型
    • 定义:也称为弱类型定义语言, 一个变量可以赋不同数据类型的值, 允许将一块内存看做多种类型, 比如直接将整型变量与字符变量相加
    • 示例:C, C++, JavaScript, PHP

1. 基中基

在 Python 中,要实现并发编程,通常需要使用特定的库或框架,如 threading、multiprocessing、asyncio 等。这些库提供了线程、进程、协程等抽象,用于实现并发任务的调度和执行。Python 的标准库提供了一些基本的并发工具,但在处理大规模并发时可能需要使用更高级的第三方库。

而在 Go 中,原生的 Goroutine 和调度器提供了并发编程的内置支持,不需要依赖额外的框架或库。Goroutine 是 Go 语言提供的一种轻量级的并发执行单元,通过使用关键字 go 来启动 Goroutine,无需额外的线程或进程管理。调度器会自动将 Goroutines 调度到可用的系统线程上执行,并进行高效的并发调度。

由于 Goroutines 是 Go 语言内部的机制,Go 运行时系统负责管理 Goroutines 的创建、调度和销毁,使得并发编程在 Go 中变得非常简单和高效。此外,Go 还提供了通道(Channel)作为 Goroutines 之间进行通信和同步的机制,进一步简化了并发编程的复杂性。

总结: Go 在语言本身就提供了原生的并发支持,使用 Goroutine 和调度器实现轻量级的并发编程。相比之下,Python 需要借助额外的库或框架来处理并发任务,并且在处理大规模并发时可能需要使用更高级的工具。

Goroutine 是 Go 语言中并发编程的核心概念之一,它是一种轻量级的执行单元。每个 Goroutine 都是一个独立的执行线程,可以与其他 Goroutines 并发地执行。

Goroutine 可以看作是一种比传统线程更轻量级、更高效的并发编程抽象。与传统的线程相比,Goroutine 的创建和销毁开销很小,可以高效地创建大量的 Goroutines。这使得在 Go 语言中可以方便地使用大量的 Goroutines 来实现并发任务,而不会消耗过多的系统资源。

使用 Goroutines 的一个重要特点是它们之间的切换开销非常低。由于 Goroutines 是由 Go 调度器(Scheduler)管理的,调度器可以在 Goroutines 之间非常高效地进行切换,以实现并发执行。当一个 Goroutine 遇到阻塞操作(例如等待 I/O 完成或休眠)时,调度器会自动切换到其他可执行的 Goroutine,以充分利用系统资源。

另一个重要特点是 Goroutines 之间可以通过通道(Channel)进行通信和同步。通道是 Goroutines 之间安全地传递数据的机制,可以用于在不同的 Goroutines 之间进行数据交换和共享。

通过使用 Goroutines,开发者可以将任务并发地划分为多个独立的执行单元,每个执行单元由一个 Goroutine 来处理。这样可以简化并发编程的复杂性,并充分发挥多核处理器的性能优势。

总结: Goroutine 是 Go 语言中的一种轻量级并发执行单元,它提供了高效的并发编程抽象。通过使用 Goroutines,开发者可以方便地实现并发任务,并充分利用多核处理器的性能。同时,通过通道的机制,Goroutines 可以安全地进行通信和同步,实现高效的并发编程。

1.1. 编译器下载安装

官网:https://golang.org/dl/

  • mac
    • 下载*.pkg直接安装 默认安装目录 /usr/local/go
    • 添加**/usr/local/go/bin**目录到环境变量 export PATH=$PATH:/usr/local/go/bin

1.2. Hello World

1
2
3
4
5
6
7
8
package main import "fmt" func main() {   
fmt.Printf("Hello, Go\n")
} /* main 是入口, 名固定不能改 */

$ go build hello.go
$./hello

>> Hello, Go
  • package main 包声名, 必须在源文件非注释第一行声名这个文件属于哪个包, 每个Go程序都必须有一个名为 main 的包
  • import “fmt” 导入包, 表示这个程序需要”fmt”包, 注: “fmt” go的格式化输入输出包
  • func main() {} go的第一个程序都必须有一个main函数, 这是程序入口, go程序执行顺序: init -> main -> 其他, 没有init就main最先执行

*注: 当标识符以大写字母开头***(包括常量、变量、类型、函数名、结构字段等等)表示可被外部包使用, 以小写字母开头表示不可被外部包使用(但对包内部是可用的)

1.3. 基础

  • 注释

    • // 单行注释
    • /* 多行注释 */
  • 标识符: 由字母, 数字, 下划线构成, 不能数字开头, 不能是go的关键字

  • 关键字:

官方文档:https://go.dev/ref/spec#Keywords

关键字 涉及功能
select, case, if, else, switch, default 逻辑判断
异常捕获
for, continue, break, return 循环
import, package, 导包
func, interface 定义
异步
变量空间
, select, defer, go, map, struct, chan, goto, range, type, const, fallthrough, var 其他
  • 预定义标识符:
标识符 含义
bool, true, false 布尔类型, 真,假
byte alias for uint8
rune alias for int32
int, int8, int16, int32, int64 整数类型关键字, 有符号整数类型的不同位数
uint, uint8, uint16, uint32, uint64 无符号整数类型的关键字, 无符号整数类型的不同位数
uintptr 用于存储指针值的整数类型
float32, float64 浮点数类型的不同精度
complex32, complex64 复数类型的不同精度
string 字符串类型的关键字
error 表示错误类型的接口
make 用于创建切片、映射和通道的内建函数
new 用于分配内存的内建函数
append 用于向切片追加元素的内建函数
len 用于返回切片、数组、字典、字符串或通道的长度的内建函数
cap 对于切片或容量为数组、切片或通道的内建函数,返回其容量

1.4. 数据类型

  • 布尔: true / false
  • 数字: uint8 / uint16 / uint32 / uint64 (无符号 0-255-65535-…) / int8 / int16 / float32 / float64 ( -128-127 -32768 - 32768 .. ..)
  • 字符串: utf-8
  • 派生类型:

一. 基础

  • 编译型语言
    • 定义:在程序运行之前,源代码会先经过编译器将其转换为机器语言的形式,生成可执行文件。在运行时,计算机直接执行该可执行文件,无需再进行翻译或解释。C、C++ 和 Java 等语言属于编译型语言。
    • 白话定义:只有第一次执行的时候需要编译,之后如果没修改源代码就不会再编译了
    • 示例:C++、Go、Java
  • 解释型语言
    • 定义:在程序运行时,源代码逐行解释并执行。解释器将源代码转换为机器语言,并逐行执行代码。解释型语言的代码无需编译,可以直接运行,但由于需要逐行解释执行,相对于编译型语言,解释型语言的执行速度通常较慢。
    • 白话定义:每次运行都会从第一行进行编译,编译一行执行一行
    • 示例:Python、JavaScript、Ruby

1. 基中基

1.1. 关键字

关键字 涉及功能
True, False, None 真 假 空
if, elif, else, and, not, assert, or, is, in 逻辑判断
try, except, finally, raise 异常捕获
for, while, continue, break, return 循环
from, import 导包
def, class, lambda 定义函数、类、匿名函数
async, await, yield 异步
global, nonlocal 变量空间
as, del, pass, with, type 重命名、删除、PASS、上下文、类型

1.2. 逻辑运算符

  • and: 一假即假
  • or: 一真即真(注意短路逻辑: 1 or 1 / 0 不会报错,程序只会走第一个1然后就会执行下一行)
  • not: 真假取反

1.3. 比较运算符

  • // 取整除 9 // 4 = 2

  • % 取余 9%4 = 1

  • ^ 取异或 把数字转化为二进制取 0 1 为 1 ,1 0 为 1, 1 1 为1, 0 0 为 0

  • **

1.4. 比较运算符

  • > < == != >= <=

  • 返回的结果都是bool类型, True表示条件成立, False表示不成立.

1.5. 输入输出

  • print() 调用底层的sys.stdout.write方法,前往控制台打印输出

  • input() 无论输入什么类型的数据,都会转化为字符串

1.6. 循环

  • 循环语句结合else语句使用,当循环语句执行了break表示非正常结束,else语句不会执行,否则会执行else语句

  • 循环语句里有break/return时, break/return执行了, else语句就不会执行

  • break/continue 只影响一层循环

1.7. if三目运算

  • a if a > b else b 条件成立取a,不成立取b

  • if 除了判断bool类型, 还可以判断

    • 容器类型(字符串, 列表, 元组, 字典, 集合, range(), bytes()) 判断是否有数据
    • 非零即真(只要不是0, 条件都成立)
    • None 条件不成立, not none, 表示非空, 条件成立

1.8 注释

  • 单行 # 这是注释
  • 多行 """ 这是多行注释 """

2. 容器

2.1. 字符串

字符串用join比直接+高效原因:在 Python 中,字符串是不可变类型,这意味着一旦我们创建了一个字符串对象,它就不能被修改或更新。因此,每次使用 + 操作符拼接字符串时,都会创建一个新的字符串对象,并将之前字符串对象的内容复制到新的字符串对象中,这个复制的操作会带来额外的内存分配和内存拷贝的开销,特别是在需要拼接大量的字符串时,会消耗大量的系统资源,导致程序运行缓慢。 相对地,使用 join() 方法的拼接字符串操作则更加高效。join() 方法本质上是将多个字符串通过指定的分隔符拼接在一起,而与此相关的方法包括 split()replace()format() 等方法,它们均采用类似的算法。在 join() 方法的实现中,Python 的解释器会先在内存中分配一个足够大的单个字符串缓冲区,然后扫描要拼接的字符串,将其复制到单个缓冲区中,并在不同字符串之间插入指定的分隔符。这种方法可以有效地避免频繁创建或拷贝字符串对象,从而提高拼接字符串的效率。 此外,join() 方法还可以接受一个可迭代对象作为参数,如列表,元组,生成器等,它们会返回一个字符串,其中可迭代对象按照指定的分隔符进行拼接。这种方式具有更高的灵活性和实用性,因为它可以用于拼接任意数量的字符串,而且可以用于迭代较大的数据集合,而不会导致系统资源消耗过多。

  • 定义:用单引号、双引号、三引号均可,仅三引号可以换行
  • 切片:[开始位置:结束位置:步长] 左闭右开
    • [::-1] 字符串快速逆置
1
2
3
4
5
6
7
8
my_str = "python"
isinstance(my_str, str) # True

my_age = 18
isinstance(my_age, int) # True

my_age = str(my_age) # "18"
isinstance(my_age, str) # True

常用

  • .find(要查询的字符, 查询开始索引, 查询结束索引) 左闭右开,查空返回 -1
  • .replace(str1, str2, 替换次数) 字符串替换,替换次数默认-1全部替换
  • .split(str1, 切片次数) 字符串以 str1 为分隔符进行切片,切片次数默认-1全部切,返回分隔后的列表
  • .lower() 英文转小写 upper()转大写

2.2. 列表

定义[]list()

列表推导式: 列表中可以包含条件语句,表示筛选符合条件的元素。在这种情况下,底层逻辑还会涉及以下步骤:

  1. 创建一个空列表。
  2. 对于列表推导式中的每个元素表达式,按照迭代顺序依次执行以下步骤:
    1. 在当前作用域中计算元素表达式的值。
    2. 如果条件表达式的值为真,则将计算得到的值添加到列表中。
  3. 返回最终生成的列表。

  • .append 加整个对象,加字典也是整个字典
  • .extend 打散加进去,加字典默认加字典的key
  • .insert(index, obj) 在指定位置前面加, 是整个加进去的

  • del list[::] 可以和切片一起操作根据索引删除列表元素

  • .pop() 只能根据单独索引删除对应元素, 默认删最后一个, 会返回删除的元素

  • .remove() 指定元素删除

list[index] = "new" 可以用切片同时更改多个数据,注意下面示例

1
2
3
4
5
6
7
8
9
10
>>> my_list = [1, "2", 3]
>>> my_list[0] = "1"
>>> my_list
['1', '2', 3]
>>> my_list[:0] = "456"
>>> my_list
['4', '5', '6', '1', '2', 3]
>>> my_list[:2] = [1, 2]
>>> my_list
[1, 2, '6', '1', '2', 3]

  • in 判断是否存在,只可判断最外层数据,内层还有容器要索引进去查
  • .index(obj, start, end) 左闭右开,找不到报ValueError
  • .count(obj)

排序

  • .sort(reverse=False) 默认是从小到大, reverse=True 改为从大到小

  • .reverse() 将列表逆置, 与上面的reverse=True不同

2.3. 元组

定义tuple()(1, 2) 如果只有一个元素, 逗号不能省略, 有序

2.4. 字典

定义

  • dict(){"a": 1, "b": 2}
  • zip组合keyvalue 生成字典
1
2
3
4
5
6
>>> keys = ["a", "b"]
>>> values = [1, 2]

>>> my_dict = dict(zip(keys, values))
>>> my_dict
{'a': 1, 'b': 2}

增:

  • dict[key] = value

删:

  • del dict[key] 必须要有key数据

  • .pop(key, default) 必须要有key数据, 会返回所删项的value, 如果字典里没有这个key就会返回default

  • .popitem() takes no arguments, return the last item as a tuple()

  • .clear() 清空

改:

  • dict[key] = value 和增加元素相同, 所以当原本没有此key就变成增加元素了

查:

  • dict[key] 如果不存在会报错

  • .get(key, default) 如果不存在则返回default 不写default找不到会返回None 不会报错

合并:

  • dict1.update(dict2) 把字典2的每个键值对数据合并到字典1中, 如果有重复的则更新1的内容

遍历:

  • for key in dict.keys()
  • for values in dict.values()
  • for item in dict.items() item是个tuple
  • for key, value in dict.items() 遍历 + 拆包

排序:

  • sorted(d.items(), key=lambda x : x[0/1])

2.5. 集合

  • 集合是一个容器类型, 可以存储多个数据, 但是多个数据不能重复
  • 集合只能存储不可变类型数据, 也就是: 数字, 字符串, 元组 同字典的key
  • 空集合不能使用**{}**来表示, {}是字典, 创建时用 set() 来创建空集合
  • 遍历集合不能用通过下标, 可以用for遍历, 可迭代对象

特点:

  1. 无序, so集合不能通过索引获取数据和通过索引修改数据
  2. 数据不能重复,数据是唯一的
  3. 可变类型

操作符:

^ 补 {1,2} ^ {2, 3} => {1, 3}

& 交 {1,2} & {2, 3} => {2, }

| 并 {1,2} | {2, 3} => {1, 2, 3}

- 差 {1,2} - {2, 3} => {1, }

增:

.add() 重复的数据只保留一个

删:

.remove(value) 指定数据删除

2.6. 公共方法

运算符 Python 表达式 结果 描述 支持的数据类型
+ [1, 2] + [3, 4] [1, 2, 3, 4] 合并 字符串、列表、元组
* [‘Hi!’] * 4 [‘Hi!’, ‘Hi!’, ‘Hi!’, ‘Hi!’] 复制 字符串、列表、元组
in 3 in (1, 2, 3) True 元素是否存在 字符串、列表、元组、字典
not in 4 not in (1, 2, 3) True 元素是否不存在 字符串、列表、元组、字典

python内置函数

  • len() 获取容器的元素数量

  • max() 返回容器中元素最大值 | 类似的 min() 最小

  • enumerate()

    • 在使用for循环的时候可以遍历数据又可以遍历索引, 列表/元组/字典都可以使用
    • 当用于字典遍历所有items时返回的是一个索引数据和一个元组

2.7. 可变不可变类型

不可变类型

  • 定义:不允许在原本内存空间基础上修改数据, 修改数据后内存地址会发生变化
  • 示例:列表, 字典, 集合

可变类型

  • 定义:允许在原本内存空间修改数据,修改后一是在原有内存空间基础上修改数据内存不变,二是重新赋值内存地址可能发生变化
  • 示例:数字, 字符串, 元组

3. 函数

程序中定义的变量都是保存在内存中的, 局部变量也是, 当函数执行结束后局部变量都会销毁,内存释放

3.1. 文档说明

1
2
3
4
5
6
7
8
9
10
11
12
13
def show():
""" func docs """
# ''' others '''
print("life is short i use python")


show()
help(show)
# life is short i use python
# Help on function show in module __main__:
#
# show()
# func docs

3.2. 返回值

  • 函数不写return 取函数返回值时会取到 None
  • 在多层循环中 return 可把多层全部终止,break 只能终止一层

3.3. 局部变量&全局变量

  • 局部变量:作用域仅在函数体内部, 随着函数执行结束会销毁

  • 全局变量:在函数体内外都生效,不会随着函数执行结束会销毁

  • global 本质是表示: 要修改全局变量的内存地址, 所以只有不可变类型需要global

  • 在函数内部使用全局变量时, 要先声明 global 全局变量, 如果是可变类型就不需要了

  • 对于操作全局变量的数据, 如果是通过重新赋值来完成的, 那么必须加上global关键字

  • nonlocal 使用场景是函数嵌套时,内层函数要使用外层函数的变量或参数

1
2
3
4
5
6
7
8
9
10
11
12
13
a = 1
b = [1]

def t():
global a
a = 2
b.append(2)
print(a, b)

t()
print(a, b)
# 2 [1, 2]
# 2 [1, 2]

3.4. 函数参数

  • 分类

    • 位置参数:按照位置顺序依次给函数的参数传值

    • 关键字参数:按照关键字名给函数的参数传值

    注:前面按位置, 后面按关键字, 如果前面用了关键字参数, 后面不能再使用位置参数, 只能使用关键字参数传参

  • 不定长参数: 函数的参数个数不确定, 可能0个, 可能多个

    • 不定长位置参数, *args, 调用函数时所有位置参数都封装成元组, 赋值给args
    • 不定长关键字参数, **kwargs, 调用函数时所有关键字参数都封装成字典, 赋值给kwargs

    注: *args**kwargs 这两个参数名可以修改, 但一般不改, 大家习惯了

1
2
3
4
5
def show(name, *args, age=18, **kwargs):
print("name:", name, "age:", age, "args:", args, "kwargs:", kwargs)
show("李四", 1, 2, "a", a=1, b=2, age=20)

# name: 李四 age: 20 args: (1, 2, 'a') kwargs: {'a': 1, 'b': 2}
  • 拆包:使用不同变量保存容器类型中的每个数据,对应的变量和数据数量要保持一致致

    • 容器类型如:字符串, 列表, 元组, 字典, range, 集合(set) 都可以利用拆包, 容器类型可以使用变量保存不同的数据

    • *my_tuple: 对元组/列表进行拆包, 也就是把元组/列表里每个数据按位置参数进行传参

    • **my_dict: 对字典进行拆包, 也就是把字典里面的每一个键值对按关键字的方式进行传参

3.5. 匿名函数

定义:没有名字的函数, 就是匿名函数, 匿名函数返回值不需要 return,用 lambda 定义

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

1
2
3
>>> my_func = lambda a: print(a)
>>> my_func("hello world")
hello world

3.6. 常见函数定义

  • 递归函数: 在一个函数内调用的是函数本身, 这样的函数称为递归函数
  • 函数嵌套:python中, 可以在函数内部再定义一个函数, 称为函数的嵌套(例:装饰器)
  • 高阶函数:函数的参数或者返回值是一个函数类型, 那么这样的函数就叫高阶函数(例:装饰器)

4. 文件

4.1. 常识

  • 在windows的python解释器里面, 打开文件默认的编码格式是 gbk

  • 在mac和linux的解释器里面, 打开文件默认的编码格式是 utf-8

  • utf-8 一个汉字占用三个字节, 一个字母占1个字节

  • 编码: .encode("utf-8")

  • 解码: .decode("utf-8")

5. 面向对象

面向对象就是对面向过程的封装

面向对象的三大特性

封装: 把属性和方法放到类里面的操作就是封装, 封装可以控制属性和方法的访问权限

继承: 子类可以使用父类的方法或者属性, 提高了代码的复用性, 注意点: 父类的功能满足不了子类的需要, 重写父类的方法

多态:

  • 对象调用同一个方法会出现不同的表现形式(表现结果)
  • 多态的好处, 代码的可扩展性强, 代码兼容性强, 不关系类型, 只关系对象是否具有指定功能方法

5.0. 类的实例化过程

  1. 内存分配:Python为对象分配所需的内存空间。

  2. 初始化实例:调用类的__init__方法来初始化实例。__init__方法是类中一个特殊的方法,它在实例化对象时被自动调用。

  3. 创建对象引用:创建一个指向该实例的引用,允许你通过变量来访问该实例。

  4. 执行__new__方法(可选):如果定义了__new__方法,它将在__init__之前被调用。__new__方法负责创建实例。

  5. 返回实例:返回一个指向新实例的引用,使你可以使用该引用来操作该实例。

总结起来,实例化一个类时,Python会为对象分配内存空间,然后调用__init__方法初始化实例,最后返回新实例的引用。这样,就可以通过该引用来操作和访问该实例的属性和方法。

5.1. 魔法方法

定义:方法名前后都有两个下划线, 这样的方法称为魔法方法, 魔法方法具有一定的特殊功能

常见魔法方法

  • __new__ 分配内存的方法, 在__init__之前调用

  • __init__, 在创建一个对象时默认被调用,不需要手动调用, 可以在此方法内添加对象属性

  • __del__, 创建对象后, python解释器默认调用__init__方法, 当删除对象时, python解释器也会默认调用__del__方法

  • __str__, 当使用print输出对象的时候, 默认打印对象的内存地址, 如果类定义了此方法,那么就会打印从在这个方法中return的数据, 此方法返回必须是字符串类型, 作为这个对象的描述信息.

  • __slots__方法, 限定自定义类型的的对象只能绑定某些属性, 只对当前类对象生效, 对子类并不起任何作用

  • __enter__表示上文方法,需要返回一个操作文件对象

  • __exit__表示下文方法,with语句执行完成会自动执行,即使出现异常也会执行该方法

对象销毁的方式:

  • 程序运行结束, 程序中所使用对象都要在内存中销毁

  • 当对象没有变量使用的时候, 该对象就会被销毁, 引用计数为0时会销毁

5.2. 继承

语法class 子类名(父类名): / class 子类名(父类1, 父类2):

说明:子类复用父类里面的属性或方法, 提高代码的复用性, 能够使用父类里面的方法或者属性, 包括__init__方法

常识:

  • 父类也称为基类, 子类也称为派生类

  • 单继承:子类只继承一个父类

  • 多继承:子类继承多个父类, 可以使用多个父类里的方法

    • .mro() 方法可查看类的继承顺序
  • 多层继承:只要有类继承关系, 子类对父类及所有上层父类的方法都可以使用

  • 继承后方法的调用:先从本类查找, 依次往后查找, 找到就停, 如果没找到对应方法, 程序崩溃

  • 重写:子类继承父类, 对父类的功能方法进行重新改造(子类方法名要和父类方法名相同)

子类调用父类方法:

  • self.方法() : 当子类没有这个方法时候才可以用, 子类有相同方法时用父类的类名.方法(self)

  • 父类的类名.方法(self) : 类名调用对象方法, 需要自己手动传入self参数, 对象调用对象方法, 不需要传self参数

  • super().方法() : super是一个类, super()表示创建了一个父类对象, 通过__init__方法给对象添加属性

    • 完整写法 super(子类名称, self).父类方法() : 指定类名, 根据子类获取对应父类

    • super本质: 根据指定类 在类的继承顺序**类名.mro()**中获取下一个类, 然后调用下一个类的方法, 如果是单继承, super的调用可以认为是调用的是父类的方法

5.3. 私有权限

  1. 在属性名和方法名前加两个下划线
  2. 私有属性和私有方法只能在本类中使用, 不能在类外部使用
  3. 其实私有属性及方法只是对属性名和方法名进行了包装, 把名字进行了修改
  4. 总结: 私有属性和方法 的包装格式: 在属性名和方法名前面加 _本类类名__
  5. 子类无法使用父类的私有属性和私有方法, 也是把名字做了包装, 同上
  6. 给对象添加私有属性只能在__init__方法里面完成

5.4. 类属性和实例属性

  • 类属性: 在类的内部init方法外部定义的属性, 类属性属于类

    • 私有类属性: 类名前加两个下划线, 也是把名字做了包装, 实际同对象的私有
  • 实例属性: 在init方法内部定义的属性称为实例属性, 实例属性属于实例 (实例 == 对象)

  • 类不能访问对象属性, 但是对象可以访问类属性(对象不能修改类属性, 只能类去修改)

  • 总结: 对象属性的操作是由对象完成, 类属性操作由类来完成, 只不过对象可以访问类属性(也可以用 self.class.类属性 修改类属性, 用class找到类然后是类去修改类属性), 类不能访问对象属性

5.5. 类中方法的种类

  • 实例方法: 方法的第一个参数是self, 那么这样的方法就是对象方法, self表示当前对象, 实例方法, 类不能调用

  • 类方法(修改和获取类的私有属性时使用): 方法第一参数cls并且还需要使用@classmethod的关键字进行修饰, cls表示当前类, 类方法可以获取和修改类的私有属性, 类方法类和对象都可以调用

  • 静态方法: 方法里没有self和cls参数并且还需要使用@staticmethod的关键字进行修饰

6. 异常&模块

6.1. 异常

异常捕获 try...except...

  • try 表示尝试执行可能出问题的代码, except 表示如果代码出现异常, 进行捕获 as e:
  • 捕获异常类型的通用写法就是用Exception, 因为大多数异常类型都是最终继承Exception
  • BaseException 可以捕获任何异常

try...except...else...finally

  • exceptelse 互斥, finally不管有没有异常都执行

异常的传递

  • 当执行代码的时候遇到错误, 首先判断当前代码块对异常进行捕获, 如果没有, 那么再把异常一层一层往外传递, 如果外界的都没对异常的捕获, 程序就会崩溃, 如果有异常捕获, 就不会崩溃了

自定义异常

  • class定义自定义异常类, 必须继承Exception或者BaseException才可以
  • 抛出自定义异常使用关键字raise
  • 注意:raise只能抛出异常类的对象

6.2. 模块

通俗理解模块就是一个 .py 文件, 模块里面可以定义具体的功能代码(类, 函数, 全局变量, 匿名函数等等)

1
2
3
4
# 查看导入模块的搜索路径 
import sys

print(sys.path)
  • 模块好比一个工具箱, 模块里的每一个具体代码好比一个工具
  • 模块的命名规则和变量名的命名规则一样 使用下划线命名
  • 模块名的组成和变量名组成一样, 字母, 数字, 下划线开头, 如果以数字开头, 这个模块就不能使用了

导入模块的两种方式

  • import 模块名 as 别名
  • from 模块名 import 功能代码(函数, 类, 全局变量) as 别名
  • from 模块名 import * 导入模块里所有功能代码 一般不这样使用

导入模块注意点

  • 自制的模块名不要和系统的模块重名
  • 使用from 模块名 import 功能, 在当前模块不要再定义导入功能的代码, 否则会覆盖之前导入功能代码

自制模块

  • all 指定导入对应的功能代码 all = [类名, 类名…..] all定义针对外界使用from 模块名 import * 导入, 只能导入all里面指定的功能代码

主模块名字: main

导入的模块名字: 就是模块原本的名字

包: 通俗理解只要文件夹里包含一个__init__.py文件, 那么这个文件夹就是包

  • 包的作用: 包是用来管理不同模块的

  • 模块的作用: 模块是用来管理不同功能代码的

  • 包名的命名规则和变量名一样, 使用下划线命名法

  • 包名的组成和变量名的组成一样, 数字, 字母, 下划线 不能数字开头

  • 包的特点:

    • 包里面有一个__init__.py文件,这是包的初始化文件, 当且仅当第一次导入包的时候会执行这个文件
    • init.py 其实就是包的象征文件
    • init.py 可以控制模块的导入行为
    • init.py 可以定义类, 函数, 全局变量等代码

包的导入目的使用包里面的模块

格式:

  • import 包名 指定导入包, 用包调用模块, 使用模块中的功能代码 第一次导入包的时候会默认调用__init__.py
  • import 包名.模块名
  • from 包名 import 模块名
  • from 包名 import 模块名 as 模块别名
  • from 包名 import * 默认不是导入包里所有模块, 需要在__init__.py中使用__all__去指定

二. 高级

1. 多任务

1.1. 常识

  • 多任务的目的:充分利用CPU资源,提高执行效率

  • 时间片:内核分配给程序执行的一小段时间,这个时间内进程拥有cpu资源

  • 同步:协同步调,按预定的先后次序进行运行。如:你说完,我再说

  • 进程、线程同步:可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行; B执行,再将结果给A; A再继续操作

  • 进程状态: 等待状态不占用时间片, 即使时间片有剩余也会退出不占用CPU资源, 只有运行状态才占用CPU资源

    image-20230704132442670

1.2. 执行形式

  • 并发:在一个时间段内,交替的执行多个任务,任务数 > CPU核心数,时间片轮转
  • 并行:在一个时间点,多核CPU同时执行多个任务,任务数 < CPU核心数
  • 一般情况下,并发和并行同时存在

1.3. 实现方式:进程

  • 进程是操作系统进行资源(CPU、内存)分配的基本单位
  • 程序中至少有一个进程,这个进程称为主进程
  • 主进程会等待所有子进程执行结束再结束
    • 如果子进程没执行完,主进程会一直等待,此时如果子进程进入死循环会导致主进程无法退出解决办法:
      • 设置子进程为守护主进程,主进程退出时子进程直接销毁: sub_process.daemon = True
      • 主进程退出前先销毁子进程: sub_process.terminate()
  • 每个进程中至少有一个线程,这个线程称为主线程
  • 进程间不共享全局变量
  • 进程之间执行也是无序的,由操作系统调度决定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 进程创建子进程时程序会复制一份代码去跑(也就是说操作系统会再次进行资源分配,所以创建出来的子进程所拥有的内存是和创建它的进程的内存是不同的,所以不可能共享全局变量)
# 打印全局变量id可发现变量的内存地址是不同的
import time
import multiprocessing


my_list = [] # 列表可变类型,为全局变量


def read_val():
print(f"reading list: {my_list}, id: {id(my_list)}")


def write_val():
for i in range(3):
my_list.append(i)
print(f"writed.....{my_list}, id: {id(my_list)}")
time.sleep(0.5)


# 创建子进程时:linux和mac不会拷贝主进程执行的代码,但windows会拷贝主进程代码并执行,所以对windows来说创建子进程的代码会发生递归执行而报错,需要把此部分代码放在__name__ == "__main__"判断下(判断主模块的代码只会执行一次),linux和mac就不需要
if __name__ == "__main__":
read_task = multiprocessing.Process(target=read_val)
write_task = multiprocessing.Process(target=write_val)

write_task.start()
read_task.start()

1.4. 实现方式:线程

  • 线程是进程中执行代码的一个分支,每个线程想到执行代码需要CPU进行调度
  • 线程是CPU调度的基本单位,每个进程至少有一个线程,称为主线程
  • 主线程会等待所有子线程结束再结束
    • sub_thread.setDaemon(True)
    • threading.Thread(target=task, daemon=True)
  • 线程执行时无序的, 谁抢到CPU, 谁就执行
  • 线程之间共享全局变量,因为在同一进程里面,所以使用的内存资源是相同的,这会导致数据错乱问题,解决方案
    • 线程等待 sub_thread.join()
    • 互斥锁:对共享数据进行锁定,保证同一时刻只有一个线程操作共享数据
    • 以上两种方法都是把多任务改成单任务去执行,保证了数据的准确性,但执行效率会下降
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import time
import threading


def sing(name):
cur_thread = threading.current_thread()
print(f"sing: {cur_thread}\n")
for _ in range(3):
print(f"singing {name}... \n")
time.sleep(0.2)

def dance():
cur_thread = threading.current_thread()
print(f"dance: {cur_thread}\n")
for _ in range(3):
print("dancing... \n")
time.sleep(0.2)

def tutorial0():
"""线程无序"""
main_thread = threading.current_thread()
print(f"main thread: {main_thread}")

sing_thread = threading.Thread(target=sing, args=("正月十八", ))
dance_thread = threading.Thread(target=dance)

sing_thread.start()
dance_thread.start()


# 线程之间共享全局变量
g_list = []


def add_data():
for i in range(3):
g_list.append(i)
print(f"added, {g_list}\n")


def read_data():
print(f"read data {g_list}\n")


def tutorial1():
"""线程共享全局变量"""
add_thread = threading.Thread(target=add_data)
read_thread = threading.Thread(target=read_data)

add_thread.start()
read_thread.start()


g_num = 0
lock = threading.Lock()


def add_num0():
lock.acquire()
for _ in range(100_0000):
global g_num # int不可变要用全局需要声名
g_num += 1
print(f"add0: {g_num}")
lock.release()


def add_num1():
lock.acquire()
for _ in range(100_0000):
global g_num # int不可变要用全局需要声名
g_num += 1
print(f"add1: {g_num}")
lock.release()


def tutorial2():
"""数据保护"""
thread0 = threading.Thread(target=add_num0)
thread1 = threading.Thread(target=add_num1)

thread0.start()
# thread0.join() # 线程等待 在0执行完再向下执行
thread1.start()

if __name__ == "__main__":
tutorial2()

1.5. 实现方式:协程

迭代器 Iterator

  • 可迭代对象(Iterable)定义:包含 __iter__ 方法
    • 可迭代对象不一定是迭代器,但迭代器一定是可迭代对象
1
2
3
4
# 判断一个对象是否可迭代
from collections import Iterable

isinstance(A, Iterable)
  • 迭代器定义:包含 __iter____next__ 方法
    • 迭代是访问集合元素的一种方式
    • 迭代器是一个可以记住遍历位置的对象
    • 迭代器对象从集合第一个元素开始访问,直到所有元素被访问结束
    • 迭代器只能往前不能后退
    • 迭代器可以节省内存空间,实现循环
  • 迭代器优点:存放生成数据的实现方式而不是具体数据,占用很少的内存空间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
from collections.abc import Iterable, Iterator


class ClassIterator:

def __init__(self, obj) -> None:
self.obj = obj
self.cur_num = 0

def __iter__(self):
pass

def __next__(self):
if self.cur_num >= len(self.obj.names):
raise StopIteration

res = self.obj.names[self.cur_num]
self.cur_num += 1
return res


class Classmate:

def __init__(self) -> None:
self.names = list()

def add(self, name):
self.names.append(name)

def __iter__(self):
"""想要一个对象称为一个 可迭代对象, 即可以用for遍历
必须要有此方法
"""
return ClassIterator(self)


# iter返回self
class Fibonacci:

def __init__(self, nums) -> None:
self.nums = nums
self.cur_num = 0
self.a = 0
self.b = 1

def __iter__(self):
return self

def __next__(self):
if self.cur_num >= len(self.nums):
raise StopIteration

res = self.a
self.a, self.b = self.b, self.a + self.b
self.cur_num += 1

return res


if __name__ == "__main__":
classmate = Classmate()

classmate.add("foo")
classmate.add("zoo")
classmate.add("yoo")

# iter方法会自动调用__iter__方法接收返回值, 其返回值就是迭代器也就是ClassIterator类创建的对象就是迭代器
classmate_iterator = iter(classmate)
print(isinstance(classmate_iterator, Iterator)) # 判断是否是迭代器 True

for i in classmate:
print(i) # foo zoo yoo

print(isinstance(classmate, Iterable))

生成器 Generator

  • 生成器是一种特殊的迭代器
  • 如果一个函数中有yield语句,那么这个函数就不再是函数,而是一个生成器模板
  • 定义:生成器推导式
    • 列表推导式:[i for i in range(3)]
      • 把列表推导式的[] 改为 () 返回的就是一个生成器
  • 生成器的启动:让生成器从断点处继续执行,即唤醒生成器
    • next()第几次启动都可以,但不能传参
    • obj.send(param) 需要传参时使用,不能第一次启动时使用
  • 获取生成器数据用 next(generator)方法
  • 生成器数据全部取出后再次使用next()方法会报StopIteration错误
  • yield关键字有两个作用
    • 保存当前运行状态,暂停执行,将生成器挂起
    • yield关键字后面表达式的值作为返回值返回,此时类似return
1
2
3
4
5
6
7
8
9
10
11
12
def create_num(cnt):
a, b = 0, 1
cur_num = 0
while cur_num < cnt:
yield a
a, b = b, a + b
cur_num += 1

gen_obj = create_num(10) # 此时创建了一个生成器对象
print(gen_obj) # <generator object create_num at 0x0000022C5899D510>
print([i for i in gen_obj]) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

协程 Coroutine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# 使用greenlet

import time
from greenlet import greenlet

def t1():
while True:
print("----------A----------")
gr2.switch()
time.sleep(1)


def t2():
while True:
print("----------B----------")
gr1.switch()
time.sleep(1)

gr1 = greenlet(t1)
gr2 = greenlet(t2)

print(gr1, gr2)
gr2.switch()

"""
<greenlet.greenlet object at 0x00000276263C30F0 (otid=0x00000276263A9EE0) pending> <greenlet.greenlet object at 0x00000276263C31A0 (otid=0x00000276263C7040) pending>
----------B----------
----------A----------
----------B----------
----------A----------
...
"""

# 使用gevent碰到延时就切换到其他的greenlet去运行
from gevent import monkey
monkey.patch_all()

1.6. 不同实现方式对比

  • 进程是资源分配的基本单位,切换需要资源最大,效率很低
  • 线程是操作系统调度的基本单位,切换需要的资源一般,效率一般(不考虑GIL的情况下)
  • 协程切换任务需要的资源很小,效率高
  • 多进程、多线程根据CPU核数不同可能是并行的,协程在一个线程中所以一定是并发的

1.7. GIL锁

GIL(全局解释器锁)是一个在CPython解释器中的锁,用于确保同一时刻只有一个线程执行Python字节码。这是由于CPython的内存管理机制并不是线程安全的,因此GIL可以防止多个线程同时访问、修改同一块内存,从而避免了可能出现的数据竞争和内存错误。但同时,GIL也限制了Python多线程并行性能,在一些密集计算和多线程CPU密集型任务场景中表现不及其他语言和并发框架。

CPython解释器的内存管理机制是基于引用计数的垃圾回收,即对象被引用一次计数器加一,对象引用被释放计数器减一,当计数器变为0时,对象被回收。这种内存管理机制并不是线程安全的,因为多个线程可能同时访问和修改同一块内存,从而导致计数器不一致,或者对象被销毁多次,或者内存泄漏等问题。因此,为了避免这些问题,CPython引入了GIL锁来确保同一时刻只有一个线程执行Python字节码,从而保证内存管理的线程安全性。

  • 全局解释器锁
  • 保证同一时间, 只有一个线程使用CPU, 不管主子线程
  • GIL的存在导致, python中只有进程是可以并行的, 多线程实际也是并发的
  • 一个进程有一个GIL锁
  • GIL不是python的特性, 只是CPython解释器的概念, 历史遗留问题
  • 所以cpu超线程对python是没用的!比如2核4线程,一个python死循环会让cpu两个线程跑满(这里说的线程是硬件层面的技术,模拟多个逻辑CPU提高处理并发性能)

GIL锁什么时候释放

  • 当前线程执行超时后会释放
  • 当前线程阻塞操作时会自动释放(input, io/输入输出)
  • 当前执行完成时

GIL的弊端

  • GIL对计算密集型的程序会产生影响。因为计算密集型的程序,需要占用系统资源。
  • GIL的存在,相当于始终在进行单线程运算,这样自然就慢了。
  • IO密集型影响不大的原因在于,IO,input/output,这两个词就表明程序的瓶颈在于输入所耗费的时间,线程大部分时间在等待,所以它们是多个一起等(多线程)还是单个等(单线程)无所谓的。

解决方案:

要提升多线程执行效率,解决方案:

  • 更换解释器
  • 改为进程替换多线程
  • 子线程使用C语言实现(绕过GIL锁)

必须要知道的是:

  • CPU 密集(计算密集)型不太适合多线程
  • I/O 密集型适合多线程/协程(Gil锁会释放)

2. 高级语法

2.1. 闭包&装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
"""闭包
定义:函数嵌套的前提下,内部函数使用了外部函数的变量或参数,外部函数返回内部函数
作用:保存外部函数内的变量, 不会随着外部函数调用结束而销毁,但消耗内存!

"""
def outter0(a):
local_a = "world"
def inner(b):
print(f"inner: {a}, {b} {local_a}")
return inner

def t0():
foo = outter0("foo")
foo("hello")

goo = outter0("goo")
goo("hello")

# inner: foo, hello world
# inner: goo, hello world


def outter1(a=10):
print(f"outter: {a}")

def inner(b=10):
nonlocal a
a = a + b # 此时默认是是取local vars不声名nonlocal会报UnboundLocalError
print(f"inner a: {a}, b: {b}")
return inner


def t1():
f = outter1()
f()
# outter: 10
# inner a: 20, b: 10


"""
装饰器:本质就是一个闭包函数(但要求闭包函数有且只有一个参数, 参数必须是函数类型)
装饰器的执行事件是加载模块事立即执行 (在函数定义时候执行了), 所以一般外部函数内不写其他东西, 只有内部函数
特点:
- 不修改已有函数的源代码
- 不修改已有函数的调用方式
- 给已有函数增加额外的功能
"""

# 通用装饰器(inner的参数为 *args, **kwargs也就是接收任意参数)
def outter2(f):
def inner(*args, **kwargs):
print(f"inner: {args, kwargs}")
res = f(*args, **kwargs)
print(f"inner: {res}")
return res
return inner


@outter2 # 相当于执行了这句代码:func = outter2(func)
def func2(a, b, c=3, d=None):
print(f"func: {a, b, c, d}")
return "hello world"


def t2():
func2(1, 2, d=5)
# inner: ((1, 2), {'d': 5})
# func: (1, 2, 3, 5)
# inner: hello world


# 带有参数的装饰器:装饰器外再加一层闭包
def outter3(flag=False):
def outter2(f):
def inner(*args, **kwargs):
# 此时只是打印flag, 没修改不可变类型,不需要声名nonlocal
print(f"inner: {args, kwargs}, {flag=}")
res = f(*args, **kwargs)
print(f"inner: {res}, {flag=}")
return res
return inner
return outter2


@outter3(True)
def func3(a, b, c=3, d=None):
print(f"func: {a, b, c, d}")
return "hello world"

def t3():
func3(1, 2, d=4)
# inner: ((1, 2), {'d': 4}), flag=True
# func: (1, 2, 3, 4)
# inner: hello world, flag=True


# 类装饰器
class Outter4:

def __init__(self, f):
self.f = f

def __call__(self, *args, **kwargs):
print(f"inner: {args=}, {kwargs=}")
res = self.f(*args, **kwargs)
return res

@Outter4
def func4(a, b, c=3, d=None):
print(f"func: {a, b, c, d}")
return "hello world"


def t4():
func4(1, 2, d=4)
# inner: args=(1, 2), kwargs={'d': 4}
# func: (1, 2, 3, 4)


if __name__ == "__main__":
t4()

2.2. property

3. property属性

  • property属性就是负责把一个方法当做属性进行使用,这样做可以简化代码使用
  • 定义方式
    • 装饰器方式
    • 类属性方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 类属性方式 
class Student(object):

def __init__(self):
self.__age = 0

def get_age(self):
return self.__age

def set_age(self, value):
self.__age = value

# 第一个参数是获取值的方法, 第二个是设置值的方法
age = property(get_age, set_age)


# 装饰器方式
class Student(object):

def __init__(self):
self.__age = 0

# 获取年龄
@property
def age(self):
return self.__age

# 设置年龄
@age.setter
def age(self, value):
self.__age = value

2.3. with语句&上下文管理器

  • with 语句执行完成以后自动调用关闭文件操作, 即使出现异常
  • 一个类只要实现了__enter__()__exit__()这个两个方法,通过该类创建的对象我们就称之为上下文管理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 要实现上下文管理器, 要实现__enter__ 和 __exit__  
class File(object):

def __init__(self, file_name, file_mode):
self.file_name = file_name
self.file_mode = file_mode

# 实现上文的方法,主要用来提供资源,需要返回一个对象
def __enter__(self):
print('entered up')
self.fp = open(self.file_name, self.file_mode)
return self.fp


# 实现下文的方法,主要用来释放资源
def __exit__(self, exc_type, exc_val, exc_tb):
print('exited down')
self.fp.close()


with File("a.txt", "w") as f:
print('-' * 28)

# entered up
# ----------------------------
# exited down
  • 上下文管理器可以使用 with 语句,with语句之所以这么强大,背后是由上下文管理器做支撑的,也就是说刚才使用 open 函数创建的文件对象就是就是一个上下文管理器对象

  • __enter__表示上文方法,需要返回一个操作文件对象

  • __exit__表示下文方法,with语句执行完成会自动执行,即使出现异常也会执行该方法

2.4. 深拷贝和浅拷贝

  • import copy拷贝的目的: 保证原数据和拷贝的数据之间不影响
  • copy.copy() 浅拷贝,只对可变类型的第一层对象进行拷贝,对拷贝的对象开辟新的内存空间进行存储,不会拷贝对象内部的子对象
    • 不可变类型进行浅拷贝不会给拷贝的对象开辟新的内存空间,而只是拷贝了这个对象的引用
    • 可变类型进行浅拷贝只对可变类型的第一层对象进行拷贝,对拷贝的对象会开辟新的内存空间进行存储,子对象不进行拷贝
  • copy.deepcopy() 深拷贝, 只要发现对象有可变类型就会对该对象到最后一个可变类型的每一层对象就行拷贝, 对每一层拷贝的对象都会开辟新的内存空间进行存储
    • 不可变类型进行深拷贝如果子对象没有可变类型则不会进行拷贝,而只是拷贝了这个对象的引用,否则会对该对象到最后一个可变类型的每一层对象就行拷贝, 对每一层拷贝的对象都会开辟新的内存空间进行存储
    • 可变类型进行深拷贝会对该对象到最后一个可变类型的每一层对象就行拷贝, 对每一层拷贝的对象都会开辟新的内存空间进行存储
  • 浅拷贝最多拷贝对象的一层 (即使可变类型, 也只拷贝第一层) 其它情况都是拷贝引用
  • 深拷贝可能拷贝对象的多层 (只要是有可变类型, 就全部拷贝) 其它情况都是拷贝引用

2.5. 单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 只有一份内存空间
# __new__ 开辟内存空间, 会在__init__之前执行

class Singleton(object):
is_instance = None
def __new__(cls, *args, **kwargs):
if cls.is_instance is None:
# 保证下边代码执行一次
cls.is_instance = object.__new__(cls)
return cls.is_instance
else:
return cls.is_instance


a = Singleton()
b = Singleton()
c = Singleton()

>>> id(a) == id(b) == id(c)
>>> True

面试造飞机

  1. 生成器和迭代器的区别

    1. 定义方式:
      • 生成器是一种特殊的函数,它使用 yield 关键字来定义,并在函数体内使用 yield 来产生值。生成器函数可以暂停执行,并在需要时恢复执行,每次恢复执行时会从上次暂停的位置继续执行。
      • 迭代器是一个实现了迭代协议的对象,它包含 __iter__() 方法和 __next__() 方法。__iter__() 方法返回迭代器自身,而 __next__() 方法用于返回迭代器的下一个值,如果没有下一个值则抛出 StopIteration 异常。
    2. 使用方式:
      • 生成器通常用于生成一个序列的值,它们可以通过 for 循环来迭代产生的值,也可以通过调用 next() 函数手动迭代。
      • 迭代器则是一种更通用的迭代工具,它可以迭代任何实现了迭代协议的对象,包括列表、元组、集合等。迭代器可以通过 iter() 函数获取,也可以直接用于 for 循环中。
    3. 内存占用:
      • 生成器是一种惰性求值(Lazy Evaluation)的机制,它只在需要时生成值,并且不会一次性将所有值存储在内存中,因此生成器在处理大量数据时具有较低的内存消耗。
      • 迭代器通常需要将所有的值存储在内存中,因此在处理大量数据时可能会消耗较多的内存。
    4. 可变性:
      • 生成器是不可变的,一旦定义完成后,生成器的值无法修改。
      • 迭代器通常是可变的,可以在迭代过程中修改迭代器的状态,例如添加、删除元素等。

    综上所述,生成器和迭代器在 Python 中都是用于处理可迭代对象的工具,但它们的定义方式、使用方式、内存占用和可变性等方面有一些区别。生成器通常用于惰性生成值,而迭代器则是一种通用的迭代工具,用于迭代任意可迭代对象。

  2. python内存管理(同GIL锁是cpython解释器的特性)

    1. Python的内存管理机制主要依赖于引用计数来迅速释放不再使用的对象,同时通过垃圾回收来处理循环引用等特殊情况,保证内存的高效利用。这种自动内存管理的方式让开发者从手动管理内存的负担中解放出来,提高了开发效率。

    2. 引用计数:Python使用引用计数来跟踪每个对象被引用的次数。当一个对象被创建或者另一个对象引用它时,引用计数就会增加。当一个对象的引用计数降为0时,表示没有任何变量指向该对象,那么该对象将成为垃圾,Python会回收这部分内存以供其他对象使用。

      • 引用计数的优点是在对象不再被引用时立即释放内存,因为没有等待垃圾回收器的运行。但是引用计数无法解决循环引用的问题(两个或更多的对象相互引用,导致它们的引用计数都不会降为0),为此,Python引入了垃圾回收机制。
    3. 垃圾回收(Garbage Collection): Python中的垃圾回收机制是为了解决循环引用以及其他无法通过引用计数检测的垃圾对象。Python采用了分代回收算法,将对象分为不同的代(generation)。新创建的对象位于第0代,每经过一次垃圾回收,存活的对象会晋升到下一代。随着对象存活时间的增加,回收的频率会降低,因为大部分对象很快就会变成垃圾,只有少数长时间存活的对象才需要耗费更多的垃圾回收时间。

      • 标记-清除(Mark and Sweep):垃圾回收器首先标记所有可以访问的对象,然后清除所有未标记的对象,释放其内存。

      • 分代回收(Generational Garbage Collection):将对象按照存活时间分为不同的代,一般将新创建的对象放入第0代,存活时间更长的对象依次放入第1代、第2代,通过不同代之间的垃圾回收来提高效率。

      • 引用计数+标记-清除:综合利用引用计数和标记-清除算法,处理循环引用以及其他无法通过引用计数回收的对象。

      • 分代回收在处理循环引用的情况时,通过为每个对象分配一个分代标记来解决这个问题。分代标记可以被划分为两种类型:线程局部分代标记(thread local allocation context)和全局局部分代标记(global allocation context)。

        当一个对象被创建时,Python内核会为其分配一个分代标记,并将该标记与对象一起存储。对于循环引用的对象,分代标记会被保持不变。当一个对象被销毁时,Python通过检查对象的分代标记来确定是否已经被回收。如果对象的分代标记未被其他地方引用,那么该对象已经不再被使用,并需要进行回收。

        为了避免不必要的循环回收,Python内核会记录每个对象是否已经被回收。一旦对象被回收,就不会再被释放,即使再次被检测到。这样可以确保只有一次地回收每个对象。

  3. tcp与udp区别

    1. 连接:tcp是一种面向连接的协议,在数据传输前要先建立一个连接,UDP是无连接协议,不需建立连接
    2. 可靠性:TCP提供可靠的数据传输,确保数据包在从发送端到接收端的传输过程中不会丢失、被改变或出现重复,UDP不保证数据传输可靠性,可能导致丢包、被改变或重复
    3. 速度:由于TCP的可靠性和流量控制机制,传输速度相对UDP会慢很多
    4. 设计目的:TCP适用于对数据传输可靠性和稳定性要求较高,如文件传输、电子邮件和网页浏览等,UDP更擅长对实时性要求较高的应用,如实时语音、视频通信,在线游戏等
    5. 头部信息:TCP和UDP的头部信息分别为20字节和8字节。TCP的头部信息包括数据包的序号和个别流标记,用于确保数据包在传输过程的顺序和完整性。UDP的头部信息相对较少主要包括数据包的源端和目标端的IP地址和端口
  4. https原理

    1. SSL/TCS 密钥包括一个私钥和一个公钥
    2. 当用户通过浏览器访问一个HTTPS页面,服务器首先向浏览器发送公钥,浏览器接收到公钥后,会使用它来加密数据(请求头和请求体),然后将加密数据发送回服务器
    3. 服务器使用接收到的加密数据和自己的私钥来解密数据。
  5. TCP3次握手4次挥手

    1. 客户端向服务端发送一个SYN(初始化)包,表明希望建立一个连接
    2. 服务端同意建立连接会回复SYN+ACK包,表示可以建立连接
    3. 客户端回复ACK包,连接建立
    4. 客户端向服务端发送一个FIN包,表明希望断开连接,进入终止等待1状态
    5. 服务端向客户端回复一个ACK包,表示进入终止等待状态,此时可传数据
    6. 服务端向客户端发送一个FIN包,客户端进入终止等待2状态
    7. 客户端向服务端回复一个ACK包,客户等待超时时间后断开连接,服务端立刻断开连接
  6. Flask路由原理

    Flask 的路由原理主要基于装饰器和 Python 的函数式编程概念。具体来说,Flask 使用 @app.route() 装饰器将 URL 路径和视图函数绑定在一起。当接收到一个 HTTP 请求时,Flask 会根据请求的 URL 找到对应的视图函数,并执行该函数来处理请求。

  7. django处理csrf原理

    Django处理CSRF的原理基本上是通过在每个页面加载时生成一个唯一的CSRF令牌,并将其存储在用户的会话中。当用户在表单中提交请求时,Django会验证请求中包含的CSRF令牌是否与用户会话中存储的令牌匹配,以确保请求来自于站点的合法来源。

  8. django cookie session原理

    在Django中,会话(Session)是一种存储在服务器端的数据,用于在用户请求之间保持状态。Django通常使用基于cookie的会话来管理会话数据,其原理如下:

    1. 会话数据存储: 当用户首次访问Django应用时,Django会为该用户创建一个唯一的会话标识符(session ID),通常是一个随机生成的字符串。会话数据存储在服务器端的缓存中,默认情况下使用的是数据库缓存。会话数据可以包含任意类型的数据,例如用户的身份验证信息、用户偏好设置等。
    2. 会话ID的传递: 一旦会话数据存储在服务器端,Django会将会话ID发送给客户端浏览器,通常是通过一个名为sessionid的cookie。这个cookie包含了用户的会话ID,用于在后续的请求中标识用户的会话数据。
    3. 请求中的会话ID提取: 当用户在浏览器上发送新的请求时,浏览器会自动将之前设置的sessionid cookie包含在请求头中。
    4. 会话数据的检索: Django在处理请求时会检查请求头中是否包含sessionid cookie,如果包含,Django会使用该会话ID来检索对应的会话数据。
    5. 会话数据的使用: 一旦会话数据被检索到,Django会将其提供给视图函数,以便在视图中使用。开发人员可以在视图中读取和修改会话数据,从而实现跨请求的状态保持。
    6. 响应中的会话ID更新: 如果在处理请求时会话数据发生了变化,Django会在响应中将更新后的会话ID发送给客户端浏览器。这是为了确保会话ID的安全性,以及避免可能的会话固定攻击(Session Fixation Attack)。

    通过这种方式,Django使用基于cookie的会话机制来管理用户会话数据,从而实现了用户状态的保持和跨请求的数据传递。这种机制是一种常见的Web应用程序开发中用于处理用户状态的方式。

TIPS

字典无序性: 在Python 3.7及之前的版本中,字典元素的顺序是不确定的,即它们的存储顺序与插入顺序不一定一致。但是从Python 3.7开始,字典保留了元素插入的顺序。这意味着当你迭代字典时,它们的顺序将与你添加键-值对的顺序相同。

Python AI从入门到放弃

1. 概述

1.1. 人工智能起源

  • 图灵测试
  • 达特茅斯会议

1.2. 人工智能三个阶段

  • 1980年代是正式成形期
  • 1990-2010年代是蓬勃发展期
  • 2012年之后是深度学习期

1.3. 人工智能、机器学习和深度学习

  • 机器学习是人工智能的一个实现途径
  • 深度学习是机器学习的一个方法发展而来

1.4. 主要分支

  • 计算机视觉 CV
    • 人脸识别
  • 自然语言处理 NLP
    • 语音识别
    • 语义识别
  • 机器人

1.5. 人工智能必备三要素

  • 数据
  • 算法
  • 计算力

1.6. GPU、CPU

  • GPU – 计算密集型
  • CPU – IO密集型

2. 机器学习

2.1. 工作流程

  1. 数据获取

数据类型构成

  • 特征值 + 目标值(目标值分为离散还是连续)
  • 仅有特征值,无目标值

数据划分

  • 训练集 0.7~0.8
  • 测试集 0.2~0.3
  1. 数据基本处理

对数据进行缺失值、去除异常值等处理

  1. 特征工程

把数据转换为机器更容易识别的数据

数据和特征决定了机器学习的上限,模型和算法只是逼近这个上限而已

  • 特征提取
  • 特征预处理
  • 特征降维
  1. 机器学习(模型训练)

选择合适的算法对模型进行训练

  1. 模型评估

对训练好的模型进行评估

2.2. 机器学习算法分类

  • 监督学习:有特征值,有目标值
    • 目标值连续:回归
    • 目标值离散:分类
  • 无监督学习:仅有特征值
  • 半监督学习:有特征值,但一部分数据有目标值 ,一部分没有
  • 强化学习:即自动进行决策,并可以做连续决策
    • 动态过程,上一步的输出是下一步的输入
    • 四要素:agent, action, environment, reward

2.3. 模型评估

  • 分类模型评估

    • 准确率:预测正确的数占样本总数的比例
    • 精确率:预测为正的数占全部预测为正的比例
    • 召回率: 预测为正占全部正样本的比例
    • F1-score:主要用于评估模型的稳健性
    • AUC指标:主要用于评估样本不均衡的情况
  • 回归模型评估

    p = predicted target

    a = actual target

    • 均方根误差(Root Mean Squared Error, RMSE)
    • 相对平方误差(Relative Squared Error, RSE)
    • 平均绝对误差(Mean Absolute Error, MAE)
    • 相对绝对误差(Relative Absolute Error, RAE)
    • 决定系数(Coefficient of Determination)
  • 拟合度(评估结果)

    • 欠拟合:学习到的特征太少(有两个眼睛的就是人)
    • 过拟合:学习到的特征太多(仅黄皮肤的才是人)

3. 机器学习环境配置

3.1. 环境安装

  • 创建虚拟环境: conda create --name=ai0 python=3.10
  • 安装相关包:
    • matplotlib==2.2.2
    • numpy==1.14.2
    • pandas==0.20.3
    • tables==3.4.2
    • jupyter=1.0.0
  • 先从cmd进入指定目录后执行jupyter notebook

3.2. jupyter基操

类似vim, 分不同的输入模式,命令也和vim基本相同

  • 两种模式通用快捷键
    • Shift + Enter 执行本单元代码,跳转到下一单元
    • Ctrl + Enter 执行本单元代码,留在本单元
  • 命令模式:按 ESC 进入
    • Y, cell 切换到Code模式
    • M, cell 切换到Markdown模式
    • A, 在当前cell上面添加cell
    • B, 在当前cell下面添加cell
    • DD, 删除当前cell
    • Z, 回退
    • L, 为当前cell加上行号<!–
    • Ctrl+Shift+P, 对话框输入命令直接运行
    • Ctrl+Home, 跳转到首个cell
    • Ctrl+End, 跳转到末个cell
    • Shift + M 合并下面的cell
  • 编辑模式: 按 Enter 进入
    • Ctrl + 点击, 多光标操作
    • Ctrl + Z, 回退
    • Ctrl + Y, 重做
    • TAB, 代码补全
    • Ctrl + / 注释/取消注释
    • 代码后 + ; 屏蔽输出

4. Matplotlib

用于开发2D、3D图表

使用简单,以渐进、交互式实现数据可视化

4.1. 三层结构

  • 容器层
    • Canvas: 最底层的系统层,充当画板角色,即放置画布(Figure)的工具
    • Figure: Canvas上第一层,充当画布角色
    • Axes:应用层的第二层,在绘图过程中相当于画布上的绘图区的角色
      • Axes: 坐标系,数据的绘图区域
      • Axis: 坐标轴
  • 辅助显示层:为Axes内除了根据数据绘制出的图像以外的内容,主要包括facecolor(Axes外观)spines(边框线)axis(坐标轴)axix lable(坐标轴名称)tick(坐标轴刻度)tick lable(坐标轴刻度标签)gird(网格线)legend(图例)title(标题)
  • 图像层:指Axes内通过 plot, scatter, histogram, pie等函数根据数据绘制出的图像。

4.2. 折线图与基操

help(plt.figure) 查看命令帮助

负号报错 plt.rcParams[“axes.unicode_minus”]=False 加这句

中文乱码,需要把系统使用的中文字体对应的英文名称添加到matplotlib配置中 matplotlib.matplotlib_fname() 可查到是 matplotlib/mpl-data/matplotlibrc 此文件,查 #font.sans-serif 解注释并把英文字体名加到第一个位置

报错missing from current font,加以下代码

from pylab import mpl mpl.rcParams['font.sans-serif'] = ['SimHei']

Hello World

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import matplotlib.pyplot as plt

# 1.创建画布
plt.figure()

# 2.图像绘制
x = [i for i in range(1, 6)]
y = [i for i in range(3, 8)]
plt.plot(x, y)

# 2.1. 图像保存, 要放到show前
plt.savefig()

# 3.显示图像
plt.show()

基操

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import random
import matplotlib.pyplot as plt

# 0.生成数据
x = range(60)
y_peking = [random.uniform(10, 15) for i in x]
y_shanghai = [random.uniform(15, 25) for i in x]

# 1. 创建画布
plt.figure(figsize=(20, 8), dpi=100)

# 2.图形绘制
plt.plot(x, y_peking, color="r", label="北京", linestyle="dashed")
plt.plot(x, y_shanghai, label="上海")

# 2.1. 添加x,y刻度
x_ticks_labels = [f"11点{i}" for i in x]
y_ticks = range(40)

plt.yticks(y_ticks[::5])
plt.xticks(x[::5], x_ticks_labels[::5])

# 2.2. 添加网络信息
# 参数:linestyle: 绘制网格的方式,alpha:透明度
plt.grid(True, linestyle="-", alpha=1)

# 2.3. 添加描述
plt.xlabel("时间")
plt.ylabel("温度")
plt.title("中午11:00到12:00温度变化")

# 2.4. 显示图例, 需要在显示前声明plot里面的值
plt.legend()

# 3. 图像显示
plt.show()

多个坐标系图像显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import random
import matplotlib.pyplot as plt

# 多个坐标系显示图像
# 0.生成数据
x = range(60)
y_peking = [random.uniform(10, 15) for i in x]
y_shanghai = [random.uniform(15, 25) for i in x]

# 1. 创建画布
# plt.figure(figsize=(20, 8), dpi=100)
fig, axes = plt.subplots(nrows=1, ncols=2, figsize=(20, 8), dpi=100)

# 2.图形绘制
# plt.plot(x, y_peking, color="r", label="北京", linestyle="dashed")
# plt.plot(x, y_shanghai, label="上海")
axes[0].plot(x, y_peking, color="r", label="北京", linestyle="dashed")
axes[1].plot(x, y_shanghai, label="上海")

# 2.1. 添加x,y刻度
x_ticks_labels = [f"11点{i}" for i in x]
y_ticks = range(40)

# plt.yticks(y_ticks[::5])
# plt.xticks(x[::5], x_ticks_labels[::5])

axes[0].set_xticks(x[::5])
axes[0].set_yticks(y_ticks[::5])
axes[0].set_xticklabels(x_ticks_labels[::5])

axes[1].set_xticks(x[::5])
axes[1].set_yticks(y_ticks[::5])
axes[1].set_xticklabels(x_ticks_labels[::5])

# 2.2. 添加网络信息
# 参数:linestyle: 绘制网格的方式,alpha:透明度
# plt.grid(True, linestyle="-", alpha=1)
axes[0].grid(True, linestyle="-", alpha=1)
axes[1].grid(True, linestyle="-", alpha=1)

# 2.3. 添加描述
# plt.xlabel("时间")
# plt.ylabel("温度")
# plt.title("中午11:00到12:00温度变化")

axes[0].set_xlabel("时间")
axes[0].set_ylabel("温度")
axes[0].set_title("中午11:00到12:00温度变化")

axes[1].set_xlabel("时间")
axes[1].set_ylabel("温度")
axes[1].set_title("中午11:00到12:00温度变化")

# 2.4. 显示图例, 需要在显示前声明plot里面的值
# plt.legend(loc=0)

axes[0].legend(loc=0)
axes[1].legend(loc=0)

# 3. 图像显示
plt.show()

5. Numpy

5.1. 基础

定义:

  • Numpy(Numerical Python) 是一个开源的Python科学计算库,用于快速处理任意维度的数组
  • Numpy支持觉的数组和矩阵操作,对于同样数值计算任务,使用Numpy比直接使用Python简洁的多
  • Numpy使用ndarray对象来处理多维数组,该对象是一个快速而灵活的大数据容器

优势:

  • 内存块风格:ndarray在存储数据时,数据和数据的地址是连续的,这样使得批量操作数组元素时速度更快

    • 原因:ndarray中所有的元素类型是相同的,而Python列表中的元素类型是任意的,所以ndarray在存储元素时内存可以连续,而原生列表只能通过寻址方式找到下一个元素
  • 并行化运算:向量化运算

  • Numpy底层使用C语言编写,内部解除了GIL,其对数组的操作速度不受Python解释器的限制,所以效率远高于纯Python代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import time
import random
import numpy as np

# 创建
score = np.array([random.sample(range(0, 100), 5) for i in range(8)])
score
"""
array([[41, 11, 1, 56, 40],
[30, 29, 38, 62, 68],
[75, 38, 76, 45, 2],
[63, 84, 68, 26, 60],
[45, 48, 58, 33, 21],
[ 1, 74, 44, 19, 97],
[15, 73, 63, 82, 95],
[20, 73, 15, 8, 93]])
"""

# 效率对比
a = [random.random() for _ in range(1000_0000)]

# %time 魔法方法,查看当前行代码运行耗时情况
# cpu times CPU执行耗时, wall time 总耗时
%time sum1 = sum(a)

b = np.array(a)

%time sum2 = np.sum(b)
"""
CPU times: total: 31.2 ms
Wall time: 31.9 ms
CPU times: total: 15.6 ms
Wall time: 9.97 ms
"""

常用属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
score
"""
array([[41, 11, 1, 56, 40],
[30, 29, 38, 62, 68],
[75, 38, 76, 45, 2],
[63, 84, 68, 26, 60],
[45, 48, 58, 33, 21],
[ 1, 74, 44, 19, 97],
[15, 73, 63, 82, 95],
[20, 73, 15, 8, 93]])
"""
score.shape # (8, 5)
score.ndim # 2 数组维度(也就是有几层[])
score.size # 40
score.itemsize # 4 每个元素占字节长度
score.dtype # dtype('int32')

# 设置ndarray类型
a = np.array([[1, 2, 3], [2, 3, 4]], dtype=np.float32)
a.dtype # dtype('float32')

5.1.1. 生成数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 0/1数组
np.ones([3, 4])
"""
array([[1., 1., 1., 1.],
[1., 1., 1., 1.],
[1., 1., 1., 1.]])
"""

np.zeros([3, 4])
"""
array([[0., 0., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
"""

# 从现有数组创建数组
a = np.array([[1,2,3], [4,5,6]])

a1 = np.array(a) # 深拷贝

a2 = np.asarray(a) # 浅拷贝

# 生成固定范围数组
np.linespace(0, 100, 11) # 等间隔生成11个
# array([ 0., 10., 20., 30., 40., 50., 60., 70., 80., 90., 100.])

np.aragne(10, 50, 2) # 步长2来生成
# array([10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48])

生成随机数组

  • 生成均匀分布:np.random.uniform(low, high, size)

  • 正态分布

    • 均值:平均值
    • 标准差:方差开根号
    • 生成标准正态分布:np.random.normal(low, high, size)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import matplotlib.pyplot as plt
import numpy as np

# 均匀分布
x = np.random.uniform(1, 10, 1000_0000) # 准备数据
plt.figure(figsize=(20, 8), dpi=100) # 画布
plt.hist(x, bins=1000) # 绘制 x代表要使用的数据, bins表示要划分的区间数
plt.show() # 显示

# 正态分布
x = np.random.normal(1.75, 1, 1000_0000)
plt.figure(figsize=(20, 8), dpi=100)
plt.hist(x, bins=1000)
plt.show()

5.1.2. 数组索引、切片

  • 直接索引,先对行进行索引,再对列进行索引
  • 高维度索引,从宏观到微观
1
2
3
4
5
6
7
8
a = np.random.normal(0, 1, (8, 10))  # 8rows, 10cols
a[0:2, 0:3] # 前两行,前三列

a = np.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]])
a[0, 0, 0] # 1
a[1, 0, 0] # 7
a[1, 1, 2] # 12

5.1.3. 形状修改

  • .reshape([rows, cols]) 不进行行列互换,产生新变量

  • .resize([rows, cols]) 进行行列互换,对原值 进行更改

  • .T 行列互换

5.1.4. 类型修改

  • .astype(np.int32)

  • .tobytes()

  • np.unique(nparray) 去重

    1
    2
    3
    arr = np.array([[1, 2, 3, 3, 3], [2, 3, 4,4 , 5]])
    np.unique(arr)
    # array([1, 2, 3, 4, 5])

5.1.5 运算

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

# 逻辑运算
a = np.random.normal(0, 1, (8, 10))
a > 1 # 全部数据进行此运算,结果赋值为 True/False
a[a > 1] = 2 # 全部数据 >1 赋值为 2

# 通用判断函数,和python的all和any一样
b = a[0:2, 0:5] # 切为2行5列
np.all(b > 0) # False
np.any(b > 0) # True

# 三元运算
np.where(b > 0, 1, 0) # array([[1, 1, 0, 0, 1], [0, 1, 0, 1, 1]])

np.where(np.logical_and(b > -0.5, b < 0.5), 1, 0) # array([[1, 0, 0, 0, 0],[0, 1, 1, 0, 1]])

np.where(np.logical_or(b > -0.5, b < 0.5), 1, 0) # array([[1, 1, 1, 1, 1], [1, 1, 1, 1, 1]])

# 统计运算
min
max
median
mean
std 标准差
var 方差
argmax 最大值下标
argmin

5.2. 矩阵

  • 矩阵:二维数组
    • 向量:一维数组
  • 加法:对应位置相加
  • 标量乘法:标量和每个位置元素相乘
  • 矩阵乘法:[M行,N列] * [N行,L列] = [M行,L列]
    • 满足结合律,不满足交换律
  • 单位矩阵:对角线为1,其他位置为0的矩阵
  • 逆:A * B = 单位矩阵,A和B互为逆矩阵
  • 转置T:行列互换

5.3. 数组间运算

  • 数组和数字:可以直接运算

  • 数组和数组:广播机制

    • 维度相同
    • shape对应位置为1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np

# 数组运算
arr1 = np.array([[1, 2, 3], [2, 3, 4]]) # 2,3
arr2 = np.array([[2], [1]]) # 2, 1

arr1 + arr2 # 广播机制:2==2,1 in (1, 3) 输出: array([[3, 4, 5], [3, 4, 5]])

# 矩阵乘法
a = np.array([[1, 2], [2, 3], [3, 4]])
b = np.array([[3], [2]])

# 矩阵乘
np.matmul(a, b) # 要求 3行2列 * 2行1列 输出:3行1列 array([[ 7], [12], [17]])

# 点乘
np.dot(2, b) # array([[6], [4]])

6. Pandas

  • 封装了Numpy 和 matplotlib
  • 便捷的数据处理,展示能力
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import pandas as pd
import numpy as np

a = np.random.normal(0, 1, (10, 5))

a_shape = pd.DataFrame(a).shape # (10, 5)

row_index = [f"第{i+1}行" for i in range(a_shape[0])]
column_name = pd.date_range(start="20230713", periods=a_shape[1], freq="B") # freq:B 工作日 DatetimeIndex(['2023-07-13', '2023-07-14', '2023-07-17', '2023-07-18','2023-07-19'], dtype='datetime64[ns]', freq='B')

b = pd.DataFrame(a, index=row_index, columns=column_name) # 设置行索引, 列名

b.shape # 形状
b.index # 行索引
b.columns # 列名
b.values # array
b.T # 转置
b.head() # 前5行
b.tail() # 后5行


# 重设索引
b.index = row_index
c = b.reset_index(drop=False) # 默认drop=False不删除原索引,增加index列 值为原索引

# 设置某列为索引,可设置多列
c.set_index(keys=["index"])

7. K-近邻算法

根据邻居判断自己的类别,是一种分类算法,K Nearst Neighbor(KNN)

-

7.1. Scikit-learn

Classification 分类

Regression 回归

Clustering 聚类

Dimensionality reduction 维度缩小

Model selection 模型选择

Preprocessing 特征预处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from sklearn.neighbors import KNeighborsClassifier


# 获取数据
x = [[1], [2], [0], [0]]
y = [1, 1, 0, 0]

# machine learning
# 1. 实例化一个训练模型
estimator = KNeighborsClassifier(n_neighbors=2) # 选定几个参考数据

# 2. 调用fit方法进行训练
estimator.fit(x, y)

# 预测其他值
print(estimator.predict([[-1]]))

7.2. 距离度量

欧式距离:差平方开根号

曼哈顿距离(Manhattan Distance): 又称为城市街区距离(City Block distance)

  • d = |x1 - x2| + |y1 - y2|

切比雪夫距离(Chebyshev Distance): max(|x1-x2|, |y1-y2|)

闵可夫斯基距离(Minkowski Distance):
$$
d_{12} = p\sqrt{\sum_{k=1}^n|x_{1k} - x_{2k}|^p}
$$

  • 当p=1, 为曼哈顿距离
  • 当p=2,为欧氏距离
  • 当p$\to\infty$, 为切比雪夫距离

标准化欧氏距离(Standardized EuclideanDistance): 对欧氏距离的一种改进, 如果将方差的倒数看成一个权重,也可称为加权欧氏距离(Weighted Euclidean distance)

  • 既然数据各维分量的分布不同,将各个分量都标准化到均值、方差相等,假设样本均值为m(mean), 标准差(standard deviation)为s, 则公式为:

$$
d_{12} = \sqrt{\sum_{k=1}^n(\frac{x_{1k}-x_{2k}}{s_k})^2}
$$

余弦距离(Cosine Distance): 夹角余弦取值范围为[-1, 1], 余弦越大表示两个向量夹角越小,当两个向量的方向重合时值为1, 相反为-1,用来衡量样本间的差异

汉明距离(Hamming Distance): 一个字符串到另一个字符串需要变换几个字母,进行统计

杰卡德距离(Jaccard Distance): 通过交并集进行统计

马氏距离(Mahalanobis Distance): 通过样本分布进行计算

7.3. K值选择

过小:容易受到异常点的影响,容易过拟合

过大:受到样本均衡的问题,容易欠拟合

7.4. KD树

8. 线性回归

9. 逻辑回归

10. 决策树算法

11. 集成学习

12. 聚类算法

conda

mac下安装完anaconda后会自动安装conda,进入终端会自动启动base虚拟环境(自带的默认虚拟环境)

常用命令

基操

  1. conda create:创建新的虚拟环境。conda create -n py39 python=3.9
  2. conda activate:激活一个已经存在的虚拟环境。
  3. conda deactivate:停用当前激活的虚拟环境。
  4. conda list:列出所有已安装的包及其版本号。
  5. conda install:安装新的包。
  6. conda update:更新已安装的包。
  7. conda remove:卸载已安装的包。
  8. conda search:搜索可用的包。
  9. conda info:查看conda的配置信息。
  10. conda clean:清理conda中的缓存、未使用的软件包和环境。
  11. conda config:配置conda的选项。

常见问题

mac下安完anaconda后进终端会自动启用base虚拟环境

  • 使用 conda config --set auto_activate_base false 关闭

计算机组成原理

1. Basic

1.1. 基本组成

五大部分

  • 运算器 (CPU) (主机): 算术运算、逻辑运算
  • 控制器 (CPU) (主机): 指挥各部件, 使程序运行
  • 存储器 : 存放数据和程序
    • 主存 (主机)
    • 辅存 (I/O设备)
  • 输入设备 (I/O设备)
  • 输出设备 (I/O设备)

1.2. 各硬件部分

寄存, 就是暂存数据的意思, 类似快递点

主存储器

存储体存数据, MAR对应数据位置(主存地址), MDR暂存对应的实际数据

类似快递点: 存储体=> 货架, MAR => 店员, MDR => 取货/放货 的柜台

取数据过程: CPU把地址给到MAR, 把读指令给到主存储器, 主存储器把存储体MAR地址的数据放到MDR等待使用

存数据过程: CPU把地址给到MAR, 把数据放到MDR, 把写指令给到主存储器, 主存储器根据指令把MDR中数据存储到存储体的MAR地址

  • 存储体: 数据在存储体中按地址存储, 每个地址对应一个存储单元
    • 存储单元: 每个存储单元存放一串二进制代码
      • 存储元: 即存储二进制的电子元件, 每个存储元可存1bit
    • 存储字: 存储单元中二进制的组合
    • 存储字长: 存储单元中二进制代码的位数
  • MAR: 存储地址寄存器, 用于指明要读/写哪个存储单元, 其位数反映存储单元数量 Memory Address Register
  • MDR: 存储数据寄存器, 用于暂存要读/写的数据, 其位数=存储字长 Memory Data Register

运算器: 用于实现算术运算(+ - * /)、逻辑运算(与/非)

前三个存数据, ALU执行(运算)

  • ACC: 累加计数器, 存放操作数、运算的结果 Accumulator
  • MQ: 乘商寄存器, 进行乘、除法时用 Multiple-Quotient Register
  • X: 通用寄存器, 存放操作数
  • ALU: 算数逻辑单元, 用电路实现各种算数运算、逻辑运算 Arthmetic and Logic Unit

控制器

PC + IR 取指令, CU执行

  • PC: 程序计数器, 存放下一条指令的地址 Program Counter
  • IR: 指令寄存器, 存放当前执行的指令 Instruction Register
  • CU: 控制单元, 分析指令, 给出控制信息 Control Unit

工作过程

  • 初始: 指令、数据存入主存, PC指向第一条指令
  • 从主存中取指令放入IR、PC自动加1, CU分析指令, CU指挥其他部件执行指令

1.3. 计算机系统层次结构

微程序机器M0 传统机器M1 虚拟机器M2 虚拟机器M3 虚拟机器M4
微指令系统 用机器语言的机器 操作系统机器 汇编语言机器 高级语言机器
硬件 硬件 软件 软件 软件
由硬件直接执行微指令 执行二进制机器指令 向上提供”广义指令”(系统调用) 用汇编程序翻译成机器语言程序 用编译程序翻译成汇编语言程序
微指令1 / 2 / 3 000010000000101 LOAD 5 y=a*b + c

三种级别的语言

  • 机器语言: 二进制代码
  • 汇编语言: 助记符
    • 将汇编语言翻译成机器语言
  • 高级语言: C, Python
    • 编译程序(编译器): 将高级语言一次全部翻译为汇编语言, 或直接翻译为机器语言
    • 解释程序(解释器): 将高级语言翻译为机器语言, 翻译一句执行一句

1.4. 存储器的性能指标

  • CPU主频: CPU内数字脉冲信号振荡的频率 (Hz)
  • CPU时钟周期 = 1 / CPU主频 (秒)
  • CPI(Clock cycle Per Instruction): 执行一条指令所需的时钟周期数

不同的指令, CPI不同, 甚至相同的指令, CPI也可能变化

  • 执行一条指令的耗时 = CPI * CPU时钟周期
  • IPS(Instructions Per Second): 每秒执行多少条指令 = 主频 / 平均CPI (KMGT Kilo=10 ** 3 Million=10 ** 6)
  • FLOPS(Floating-point Operations Per Second): 每秒执行多少次浮点运算 (KMGT Giga=10 ** 9 Tera=10 ** 12)

1.5. 系统整体的性能指标

描述存储容量, 文件大小时: K = 2^10, M=2^20, G=2^30, T=2^40

描述频率, 速率时: K=10^3, M=10^6, G=10^9, T=10^12

  • 数据通路带宽: 数据总线一次所能并行传送信息的位数(各硬件部件通过数据总线传输数据)
  • 吞吐量: 指系统在单位时间内处理请求的数量
  • 响应时间: 指从用户向计算机发送一个请求, 到系统对该请求做出响应并获得它所需要的结果的时间
  • 基准程序: 用来测量计算机的一种实用程序(跑分软件)

2. 数据的表示和运算

2.1. 进位计数制

  • 位权: 由符号的位置反映权重

  • 基数: 每个数码位所用到的不同符号的个数, r进制的基数为r

  • 计算机使用二进制

    • 可使用两个稳定状态的物理器件表示
    • 0,1正好对应逻辑值 真、假。方便实现逻辑运算
    • 可很方便地使用逻辑门电路实现算术运算
  • 二进制 >> 八进制: 3位一组,每组转换成对应的八进制符号

  • 二进制 >> 十六进制: 3位一组

  • 十进制 >> r进制

    • 整数部分:除基取余法,先取得的“余”是整数的低位
    • 小数部分:乘基取整法,先取得的“整”是小数的高位
2^12^ 2^11^ 2^10^ 2^9^ 2^8^ 2^7^ 2^6^ 2^5^ 2^4^ 2^3^ 2^2^ 2^1^ 2^0^ 2^-1^ 2^-2^ 2^-3^
4096 2048 1024 512 256 128 64 32 16 8 4 2 1 0.5 0.25 0.125

参考上图可知有部分小数是无法用二进制精确表示的

  • 真值: 实际的带正负号的数值(人类习惯的样子)
  • 机器数:把正负号数字化的数(存到机器里的样子)

2.2. BCD码

二进制转十进制时按乘转换麻烦, 所以用BCD转换

BCD: Binary-Coded Decimal, 用二进制编码的十进制

  • 8421码

映射关系:

0 1 2 3 4 5 6 7 8 9
0000 0001 0010 0011 0100 0101 0110 0111 1000 1001

例:

十进制: 5 + 8 13

十进制: 0101 + 1000 1101

8421码中 1010 ~ 1111 没有定义 需要 + 0110 做数据修正(强制向高位进1) 上例得 0001 0011 即 13

4个十进制位 –>> 16种不同的状态

BCD码只使用其中的10种 –>> 不同的映射方案

  • 余3码: 8421码 + (0011)2

映射关系:

0 1 2 3 4 5 6 7 8 9
0011 0100 0101 0110 0111 1000 1001 1010 1011 1100
  • 2421码: 改变权值的定义

XXXX -> 2421 并规定 04 开头是0 5? 开头是1

映射关系:

0 1 2 3 4 5 6 7 8 9
0000 0001 0010 0011 0100 1011 1100 1101 1110 1111

2.3. 字符与字符串

2.3.1. ASCII码

常用的数字、字母、符号(英文)共128个字符 –> 7个二进制编码 –> ASCII码

存入计算机通常在高位补个0, 凑足1B(字节) 即 8bit

可印刷字符: 32~126, 其余为控制、通信字符

规律:

​ 数字: 48(0011 0000) ~ 57(0011 1001) 后面一个比特位刚好是数字对应的8421码

​ 大写字母: 65(0100 0001) ~ 90(0101 1010) 前三个010 后面为1~26

​ 小写字母: 97(0110 0001) ~ 122(0111 1010) 前三个011 后面为1~26

1
2
ord('a') - ord('A') == 32
>> True

image-20220724170433901

2.3.2. 汉字的表示和编码

1980年 GB 2312-80: 汉字+各种符号共7445个

区位码: 94个区, 每区94个位置(即94行, 94列)

国标码: 为防止信息交换时与”控制/通信字符”冲突, 在区位码基础上加32(16进制: 20H)

国标码已可保证数据传输, 但无法和ASCII做区分

汉字内码: 保证高位为1, 与ASCII码做区分, 在国标码基础上加128 (1000, 0000) (16进制: 80H)

汉字内码可以保证数据在计算机内部的处理, 计算机发现高位为1时可判断出是汉字

例:

汉字(十进制) 区位码(十六进制) 国标码(十六进制) 汉字内码(十六进制)
啊(16 01) 10H 01H 30H 21H B0H A1H

输入:输入编码

拼音 a1 => 国标码 => 汉字内码

输出:汉字字形码

就是对应到一个字块的像素

2.3.3. 字符串

  • 从低地址到高地址逐个字符存储, 很多语言中, ‘\0’作为字符串结尾标识
  • 对于多字节的数据(如汉字), 可采取大/小端存储模式
  • 大/小端模式: 将数据的最高有效字节存放在低/高地址单元中

2.4. 奇偶校验

2.4.1. 原理

  • 码字: 由若干位代码组成的一个字

  • 将两个码字逐位进行对比, 具有不同的位的个数称为两个码字间的距离

  • 码距(d): 一种编码方案可能有若干个合法码字, 各合法码字间的最小距离我称为码距

  • 当d = 1时, 无检错能力

  • 当d = 2时, 有检错能力

  • 当d = 3 时, 可能有检错、纠错能力

奇校验码: 整个校验码(有效信息位和校验位) 中的 1 个个数为奇数

奇/偶 校验位 信息位
1/0 n

偶校验硬件实现:

  • 计算校验位的值: 各信息进行异或运算, 得到的结果即为偶校验位

  • 校验: 对所有位进行异或, 结果为1说明出错

2.4.2. 海明码

由于奇偶校验的策略仅加了一个比特位, 只能携带2种状态, 能发现数位错误, 但无法确定是哪一位出错

海明码是在原数据中的一些固定位置,插入一个0(或1),以进行奇(或偶)校验位,虽然使原数据变长,但可使其拥有纠错能力。
能侦测并更正一个比特的错误;若有两个比特出错,则只能侦测,不能更正;若有三个或更多的比特出错,则不能侦测,更不能更正。

海明码设计思路: 将信息位分组进行偶校验 => 多个校验位 => 多个校验位标注出错位置

n个信息位 k个校验位 共 n+k 位 2^k种状态

公式: 2^k^ >= n + k + 1

n 1 2-4 5-11 12-26 27-57 58-120
k 2 3 4 5 6 7

求解步骤

信息位: 1010

  1. 确定海明码的位数:2^k^ >= n + k + 1

n = 4 一> k = 3

设信息位D4D3D2D1(1010),共4位,校验位P1P2P3, 共3位, 对应海明码为H7H6H5H4H3H2H1.

  1. 确定校验位的分布

校验位Pi 放在海明码位号为2^i-1^的位置上,其余信息位顺序放

H7 H6 H5 H4 H3 H2 H1
D4 D3 D2 P3 D1 P2 P1
1 0 1 0 0 1 0
  1. 求校验位的值

H3 : 3 转二进制 0 1 1

H5 : 5 —>>> 1 0 1

H6 : 6 —>>> 1 1 0

H7 : 7 —>>> 1 1 1

根据P下标对应二进制位的权重进行分组

​ P1 对应二进制权重1的位置为1的信息位 H3 H5 H7 再根据对应信息位的值求异或得 0

​ P2 对应二进制权重2的位置为1的信息位 H3 H6 H7 再根据对应信息位的值求异或得 1

​ P3 对应二进制权重4的位置为1的信息位 H5 H6 H7 再根据对应信息位的值求异或得 0

  1. 纠错

校验方程:校验位及其对应分组值求异或 为0即无错误

接收到:1010010 无错误

接收到:1010000 H2位错误

​ S1 => P1 H3 H5 H7 => H1 H3 H5 H7 => 0011 => 0

​ S2 => P2 H3 H6 H7 => H2 H3 H6 H7 => 0001 => 1

​ S3 => P3 H3 H5 H7 => H4 H3 H5 H7 => 0011 => 0

错误位即: 010 => H2

  1. 补充

海明码的检错、纠错能力:1位可纠错, 2位可检错

实际使用中会在头部再加一个全校验位H8(P),对整体进行偶校验

S3S2S1 = 000 且全体偶校验成功 -> 无错误

S3S2S1 != 000 且全体偶校验失败 -> 1位错误,纠正即可

S3S2S1 != 000 且全体偶校验成功 -> 2位错误,不可纠正,需重传

2.5. 循环冗余校验

Cyclic Redundancy Check, CRC

2.5.1. 原理

  1. 数据发送、接受方约定一个”除数“

  2. K个信息位 + R个校验位 作为”被除数“, 添加校验位后需要保证除法的余数为0

  3. 收到数据后,进行除法检查余数是否为0

  4. 若余数为非0说明出错,则进行纠错或重传

2.5.2. 构造方法

模二除:十进制除法基础上,每次上的值由被除数高为是否为1决定,1就上1,0就上0再加一位(模二除后余数位数应该正好比除数少1位)

模二减:对应位取异或

  1. 由生成多项式确定”除数”。若生成多项式中x的最高次为R,则”除数“有R+1位
  2. K个信息位 + R个0,作为”被除数“
  3. 被除数、除数 进行”模二除“,得R位余数
  4. K个信息位 + R位余数 = CRC码

2.5.3. 检错纠错

  1. 可检测出所有奇数个错误
  2. 可检测出所有双比特的错误
  3. 可检测出所有小于等于校验位长度的连续错误
  4. 若选择合适的生成多项式,且2^R^ >= K + R + 1,则可纠正单比特位错误

2.5.4. 例

设生成多项式为G(x) = x^3^ + x^2^ + 1, 信息码为101001,求对应的CRC码。

  1. 确定K、R以及生成多项式对应的二进制码

K=信息码的长度:6

R=生成多项式最高次幂:3

N(校验码位数) = K + R = 9

生成多项式G(x) = 1 * x^3^ + 1 * x^2^ + 0 * x^1^ + 1 * x^0^, 对应二进制码1101

  1. 移位

信息码左移R位,低位补0,对应二进制码 1101000

  1. 相除(模二除)

101001 000 模二除 1101 余数001,

得CRC码 101001 001

  1. 检错和纠错

发送: 101001001

接收: 101001001 用1101进行模二除 余数为000,代表没出错

接收: 101001011 用1101进行模二除 余数为010,代表出错

信息位+校验位 共9位,但校验位3位8保状态,无法表示全部所以才没纠错能力

K个信息位,R个校验位,若生成多项式选择得当,且2^R^ >= K + R _+ 1, 则CRC码可纠正1位错

CRC码实际常应用在计算机网络中,几千个bit的信息位 + 几个校验位, 仅用来检错,不用来纠错

2.6. 定点数

定点数: 小数点的位置固定 995.222 –常规计数

浮点数:小数点的位置不固定 9.95222 * 10^2^ –科学计数

二进制和十进制的一样也是分为定点和浮点

2.6.1. 无符号数

  • 概念:整个机器字长的全部二进制位均为数值位,没有符号位,相当于数的绝对值。

  • 表示范围:n位二进制数:0 ~ 2^n^ - 1

  • 通常只有无符号的整数,而没有无符号小数

2.6.2. 有符号数定点表示

数值部分也称尾数

用定点方式表示19.75时,需要把整数和小数部分分别单独保存

可用原码、反码、补码三种方式来表示定点整数和定点小数。还可以用移码表示定点整数

若真值为x,则用 [x]、 [x]、 [x]、 [x]、分别表示真值所对应的原码、反码、补码、移码

  • 定点整数
x0 x1 x2 xn
符号位 小数点位置(隐含)
  • 定点小数
x0 x1 x2 xn
符号位 小数点位置(隐含)

原码:用尾数表示真值的绝对值,符号位 0/1 对应 正/负

  • 整数表示范围(机器字长n+1):-(2^n^-1) <= x <= 2^n^-1
    • 真值0有+0和-0两种形式
    • 8位表示整数+19:0 0010011
  • 小数表示范围(机器字长n+1): -(1-2^-n^) <= x <= 1-2^-n^
    • 真值0有+0和-0两种形式
    • 8位表示小数-0.75: 1 110000

反码:若符号位为0,则反码与原码相同;若符号位为1,则数值位全部取反

  • 整数/小数 表示范围和原码相同

补码:正数的补码与原码相同;负数的补码=反码末位+1(要考虑进位)

和MySQL对比

  • 大小写: oracle 本身不区分大小写 (会把关键字全部转为大写再执行),但是对引号里的字符区分大小写。

  • 主键自增: oracle 没有自带的主键自增, 需要先创建一个序列, 再创建一个触发器, 来实现主键自增

相关命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 查看当前用户所有序列
select * from user_objects where object_type='SEQUENCE';

# 查看表的所有触发器
select * from all_triggers where table_name='表名';

# 创建序列
create sequence 序列名称(一般是表名_SEQ)
start with 1 # 开始数字
minvalue 1 # 最小值
maxvalue # 最大值
cycle # 是否循环 cycle / nocycle
increment by 1 # 增长数字
nocache # 不使用缓存

# 删除序列
DROP SEQUENCE 序列名;

# 创建触发器
create or replace trigger 触发器名
before insert on 表名
referencing old as old new as new
for each row
begin
select 序列名.nextval
into :new.ID
from dual;
end 触发器名;

# 删除触发器
drop trigger 触发器名;


基操

1
2
3
4
5
6
7
8
9
# 查版本
select * from v$version;

# sqlalchemy_URI 注: 不用指定数据库
oracle+cx_oracle://{账号}:{密码}@10.168.199.21:1521/?service_name={服务名}




Docker Alpine安装oracle客户端

  1. 进入docker容器

    1
    2
    3
    docker run -it --name=容器名 镜像名:latest /bin/sh       由镜像创建容器并进入(只有镜像无容器)

    docker exec -it 容器名 /bin/sh 直接进入运行中的容器
  2. 安装必要包

1
apk add libaio, libnsl, unzip
  1. 下载解压oracle basic包

需要对应服务器版本, 命令select * from v$version;

各版本下载地址(32位): https://www.oracle.com/database/technologies/instant-client/linux-x86-32-downloads.html

各版本下载地址(64位): https://www.oracle.com/database/technologies/instant-client/linux-x86-64-downloads.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 下载basic免安装zip包, 需要登录验证, 所以下载链接不能复用, 要在上面链接中找对应版本下载

# 弄个单独目录存
cd /usr/local
mkdir oracle
cd oracle

# wget -c 支持断点续传
wget -c https://download.oracle.com/otn/linux/instantclient/11204/instantclient-basic-linux.x64-11.2.0.4.0.zip
?AuthParam=1644827926_6725c450378f19288cb3dc5d040b8a18

# unzip 解压
unzip instantclient-basic-linux.x64-11.2.0.4.0.zip?AuthParam\=1644827926_6725c450378f19288cb3dc5d040b8a18

# 整理下文件
mv ./instantclient_11_2/* ./
rmdir instantclient_11_2

unzip End-of-central-directory signature not found: 多半是下载的文件有问题, 重新下载就可以

  1. 添加环境变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 通过查看 /etc/profile 可以看到会加载 /etc/profile.d/目录下的 .sh结尾的文件

# 弄个单独的环境变量配置文件
vi /etc/profile.d/oracle.sh

export ORACLE_HOME=/usr/local/oracle
export NLS_LANG=AMERICAN_AMERICA.AL32UTF8
export LD_LIBRARY_PATH=$ORACLE_HOME
export PATH=$ORACLE_HOME:$PATH

# 测试下加上没有
source /etc/profile
echo $PATH

# 退出容器
exit

注: 这里会有很多奇怪的报错, 以下为作者碰到的

报错1. Error loading shared library /usr/local/oracle/lib/libclntsh.so: No such file or directory

(一开始是打算用软链接的, 但是没用, 直接复制就可以了, 不知道为什么)

1
2
mkdir /usr/local/oracle/lib
cp /usr/local/oracle/libclntsh.so.11.1 /usr/local/oracle/lib/libclntsh.so

如果经过以上操作扔有此报错, 且是用supervisor启动的, 可能是因为supervisor没读到环境变量, 需要在supervisor配置文件中添加

1
2
environment=ORACLE_HOME="/usr/local/oracle",NLS_LANG="AMERICAN_AMERICA.AL32UTF8",LD_LIBRARY_PATH="/usr/local/oracle"

注: supervisor添加环境变量时, 多个变量以,分隔, 单个变量多个值以:分隔

报错2.
Error loading shared library libnsl.so.1: No such file or directory (needed by /usr/local/oracle/lib/libclntsh.so
(libnsl.so.1 或 libnsl.so) 建对应软链接

1
2
ln -s /usr/lib/libnsl.so.2.0.0 /usr/lib/libnsl.so.1
ln -s /usr/lib/libnsl.so.2.0.0 /usr/lib/libnsl.so

报错3: cx_Oracle.DatabaseError: ORA-21561: OID generation faile

这是hosts文件有问题

1
2
hostname  # 查看当前hostname
把当前hostname加入到 /etc/hosts 文件的 127.0.0.1 即可
  1. 提交镜像
1
docker commit -a 'laowang' 容器名 镜像名:1.0.0
  1. 换docker启动用的镜像版本号

注: 最后会发现环境变量没有加载, 原因未知

绕路解决方法:

  • 通过DockerFile直接运行容器的项目: 修改Dockerfile文件, 例: CMD source /etc/profile && gunicorn ....
  • 通过DockerCompose启动的项目: 修改docker-compose.yml文件 例: command: /bin/sh -c "source /etc/profile && gunicorn ...."

python 直连测试代码

import cx_Oracle
conn = cx_Oracle.connect(“user/passwd@host/instance”)

报错

  • 报错ORA-00904: 后查到是数据库字段写错了, 和模型类的不一致

  • 索引失效(partition of such index is in unusable state):

    • select index_name,status from user_indexes;  # 查失效索引
      
      alter index SYS_C00164313 rebuild;  # 重建索引
      

报错内容

1
2
3
4
5
6
7
8
9
fatal: in unpopulated submodule '.deploy_git'
FATAL {
err: Error: Spawn failed
at ChildProcess.<anonymous> (/Users/krmac/myProjects/blog/node_modules/hexo-util/lib/spawn.js:51:21)
at ChildProcess.emit (node:events:390:28)
at Process.ChildProcess._handle.onexit (node:internal/child_process:290:12) {
code: 128
}
}

解决

直接删除 .deploy_git 重新 hexo g 生成即可