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

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