Python学习
基础语法
编程风格规范
# PEP8规范 ----python编码风格
# 缩进四个字符
# 运算符周围空格
# 注释独占一行
# 逗号后面空格
# 函数,类定义都会空行
# 续行符 \
数据类型
*python是强类型语言, 不同类型的数据不能进行计算*
*python是动态类型语言*
基本数据类型
整数(int
)、浮点数(float
)、布尔值(bool
)
- 整数
-
实现方式
在 CPython 里,整数类型是使用变长对象来实现的。Python 的整数可以表示任意大小的整数,不会像 C 语言中的
int
类型那样存在溢出问题。 -
原理
Python 为小整数(通常是 -5 到 256)维护了一个全局的整数对象池,当使用这些小整数时,直接从对象池中获取,避免重复创建对象。对于大整数,Python 会动态分配内存来存储其值。
-
- 浮点数
-
实现方式
Python 的浮点数类型是基于 C 语言的
double
类型实现的,遵循 IEEE 754 标准。 -
原理
在内存中,浮点数使用 64 位(8 字节)来表示,其中 1 位表示符号,11 位表示指数,52 位表示尾数。由于浮点数的精度问题,可能会出现一些舍入误差。
-
容器数据类型
字符串(str)
-
实现方式
Python的字符串是不可变的序列对象,使用Unicode编码来存储字符
列表(list)
-
实现方式
列表是可变的序列对象,底层使用动态数组实现
-
原理
列表对象在内存中是一个连续的内存块,存储了指向列表中各个元素的指针。当列表需要扩容时,会重新分配一块更大的内存。
字典(dict)
-
实现方式
字典是键值对的集合,底层使用哈希表实现
-
原理
字典中的键通过哈希函数计算出哈希值,然后根据哈希值找到对应的数组位置。键必须是可哈希的(即不可变类型),因为在哈希表中需要根据键的哈希值来查找和存储值。当发生哈希冲突时,也会使用开放寻址法或链表法来解决。
元组(tuple)
-
实现方式
元组是不可变的序列对象,底层实现类似于列表,但元组的元素一旦确定就不可以修改
-
原理
元组对象在内存中也是一个连续的内存块,存储了指向元组中各个元素的指针,由于元组是不可变的,所以在创建后不会进行扩容操作
集合(set)
-
实现方式
集合是无序且唯一的元素集合,底层使用哈希表实现。
-
原理
哈希表是一种通过哈希函数将元素映射到数组位置的数据结构。在集合中,每个元素都会通过哈希函数计算出一个哈希值,然后根据哈希值找到对应的数组位置。如果发生哈希冲突(即不同的元素计算出相同的哈希值),会使用开放寻址法或链表法来解决冲突。冻结集合(
frozenset
)是不可变的集合,其底层实现与集合类似,但创建后不能修改。
Python文件操作
基本操作
Python 文件操作主要借助 open()
函数,其基本操作如下:
- 打开文件:
open(file_path, mode, encoding)
,mode
有r
(读)、w
(写)、a
(追加)等,encoding
常用utf - 8
。 - 读取文件:可用
read()
读全部内容,readline()
逐行读,readlines()
读所有行存于列表。 - 写入文件:
w
模式覆盖写,a
模式追加写,用write()
写入字符串。 - 关闭文件:推荐用
with
语句自动管理,避免资源泄漏。 - 二进制文件:用
'b'
模式,如'rb'
读、'wb'
写。 - 文件指针:
seek()
移动位置,tell()
获取当前位置。
存储相关
程序运行内存
数据缓存 → cache1、cache2、cache3或者内存
数据固化:将数据存储到数据库、磁盘上永久保存
数据固化的前沿操作:数据序列化
存储形式:
-
转换为二进制数据直接存储(只有python少量语言能够理解)
-
将数据转换成一种特定的格式,以便可以在网络上进行传输或存储
常见的数据格式(上述特定的格式)
json — 轻量级的数据交换格式 — 本质上是字符串
import json
# 字典转换为json格式
dict1 = {'姓名':"wzd"}
json_str1 = json.dumps(dict1)
print(json_str1, type(json_str1))
# 将json格式转换为字典格式
dict2 = json.loads(json_str1)
print(dict2, type(dict2))
二进制存储
import pickle
内存概念:数据动态 数据易丢失 → 数据固化
文件打开方式:
r 只读 r+ 读写 二进制文本 rb
w 只写 w+
a 追加写 a+
x 新建并写入
# 文件读写
# 文件指针 - 文件读写的时候是读取或者写当前光标的位置(需要特定的写入或者重复读取某些元素时需要用到)
# 默认的打开方式为只读、buffer(缓存)、encoding(编码,默认为None,系统编码)
fp = open(file=file_path, mode='r', buffer=-1, encoding=None)
# 新建文件并写入
file1 = open('text.txt', 'w')
file1.write("abc/nxyz")
# 文件的读取
fp = open('test.txt')
# 读取文件
fp.read() # 默认从头读到尾或者指定字节长度的内容
fp.readline() # 从当前位置读到行尾
fp.readlines() # 将文件从当前位置读到末尾,每一行作为列表的一个元素,最后返回一个列表
# 重置指针位置
fp.seek(0) # 将文件指针位置放置最开始的位置
# with 语句 管理python中上下文管理器的
# 做这个事情前,要额外做什么,退出这个事情之后,要额外做什么
with open("test.txt") as fp:
print(fp.read())
# 在退出时自动执行fp.close()
文件读写过程详解
写缓存:当程序执行时并没有将数据直接写入磁盘,而是会将数据写入缓存区
缓存区写入磁盘的条件:
- 当程序正常退出时,再将缓冲区中的数据写入磁盘
- 缓冲区满的时候写入磁盘
# 手动调用该函数写入
fp.flush()
# 关闭文件写入缓存
fp.close()
python项目学习
gunicorn工具
Kafka项目
Flask框架
Django框架
异常处理
异常的处理与捕获
基本语法
try:
print(a)
except ZeroDivisionError as e:
print(f"除数为零的异常{e}")
except Exception as e: # 针对不同异常做不同处理,exception父类放到最后面
print(f"捕获所有异常{e}")
finally:
print("无论如何都会执行的代码")
else:
print("没有异常时会做的处理!!!")
有关finally的相关语法
在包含 finally
块的函数中,返回操作需要特别注意。如果 try
或 except
块中有 return
语句,finally
块中的代码仍然会在返回之前执行。以下是几种不同情况的示例:
- 情况一:
try
块中有return
语句
def test_function():
try:
return 1
finally:
print("finally 块被执行。")
result = test_function()
print("返回结果:", result)
res:
**finally 块被执行
返回结果:1**
except
块中有return
语句
def test_function():
try:
num = 1 / 0 # 引发异常
except ZeroDivisionError:
return 2
finally:
print("finally 块被执行。")
result = test_function()
print("返回结果:", result)
res:
**finally 块被执行
返回结果:2**
finally
块中修改返回值
在这个例子中,try
块中有 return 3
语句,但 finally
块中有 return 4
语句,最终函数会返回 4,打印出 “返回结果: 4”。所以,一般不建议在 finally
块中使用 return
语句,以免造成混淆。
def test_function():
try:
return 3
finally:
return 4
result = test_function()
print("返回结果:", result)
res:
返回结果:4
异常的类型(常见的异常)
语法错误异常(SyntaxError)
类型错误异常(TypeError)
索引错误异常(IndexError)
文件未找到错误异常(FileNotFoundError)
自定义异常
名称错误异常(NameError)
值错误异常(ValueError)
键错误异常(KeyError)
除零错误异常(ZeroDivisionError)
异常的继承(关系)
BaseException # 所有异常的基类
+-- SystemExit # 解释器请求退出
+-- KeyboardInterrupt 用户中断执行(通常是输入^C)
+-- GeneratorExit # 生成器(generator)发生异常来通知退出
+-- Exception # 常规异常的基类
+-- StopIteration # 迭代器没有更多的值
+-- StandardError # 标准错误
| +-- BufferError
| +-- ArithmeticError
| | +-- FloatingPointError
| | +-- OverflowError
| | +-- ZeroDivisionError
| +-- AssertionError
| +-- AttributeError
| +-- EnvironmentError
| | +-- IOError
| | +-- OSError
| | +-- WindowsError (Windows)
| | +-- VMSError (VMS)
| +-- EOFError
| +-- ImportError
| +-- LookupError
| | +-- IndexError
| | +-- KeyError
| +-- MemoryError
| +-- NameError
| | +-- UnboundLocalError
| +-- ReferenceError
| +-- RuntimeError
| | +-- NotImplementedError
| +-- SyntaxError
| | +-- IndentationError
| | +-- TabError
| +-- SystemError
| +-- TypeError
| +-- ValueError
| +-- UnicodeError
| +-- UnicodeDecodeError
| +-- UnicodeEncodeError
| +-- UnicodeTranslateError
+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarnin
+-- ImportWarnin
+-- UnicodeWarnin
··· +-- BytesWarning
异常的抛出
assert - 断言异常
在 Python 中,assert
语句用于调试目的,它可以在代码中插入检查点,当条件不满足时会抛出 AssertionError
异常。
与python中的test库的功能类似,轻量化
assert condition, message
raise - 主动抛出异常
# raise xxx
def test_zero(num):
try:
if num == 0:
raise ValueError('参数错误')
return num
except Exception as e:
print e
test_zero(0)
模块和包
在 Python 中,模块(Module)和包(Package)是组织代码的重要方式,它们有助于提高代码的可维护性、可复用性
模块
模块就是python文件
print(f"__name__ is {__name__}")
if __name__ == "__main__":
print("我是直接运行的!!!")
直接运行
直接运行是在PyCharm中右击run运行程序此时__name__的值为__main__
导入运行
模块在被导入时是会被运行的,模块中的代码都会执行一遍
当导入运行时__name__的值为模块的绝对地址,比如model_test
面向对象 oop
面向过程:着重于做什么
面向对象:着重于谁去做
命名空间
类空间
实例空间
属性查找:
- 先在当前实例空间里查找属性和方法,找到即可返回
- 没找到,通过类对象指针,去类空间查找
- 类空间没有,就去父类空间查找
__dict__ # 查看空间
__bases__ # 查看继承关系
__init__ # 属于类空间
类(class)
python3中类也叫新式类((继承顺序)使用C3算法) —— 默认继承python内置类的类
python2 中的类有新式类和经典类(使用深度优先算法) python2中class B(object) 等价于python3中不写继承
最大的区别在于类的继承顺序关系
- 类可以多重继承
实例化
- 创建一个实例(new)
- 对实例进行初始化工作(**init**)
__new__ # 静态方法 用于创建一个实例
__init__ # 实例方法 用于对实例进行初始化工作
方法
- 实例方法 最普通的方法,定义在类中,调用者代表实例本身
- 类方法 使用类方法装饰器修饰,调用者代表类本身
- 静态方法 使用静态方法装饰器修饰,调用者不代表任何人
@classmethod # 类方法装饰器,使用这个装饰的方式称为类方法,调用者代表类本身
@staticmethod # 静态方法装饰器
成员变量
- 私有成员
self.__age # 私有成员,只可以方法的内部访问 - 伪私有
# 不可以直接通过名称进行访问
__age -> _类名__age (能够正常访问)
魔术方法
类中定义,由双下划线开头双下划线结尾,并且不需要显示调用,某种场合自动执行的方法 魔术方法都是有特殊意义的
初始化与销毁
__init__(self, ...)
:类实例化对象时自动调用,用于对象的初始化操作。__new__(cls, ...)
:在__init__
之前调用,是类实例化时调用的第一个方法,主要作用是创建并返回一个新的对象实例,通常用于实现单例模式等特殊需求。__del__(self)
:当对象被销毁时调用,可用于释放对象占用的资源。
字符串表示
__str__(self)
:使用str()
函数或print()
函数打印对象时调用,返回一个可读性较好的字符串。__repr__(self)
:返回一个能准确描述对象的字符串,通常用于调试和开发环境,若未定义__str__
,print()
函数也会调用它。__format__(self, format_spec)
:在使用format()
函数或格式化字符串时调用,用于自定义对象的格式化输出。
比较操作
__eq__(self, other)
:定义对象之间的相等比较操作(==
)。__ne__(self, other)
:定义对象之间的不相等比较操作(!=
)。__lt__(self, other)
:定义对象之间的小于比较操作(<
)。__le__(self, other)
:定义对象之间的小于等于比较操作(<=
)。__gt__(self, other)
:定义对象之间的大于比较操作(>
)。__ge__(self, other)
:定义对象之间的大于等于比较操作(>=
)。
['__repr__', '__call__', '__getattribute__', '__setattr__', '__delattr__', '__init__', '__new__', 'mro', '__subclasses__', '__prepare__', '__instancecheck__', '__subclasscheck__', '__dir__', '__sizeof__', '__basicsize__', '__itemsize__', '__flags__', '__weakrefoffset__', '__base__', '__dictoffset__', '__mro__', '__name__', '__qualname__', '__bases__', '__module__', '__abstractmethods__', '__dict__', '__doc__', '__text_signature__', '__hash__', '__str__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__class__']
type和object
type是元类,创建类的类
object是继承类的最初类
抽象基类
定义接口规范,类不能实例化,规定子类必须实现父类的抽象方法, 统一接口
import abc
# 定义抽象基类
class Shape(abc.ABC):
@abc.abstractmethod
def area(self):
pass
# 定义子类,实现抽象方法
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
# 创建 Rectangle 对象
rect = Rectangle(3, 4)
print(rect.area())
# 尝试实例化抽象基类,会抛出 TypeError
# shape = Shape()
面向对象三大特点
python中的一切类型皆为对象:int、str、dict等都是对象,因此我们可以自定义数据类型
封装
封装是面向对象编程(OOP)的一个核心概念,它将数据(属性)和操作这些数据的方法捆绑在一起形成一个类,并且对外部隐藏对象的内部实现细节,仅对外提供必要的接口。
继承
在 Python 里,继承是面向对象编程的一个重要特性,它允许一个类(子类)继承另一个类(父类)的属性和方法。继承可以提高代码的复用性,让代码更易于维护和扩展。
super().fuction_name() # 子类拿到父类的方法
# 查看是否属于某个类(包含继承关系)
isinstance(d1, dict)
多态
简单来说,就是接口的不同形态 python 动态类型语言
鸭子类型(Duck Typing)
Python 是动态类型语言,不关心对象的具体类型,只关心对象是否具有特定的方法。如果一个对象走起路来像鸭子、叫起来也像鸭子,那么它就可以被当作鸭子。这就是鸭子类型的概念。
在 Python 里,多态是面向对象编程的一个重要特性,它允许不同的对象对同一消息做出不同的响应。多态性增强了代码的灵活性和可扩展性,让代码能更高效地处理不同类型的对象。
多进程多线程编程
CPU相关
CPU亲和性绑定:进行CPU绑定(多核CPU切换程序运行),即一个程序被某个CPU固定执行 - 通过减少线程/进程在CPU核心间(多个CPU情况)的迁移,提高缓存利用率,降低上下文切换开销,从而提高性能。
CPU缓存:CPU在运行程序的过程中,由于CPU是并发运行程序的,在继续执行某一个程序时会有上下文切换的概念,CPU会生成缓存记录上一次执行的偏移,这就叫CPU缓存。
Linux五大内核子系统
- 进程调度
- 进程通信
- 文件系统
- 内存管理
- 网络接口
进程 - 资源分配的最小单位
进程是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位。每个进程都有自己独立的地址空间、内存、文件描述符等资源。
适用于 CPU 密集型任务,像大规模数据处理、科学计算等,因为进程能在多核 CPU 系统中并行执行,充分利用多核资源。
协程
协程也叫微线程,是一种用户态的轻量级线程。协程的调度由用户控制,不像线程那样由操作系统内核调度。
线程 - 进行调度的最小单位
线程是进程中的一个执行单元,是程序执行的最小单位。一个进程可以包含多个线程,它们共享进程的资源,如地址空间、内存等,但有各自独立的栈空间、寄存器和程序计数器。
适用于 I/O 密集型任务,例如网络请求、文件读写等。不过在 Python 里,由于全局解释器锁(GIL)的存在,多线程在 CPU 密集型任务中无法充分利用多核 CPU 的优势。
线程锁
-
互斥锁
解决资源争抢。保障线程安全
import threading # 共享资源 shared_variable = 0 # 创建锁对象 lock = threading.Lock() # RLock 如果已经获取了锁则不会继续等待获取 def increment(): global shared_variable for _ in range(100000): # 获取锁 lock.acquire() try: # 访问共享资源 shared_variable += 1 finally: # 释放锁 lock.release() # 创建两个线程 thread1 = threading.Thread(target=increment) thread2 = threading.Thread(target=increment) # 启动线程 thread1.start() thread2.start() # 等待线程执行完毕 thread1.join() thread2.join() print(f"共享变量的最终值: {shared_variable}") # 还可以使用with上下文管理器来得到释放互斥锁 with lock: shared_variable += 1
-
条件变量
通过判断是否满足某个条件来决定是否分配资源
-
信号量
控制线程的并发数量
-
条件锁
信号量机制与条件变量机制的结合
死锁的避免:
- 按顺序获取锁
- 使用超时机制
- 使用上下文with管理锁
- 减少锁的粒度
银行家算法
银行家算法是一种资源分配与死锁预防算法。
银行家算法通过对资源分配的严格控制和系统状态的实时检查,有效地预防了死锁的发生,提高了系统资源的利用率和稳定性。
具体实现步骤:
- 初始化资源信息:
- 进程请求资源:
- 系统尝试分配资源:
- 检查系统安全性:
- 确定资源分配决策:
面试回答
- 真正在cpu上运行的是线程
- 线程共享内存空间,进程的内存空间是独立的
- 一个线程只能属于一个进程,而一个进程可以共享该进程的所有资源,但至少有一个线程
- 资源分配给进程,同一进程的所有线程共享该进程的所有资源。进程间的资源是独立的
- 同一个进程的线程之间可以直接交流,两个进程之间向通信必须通过一个中间代理来实现
#GIL - 全局解释性锁
GIL 是 CPython 解释器为了保证线程安全而引入的一个互斥锁。这意味着在同一时刻,CPython 解释器只能允许一个线程执行 Python 字节码。也就是说,即使在多核 CPU 系统中,使用多线程进行 Python 编程时,同一时刻也只有一个线程能真正执行 Python 代码,其他线程需要等待 GIL 被释放。
当线程遇到io阻塞时会释放全局解释器锁 = 适用于io密集型任务
Python解释器
-
CPython
CPython是Python最常用的解释器,由C语言编写。是Python官方实现的解释器
-
PyPy
是python语言的另一种实现,使用即时编译技术来提高代码的执行速度。对于计算密集型任务速度更快
-
Jython
运行在Java虚拟机上的Python解释器,允许Python代码与Java代码进行交互
-
IronPython
是运行在.NET平台上的Python解释器,允许Python代码与.NET代码进行交互
扩展:资源限制 - ulimit
- 内核参数调优 /etc/sysctl.conf
扩展:dns域名服务器 /etc/resolv.conf
- 用户限制调优
# 查看用户资源限制
ulimit -a
# 临时进行修改
ulimit -n xxxx
# 永久修改生效
vim /etc/security/limits/conf
# 示例
[root@sc ~]# ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 14989
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 14989
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
高阶语法
高阶函数
高阶函数满足以下两个条件中的任意一个:
- 接受函数作为参数:函数可以将其他函数作为参数传入,在函数内部调用这些传入的函数。
- 返回函数作为结果:函数的返回值可以是另一个函数。
常见的内置高阶函数
-
map()
函数会根据提供的函数对指定序列做映射。它接收一个函数和一个或多个可迭代对象作为参数,返回一个迭代器,该迭代器中的每个元素是将可迭代对象中的元素依次传入函数后得到的结果。# 示例:将列表中的每个元素平方 numbers = [1, 2, 3, 4] squared = map(lambda x: x ** 2, numbers) print(list(squared)) # 输出: [1, 4, 9, 16]
-
filter()
函数用于过滤序列,过滤掉不符合条件的元素,返回一个迭代器,该迭代器包含所有使函数返回值为True
的元素。它接收一个函数和一个可迭代对象作为参数。
自定义高阶函数
def multiply_by(n):
def multiplier(x):
return x * n
return multiplier
# 创建一个乘以 3 的函数
triple = multiply_by(3)
print(triple(5)) # 输出: 15
- 代码复用:通过将函数作为参数传递,可以在不同的场景下复用相同的逻辑,减少代码重复。
- 灵活性:高阶函数可以根据传入的函数动态改变行为,提高代码的灵活性和可扩展性。
可迭代对象
可以使用 collections.abc
模块中的 Iterable
来判定一个对象是否为可迭代对象
from collections.abc import Iterable
my_list = [1, 2, 3]
my_num = 123
print(isinstance(my_list, Iterable)) # 输出: True
print(isinstance(my_num, Iterable)) # 输出: False
常见的可迭代对象
- 列表
- 元组
- 字符串
- 字典
- 集合
可迭代对象的工作原理
可迭代对象之所以能被遍历,是因为它实现了 __iter__()
方法。该方法会返回一个迭代器对象,迭代器对象实现了 __next__()
方法,用于逐个返回元素。当没有更多元素时,__next__()
方法会抛出 StopIteration
异常。
my_list = [1, 2, 3]
my_iter = iter(my_list) # 调用 __iter__() 方法获取迭代器
print(next(my_iter)) # 输出: 1
print(next(my_iter)) # 输出: 2
print(next(my_iter)) # 输出: 3
try:
print(next(my_iter)) # 抛出 StopIteration 异常
except StopIteration:
print("No more elements")
迭代器 惰性求值/懒加载
1. 迭代器的定义与基本概念
迭代器是一个实现了 __iter__()
和 __next__()
方法的对象。
__iter__()
方法:该方法返回迭代器对象本身,这使得迭代器可以在for
循环等场景中被使用。__next__()
方法:该方法返回迭代器的下一个元素。当没有更多元素时,它会抛出StopIteration
异常。
2.创建迭代器
-
内置可迭代对象转换
Python 中的许多内置对象,如列表、元组、字符串等都是可迭代对象,可以使用
iter()
函数将它们转换为迭代器。示例如下:my_list = [1, 2, 3, 4, 5] my_iterator = iter(my_list) print(next(my_iterator)) # 输出 1 print(next(my_iterator)) # 输出 2
-
自定义迭代器类
可以通过创建一个类,实现
__iter__()
和__next__()
方法来定义自定义迭代器。示例如下:class MyRange: def __init__(self, start, end): self.current = start self.end = end def __iter__(self): return self def __next__(self): if self.current < self.end: value = self.current self.current += 1 return value else: raise StopIteration my_range = MyRange(0, 3) for num in my_range: print(num)
3.迭代器的作用
- 节省内存 迭代器中的元素是一次一次加载到内存中的,不会一次性将所有元素都加载到内存中,如range()
- 可以自定义遍历逻辑 可以在迭代器的next函数中书写逻辑,实现斐波那契数列
- 迭代器的局限:迭代器是单向的,一旦迭代完成,无法重新开始迭代
迭代器与可迭代对象的关系
可迭代对象是可以被迭代的对象,它实现了 __iter__()
方法,但不一定实现 __next__()
方法。而迭代器是可迭代对象的一种特殊形式,它同时实现了 __iter__()
和 __next__()
方法。可以使用 iter()
函数将可迭代对象转换为迭代器。
生成器
- 生成器函数
- 生成器表达式
在 Python 里,生成器是一种特殊的迭代器,它让你能更便捷地创建迭代器。
生成器的定义与工作原理
生成器是一种一边循环一边计算的机制,在迭代过程中逐个生成值,而不是一次性生成所有值,这有助于节省内存,特别是在处理大规模数据时。生成器的工作原理基于 Python 的迭代协议,它实现了 __iter__()
和 __next__()
方法。
生成器表达式
生成器的创建与列表推导式十分相似,只是将方括号替换为了圆括号
# 生成器表达式
gen_exp = (x * 2 for x in range(5))
print(gen_exp)
# 列表推导式
list_comp = [x * 2 for x in range(5)]
# 集合推导式
s1 = {x.upper() for x in "sdsds"}
# 字典推导式
d1 = {y:x for x,y in d.items()}
生成器函数
生成器函数是包含 yield
关键字的函数。当函数执行到 yield
语句时,会暂停执行并返回一个值,下次调用时会从暂停的地方继续执行。
def my_generator():
yield 1
yield 2
yield 3
gen = my_generator()
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3
生成器的作用 - 与迭代器类似
使用场景
- 大数据处理 - 但需要进行运算时才进行运算,避免不必要的运算开销
- 无限序列生成 可以使用生成器函数生成无限序列
生成器的控制
使用send可以改变生成器在中间的值
除了使用 next()
函数获取生成器的下一个值,还可以使用 send()
方法向生成器发送值,以及使用 throw()
方法抛出异常,使用 close()
方法关闭生成器。
def my_generator():
value = yield
print(f"Received value: {value}")
gen = my_generator()
next(gen) # 启动生成器
gen.send(10) # 向生成器发送值
生成器是 Python 中非常强大且实用的工具,它在内存管理、性能优化和处理复杂数据序列方面有着重要的应用。
闭包
闭包由函数及其相关的引用环境组合而成。当一个函数在其内部引用了外部函数的局部变量,并且外部函数将这个内部函数作为返回值返回时,就形成了一个闭包。示例如下。
简单来说,就是函数在执行之后留下的是内部函数的引用(能够记录第一次调用时传入的实参)
def outer_function(x):
def inner_function(y):
return x + y
return inner_function
# 创建闭包
closure = outer_function(10)
# 调用闭包
result = closure(5)
print(result) # 输出 15
闭包的优点
- 实现数据的封装与隐藏
- 函数式编程→ 回调函数
注意事项
- 变量绑定问题:在使用闭包时,要注意变量的绑定问题。如果闭包引用的外部变量是可变对象(如列表、字典等),那么在闭包内部对这些变量的修改会影响到外部作用域。
装饰器
装饰器是一种强大且实用的编程工具,它能够在不修改原函数代码的基础上,对函数或类的功能进行扩展。
装饰器的底层原理是基于闭包的
@w1 = f1=w1(f1)
装饰器的定义与原理
装饰器本质上是一个函数,它接受一个函数作为参数,并返回一个新的函数。新的函数通常会在原函数的基础上添加一些额外的功能,比如日志记录、性能测试、权限验证等。装饰器利用了 Python 中函数是一等公民的特性,即函数可以作为参数传递、返回值返回,也可以赋值给变量。
def my_decorator(func):
def wrapper():
print("在函数执行前做一些操作")
func()
print("在函数执行后做一些操作")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
语法糖
多装饰器情况
- 当有多个装饰器装饰函数时,装饰器从上至下开始装饰。但是结果需要考虑递归调用return()
多个装饰器的执行顺序是从下往上,也就是先应用最靠近被装饰函数的装饰器,然后依次向上应用其他装饰器。不过,在调用被装饰后的函数时,装饰器的执行顺序是从外到内,即最外层的装饰器先执行,然后依次执行内层的装饰器,最后执行被装饰的函数。
带参数的装饰器
-
带参数的装饰器
def repeat(n): def decorator(func): def wrapper(*args, **kwargs): for _ in range(n): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) # n为全局变量 def greet(name): print(f"Hello, {name}!") greet("Alice")
在这个例子中,
repeat
是一个返回装饰器的函数,它接受一个参数n
,表示函数需要重复执行的次数。decorator
是真正的装饰器函数,wrapper
函数会重复调用原函数func
n
次。
from functools import wraps
# 保留装饰函数的元数据
类实现装饰器
魔术方法
__call__ # callable方法 对象通过()实现调用该方法
不带参数的装饰器
import time
class Timer:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
start_time = time.time()
result = self.func(*args, **kwargs)
end_time = time.time()
print(f"函数 {self.func.__name__} 执行耗时: {end_time - start_time} 秒")
return result
@Timer
def example_function():
time.sleep(2)
return "函数执行完成"
result = example_function()
print(result)
带参数的类装饰器实现
import time
class Timer:
def __init__(self, name):
self.name= name
def __call__(self, func):
def inner(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"函数 {func.__name__} 执行耗时: {end_time - start_time} 秒")
return result
return inner
@Timer("sc")
def example_function():
time.sleep(2)
return "函数执行完成"
result = example_function()
print(result)