Python基础
基础
GIL 全局解释器锁
Python解释器中的一个互斥锁,它确保同一时间只有一个线程执行Python字节码。GIL的存在主要是为了保护Python对象免受多线程并发访问的影响,从而简化内存管理并提高程序的安全性。
影响
CPU密集型任务 在CPU密集型任务中,GIL的存在会显著限制多线程程序的性能。由于GIL限制了同一时间只有一个线程可以执行Python字节码,即使在多核CPU上,多线程程序也无法真正实现并行计算。因此,对于需要大量计算的任务(如数值计算、图像处理等),多线程程序的性能可能不如单线程程序,甚至可能因为线程切换等开销而变得更差。
O密集型任务 在I/O密集型任务中,GIL的影响相对较小。I/O密集型任务通常涉及大量的输入输出操作(如文件读写、网络通信等),这些操作通常会阻塞线程。
当一个线程被I/O操作阻塞时,GIL会被释放,其他线程可以获取GIL并继续执行。因此,在I/O密集型任务中,多线程程序可以通过线程切换来提高程序的整体效率,充分利用等待I/O的时间来执行其他任务。
解决
多进程 多进程是解决GIL限制的一种有效方法。由于每个进程都有自己的Python解释器和内存空间,因此GIL不会影响不同进程之间的执行。
Python的multiprocessing模块提供了方便的接口来创建和管理进程。
使用C扩展 将CPU密集型任务用C语言实现,并将其封装为Python扩展模块,可以在Python程序中调用这些C扩展模块来提高性能。
例如,NumPy、Pandas等库就是通过C扩展来实现高性能计算的。
深拷贝和浅拷贝
浅拷贝
创建一个新对象,但只复制原对象的引用,而不是实际的数据内容。如果原对象中包含嵌套对象(如列表、字典等),浅拷贝会复制这些嵌套对象的引用,而不是创建新的嵌套对象。
深拷贝
创建一个全新的对象,并递归地复制原对象中的所有嵌套对象。深拷贝会完全独立于原对象,修改深拷贝中的任何内容都不会影响原对象。
特性 | 浅拷贝 (Shallow Copy) | 深拷贝 (Deep Copy) |
---|---|---|
复制方式 | 只复制第一层对象,嵌套对象共享引用 | 递归复制所有嵌套对象 |
修改影响 | 修改嵌套对象会影响原对象 | 修改深拷贝不会影响原对象 |
实现方式 | copy.copy() 或切片操作(如 list[:] ) | copy.deepcopy() |
性能 | 速度快,占用内存少 | 速度慢,占用内存多(因为需要递归复制所有内容) |
适用场景 | 无嵌套对象或嵌套对象不需要独立修改 | 包含嵌套对象且需要完全独立的副本 |
# 原始数据
original = [[1, 2, 3], [4, 5, 6]]
print("\n### 原始数据")
print("Original:", original)
# 浅拷贝
shallow_copied = copy.copy(original)
print("\n### 浅拷贝")
print("Shallow Copied:", shallow_copied)
# 修改浅拷贝中的嵌套对象
shallow_copied[0][0] = "A"
print("\n### 修改浅拷贝后的结果")
print("Original:", original) # 原始数据也被修改了
print("Shallow Copied:", shallow_copied)
# 深拷贝
deep_copied = copy.deepcopy(original)
print("\n### 深拷贝")
print("Deep Copied:", deep_copied)
# 修改深拷贝中的嵌套对象
deep_copied[0][0] = "B"
print("\n### 修改深拷贝后的结果")
print("Original:", original) # 原始数据未受影响
print("Deep Copied:", deep_copied)
高级
装饰器
用于在不修改原有函数或类代码的情况下,动态地添加或修改其行为。 装饰器本质上是一个函数,它接收一个函数或类作为参数,并返回一个新的函数或类。 装饰器可以用于日志记录、性能测试、事务处理、权限校验等多种场景。
实现
函数可以作为参数传递,也可以作为返回值。 装饰器通常包含一个外层函数和一个内层函数,外层函数接收被装饰的函数或类,内层函数则负责在被装饰的函数执行前后添加额外的逻辑。
参数是函数
def my_decorator(func):
""" 定义装饰器 """
def wrapper(*args, **kwargs): # 内层函数,用于包装被装饰的函数
print("Before function call") # 在被装饰的函数执行前添加逻辑
result = func(*args, **kwargs) # 调用被装饰的函数
print("After function call") # 在被装饰的函数执行后添加逻辑
return result # 返回被装饰函数的结果
return wrapper # 返回内层函数
# 使用装饰器
@my_decorator
def say_hello():
print("Hello!")
# 调用被装饰的函数
say_hello()
参数是类
- 添加额外的方法或属性:为类动态添加新的功能。
- 修改类的行为:例如修改初始化方法、添加日志记录等。
- 实现单例模式:通过装饰器确保类的实例化只发生一次。
- 权限校验:在类的方法调用前进行权限检查。
- 性能监控:为类的方法添加性能监控逻辑。
def class_decorator(cls):
original_init = cls.__init__ # 保存原始的初始化方法
def new_init(self, *args, **kwargs):
print("Additional logic before initialization.")
original_init(self, *args, **kwargs) # 调用原始的初始化方法
print("Additional logic after initialization.")
cls.__init__ = new_init # 替换初始化方法
return cls
# 使用装饰器
@class_decorator
class MyClass:
def __init__(self, value):
self.value = value
print(f"Initialized with value: {self.value}")
# 测试
obj = MyClass(10)
应用场景
日志记录
def log_decorator(func):
def wrapper(*args, **kwargs):
print(f"Calling function {func.__name__} with args: {args}, kwargs: {kwargs}")
result = func(*args, **kwargs)
print(f"Finished calling function {func.__name__}")
return result
return wrapper
性能测试
计算函数的执行时间。
import time
def timer_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"Function {func.__name__} took {end_time - start_time:.6f} seconds")
return result
return wrapper
权限校验
在函数执行前检查用户权限。
def auth_decorator(func):
def wrapper(*args, **kwargs):
if not check_permission():
raise PermissionError("You do not have permission to execute this function")
return func(*args, **kwargs)
return wrapper
缓存结果
使用装饰器缓存函数的计算结果,避免重复计算。
from functools import lru_cache
@lru_cache(maxsize=128) # 使用内置的缓存装饰器
def expensive_computation(x):
time.sleep(2) # 模拟耗时计算
return x * x
迭代器
Python 中一种用于遍历可迭代对象(如列表、元组、字典、集合、字符串等)的对象。迭代器实现了两个关键方法:
__iter__()
:返回迭代器对象本身。__next__()
:返回迭代器的下一个值。如果没有更多值可供返回,则抛出 StopIteration 异常。需要显式维护状态。
可以多次使用,但每次需要重新创建。
适合自定义迭代逻辑。
迭代器实现
使用内置的 iter() 函数
# 创建一个可迭代对象
my_list = [1, 2, 3, 4, 5]
# 将可迭代对象转换为迭代器
my_iterator = iter(my_list)
# 使用 next() 函数获取下一个值
print(next(my_iterator)) # 输出:1
print(next(my_iterator)) # 输出:2
print(next(my_iterator)) # 输出:3
自定义迭代器
通过实现 __iter__()
和 __next__()
方法创建自己的迭代器。以下是一个自定义迭代器的示例,它实现了分页逻辑,每次迭代返回一页数据。
class Paginator:
def __init__(self, data, page_size):
self.data = data
self.page_size = page_size
self.current_index = 0
def __iter__(self):
return self
def __next__(self):
start = self.current_index
end = start + self.page_size
if start >= len(self.data):
raise StopIteration
self.current_index = end
return self.data[start:end]
# 使用自定义迭代器
data = list(range(100)) # 示例数据
paginator = Paginator(data, page_size=10)
for page in paginator:
print(page)
组合迭代器
使用 itertools.chain()
将多个迭代器串联成一个长迭代器。
import itertools
iterator1 = iter([1, 2, 3])
iterator2 = iter([4, 5, 6])
iterator3 = iter([7, 8, 9])
combined_iterator = itertools.chain(iterator1, iterator2, iterator3)
for value in combined_iterator:
print(value)
迭代器链
通过将多个迭代器串联起来,可以实现复杂的处理流程,类似于数据管道。
""" 通过迭代器链实现数据的过滤、转换和最终处理。 """
import itertools
# 示例数据
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 过滤器:只保留偶数
filter_iterator = filter(lambda x: x % 2 == 0, data)
# 转换器:将每个值乘以 2
map_iterator = map(lambda x: x * 2, filter_iterator)
# 最终处理:打印结果
for value in map_iterator:
print(value)
无限迭代器
通过自定义迭代器或使用 itertools 模块,可以创建无限迭代器。无限迭代器在某些场景下非常有用,例如生成无限序列或模拟实时数据流。
itertools 模块提供了许多高级迭代器工具,可以用于实现复杂的迭代逻辑。
import itertools
class InfiniteFibonacci:
def __init__(self):
self.a, self.b = 0, 1
def __iter__(self):
return self
def __next__(self):
value = self.a
self.a, self.b = self.b, self.a + self.b
return value
# 使用无限迭代器
fibonacci = InfiniteFibonacci()
for _ in range(10): # 只取前 10 个值
print(next(fibonacci))
# 无限迭代器
fibonacci = InfiniteFibonacci()
# 使用 islice 切片前 10 个值
for value in itertools.islice(fibonacci, 10):
print(value)
迭代器的懒加载特性
迭代器的懒加载特性使其在处理大数据集/大文件时非常高效。通过按需生成值,迭代器可以节省大量内存。
def read_large_file(file_path):
""" 如何使用迭代器逐行读取大文件,而无需一次性加载整个文件到内存。 """
with open(file_path, 'r') as file:
for line in file:
yield line.strip()
# 使用迭代器逐行处理文件
for line in read_large_file('large_file.txt'):
print(line)
迭代器的并发处理
可以结合 concurrent.futures
或 asyncio
模块,对迭代器中的数据进行并发处理。
import asyncio
# 异步处理迭代器数据
async def process_data(data):
await asyncio.sleep(1) # 模拟异步处理
return data * 2
async def main():
data = [1, 2, 3, 4, 5]
tasks = [process_data(item) for item in data]
results = await asyncio.gather(*tasks)
print(results)
asyncio.run(main())
itertools.tee() 复制迭代器
itertools.tee 可以将一个迭代器复制为多个独立的迭代器,每个迭代器都可以独立地遍历数据。
import itertools
data = [1, 2, 3, 4, 5]
iterator = iter(data)
# 复制迭代器
iterator1, iterator2 = itertools.tee(iterator, 2)
print("Iterator 1:", list(iterator1))
print("Iterator 2:", list(iterator2))
###### 输出 ######
# Iterator 1: [1, 2, 3, 4, 5]
# Iterator 2: [1, 2, 3, 4, 5]
itertools.groupby:分组迭代器
itertools.groupby 可以根据某个键函数将迭代器中的相邻重复元素挑出来放在一起。
import itertools
data = [(1, 'a'), (1, 'b'), (2, 'c'), (2, 'd'), (3, 'e')]
# 按第一个元素分组
for key, group in itertools.groupby(data, key=lambda x: x[0]):
print(f"Key: {key}, Group: {list(group)}")
###### 输出 ######
# Key: 1, Group: [(1, 'a'), (1, 'b')]
# Key: 2, Group: [(2, 'c'), (2, 'd')]
# Key: 3, Group: [(3, 'e')]
itertools.product:笛卡尔积
itertools.product 可以生成多个迭代器的笛卡尔积,适用于组合多个数据集。
import itertools
iterator1 = [1, 2]
iterator2 = ['a', 'b']
iterator3 = [True, False]
# 生成笛卡尔积
for combination in itertools.product(iterator1, iterator2, iterator3):
print(combination)
###### 输出 ######
# (1, 'a', True)
# (1, 'a', False)
# (1, 'b', True)
# (1, 'b', False)
# (2, 'a', True)
# (2, 'a', False)
# (2, 'b', True)
# (2, 'b', False)
排列组合
itertools.combinations 组合
itertools.permutations 排列
import itertools
data = [1, 2, 3]
# 生成组合
print("Combinations (r=2):", list(itertools.combinations(data, r=2)))
# 生成排列
print("Permutations (r=2):", list(itertools.permutations(data, r=2)))
###### 输出 ######
# Combinations (r=2): [(1, 2), (1, 3), (2, 3)]
# Permutations (r=2): [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
itertools.starmap:对元组解包后应用函数
itertools.starmap 类似于 map,但它会对输入的元组解包后再应用函数。
import itertools
data = [(1, 2), (3, 4), (5, 6)]
# 使用 starmap 对元组解包后应用函数
result = itertools.starmap(lambda x, y: x + y, data)
for value in result:
print(value)
###### 输出 ######
# 3
# 7
# 11
itertools.filterfalse 和 itertools.takewhile
itertools.filterfalse 用于过滤不满足条件的元素,itertools.takewhile 用于取满足条件的连续元素。
import itertools
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# 使用 filterfalse 过滤偶数
filtered = itertools.filterfalse(lambda x: x % 2 == 0, data)
print("Filtered (odd numbers):", list(filtered))
# 使用 takewhile 取小于 5 的连续元素
taken = itertools.takewhile(lambda x: x < 5, data)
print("Taken (less than 5):", list(taken))
###### 输出 ######
# Filtered (odd numbers): [1, 3, 5, 7, 9]
# Taken (less than 5): [1, 2, 3, 4]
itertools.zip_longest:对齐多个迭代器
itertools.zip_longest 类似于内置的 zip,但它会用指定的填充值对齐多个迭代器。
import itertools
iterator1 = [1, 2, 3]
iterator2 = ['a', 'b']
iterator3 = [True, False, None]
# 使用 zip_longest 对齐多个迭代器
for combination in itertools.zip_longest(iterator1, iterator2, iterator3, fillvalue="N/A"):
print(combination)
###### 输出 ######
# (1, 'a', True)
# (2, 'b', False)
# (3, 'N/A', None)
生成器
用于生成一系列值,而不是一次性返回所有值。
特点
- 惰性求值:生成器不会一次性计算所有值,而是按需生成值,节省内存。
- 状态保存:每次调用生成器时,它会保存上次的状态,直到下一次调用。
- 可迭代:生成器是一个迭代器,可以用于 for 循环或其他迭代操作。
和函数区别
特性 | 普通函数 (Regular Function) | 生成器 (Generator) |
---|---|---|
返回值 | 使用 return 返回单个值 | 使用 yield 返回多个值 |
执行方式 | 一次性执行完成 | 按需生成值,每次调用暂停并保存状态 |
内存占用 | 一次性生成所有结果,占用更多内存 | 每次生成一个结果,节省内存 |
是否可迭代 | 不是迭代器 | 是迭代器,支持 for 循环和 next() |
应用场景 | 适用于计算单个结果 | 适用于生成一系列值,尤其是大数据集 |
返回值类型 | 返回值可以是任意类型 | 返回值是一个生成器对象 |
支持的语法 | 不支持 yield from | 支持 yield 和 yield from |
是否支持异步 | 不支持异步操作 | 支持异步生成器(async def 和 yield ) |
是否可重用 | 函数可以多次调用 | 每次调用生成器会创建一个新的生成器对象 |
高级应用
生成无限序列
生成器可以用于生成无限序列,因为它们不会一次性计算所有值。
def infinite_sequence():
num = 0
while True:
yield num
num += 1
# 使用生成器
seq = infinite_sequence()
for _ in range(10):
print(next(seq))
生成器表达式
生成器表达式类似于列表推导式,但使用圆括号 () 而不是方括号 [],生成器表达式不会一次性计算所有值。
squares = (x * x for x in range(10))
for square in squares:
print(square)
使用send()方法
生成器不仅可以返回值,还可以通过 send() 方法向生成器传递值。
def echo():
while True:
received = yield
print(f"Received: {received}")
gen = echo()
next(gen) # 启动生成器
gen.send("Hello")
gen.send("World")
close() 和 throw()
生成器提供了 close() 方法来提前关闭生成器,以及 throw() 方法来向生成器抛出异常
def my_generator():
try:
yield 1
yield 2
except ValueError:
print("ValueError caught")
finally:
print("Generator closed")
gen = my_generator()
print(next(gen)) # 输出 1
gen.throw(ValueError) # 向生成器抛出异常
gen.close() # 关闭生成器
生成器的委托
从 Python 3.3 开始,引入了 yield from
语法,允许一个生成器委托给另一个生成器。这种方式可以简化嵌套生成器的代码
def sub_generator():
yield "Sub 1"
yield "Sub 2"
def main_generator():
yield "Main 1"
yield from sub_generator() # 委托给子生成器
yield "Main 3"
for value in main_generator():
print(value)
异步生成器
从 Python 3.6 开始,引入了异步生成器,允许在生成器中使用 async 和 await。这种方式可以用于异步编程场景
import asyncio
async def async_generator():
for i in range(3):
await asyncio.sleep(1)
yield i
async def main():
async for value in async_generator():
print(value)
asyncio.run(main())
isxxx()类型方法
isalpha()
检查字符串是否只包含字母(不包括数字和其他字符)。
print('vanson'.isalpha()) # 输出: True
print('vanson123'.isalpha()) # 输出: False
isascii()
检查字符串中的所有字符是否都是 ASCII 字符。
print('vanson'.isascii()) # 输出: True
print('vansoné'.isascii()) # 输出: False
isidentifier()
检查字符串是否是一个有效的 Python 标识符(例如变量名)。
print('vanson'.isidentifier()) # 输出: True
print('123vanson'.isidentifier()) # 输出: False
print('vanson_123'.isidentifier()) # 输出: True
isprintable()
检查字符串中的所有字符是否都是可打印字符(不包括控制字符)。
print('vanson123'.isprintable()) # 输出: True
print('vanson\n'.isprintable()) # 输出: False
isalnum()
检查字符串是否只包含字母或数字。
print('vanson123'.isalnum()) # 输出: True
print('vanson123!'.isalnum()) # 输出: False
isdecimal()
检查字符串是否只包含十进制数字。
print('123'.isdecimal()) # 输出: True
print('123.3'.isdecimal()) # 输出: False
isdigit()
检查字符串是否只包含数字(包括 Unicode 数字)。
print('123'.isdigit()) # 输出: True
print('123.3'.isdigit()) # 输出: False
isnumeric()
检查字符串是否只包含数字(支持更多数字类型,如 Unicode 数字)。
print('123'.isnumeric()) # 输出: True
print('123.3'.isnumeric()) # 输出: False
islower()
检查字符串是否全部为小写字母。
print('vanson'.islower()) # 输出: True
print('Vanson'.islower()) # 输出: False
isupper()
检查字符串是否全部为大写字母。
print('VANSON'.isupper()) # 输出: True
print('Vanson'.isupper()) # 输出: False
istitle()
检查字符串是否为标题格式(每个单词的首字母大写,其余字母小写)。
print('Vanson'.istitle()) # 输出: True
print('vanson'.istitle()) # 输出: False
isspace()
检查字符串是否只包含空白字符。
print(' '.isspace()) # 输出: True
print(' Vanson'.isspace()) # 输出: False
startswith()
检查字符串是否以指定的前缀开头。
print('Vanson123'.startswith('Va')) # 输出: True
print('Vanson123'.startswith('123')) # 输出: False
endswith()
检查字符串是否以指定的后缀结尾。
print('Vanson123'.endswith('23')) # 输出: True
print('Vanson123'.endswith('Va')) # 输出: False
contains()
用途:检查字符串是否包含指定的子字符串。
print('Vanson123'.__contains__('son')) # 输出: True
print('Vanson123'.__contains__('xyz')) # 输出: False
Concatenation连接
指将两个或多个序列(如字符串、列表、元组等)连接在一起,形成一个新的序列。连接操作通常使用 + 运算符来完成。
字符串连接
字符串连接是将两个或多个字符串连接成一个字符串。字符串连接时,+ 运算符会将字符串按顺序拼接在一起。
# 字符串连接
str1 = "Hello"
str2 = "World"
result = str1 + " " + str2 + str(123)
print(result) # 输出: Hello World
- 字符串连接时,必须确保所有操作数都是字符串类型。如果尝试将字符串与非字符串类型(如数字)直接连接,会抛出 TypeError。
- 可以使用 + 运算符多次连接多个字符串。
列表连接
列表连接是将两个或多个列表连接成一个新的列表。列表连接时,+ 运算符会将列表中的元素按顺序拼接在一起。
# 列表连接
list1 = [1, 2, 3]
list2 = [4, 5, 6]
result = list1 + list2 + list("789")
print(result) # 输出: [1, 2, 3, 4, 5, 6]
- 列表连接时,必须确保所有操作数都是列表类型。如果尝试将列表与非列表类型(如字符串或数字)直接连接,会抛出 TypeError。
- 可以使用 + 运算符多次连接多个列表。
元组连接
元组连接是将两个或多个元组连接成一个新的元组。元组连接时,+ 运算符会将元组中的元素按顺序拼接在一起。
# 元组连接
tuple1 = (1, 2, 3)
tuple2 = (4, 5, 6)
result = tuple1 + tuple2
print(result) # 输出: (1, 2, 3, 4, 5, 6)
- 元组连接时,必须确保所有操作数都是元组类型。如果尝试将元组与非元组类型(如字符串或列表)直接连接,会抛出 TypeError。
- 可以使用 + 运算符多次连接多个元组。
其他类型
Python 还支持其他序列类型的连接,如 bytes 和 bytearray。这些类型的连接方式与字符串类似,使用 + 运算符。
# bytes 连接
bytes1 = b"Hello"
bytes2 = b"World"
result = bytes1 + bytes2
print(result) # 输出: b'HelloWorld'
# bytearray 连接
bytearray1 = bytearray(b"Hello")
bytearray2 = bytearray(b"World")
result = bytearray1 + bytearray2
print(result) # 输出: bytearray(b'HelloWorld')
zip()函数
用于将多个可迭代对象(如列表、元组、字符串等)中的元素按顺序配对,生成一个元组的迭代器。
生成的迭代器的长度等于最短的输入可迭代对象的长度。
# 示例 1:配对两个列表
list1 = ['a', 'b', 'c']
list2 = [1, 2, 3]
result = list(zip(list1, list2))
print(result) # 输出: [('a', 1), ('b', 2), ('c', 3)]
# 示例 2:配对两个元组
tuple1 = ('a', 'b', 'c')
tuple2 = (1, 2, 3)
result = list(zip(tuple1, tuple2))
print(result) # 输出: [('a', 1), ('b', 2), ('c', 3)]
# 不同长度的可迭代对象
list1 = ['a', 'b', 'c']
list2 = [1, 2]
result = list(zip(list1, list2))
print(result) # 输出: [('a', 1), ('b', 2)]
# 多个可迭代对象
list1 = ['a', 'b', 'c']
list2 = [1, 2, 3]
list3 = [True, False, True]
result = list(zip(list1, list2, list3))
print(result) # 输出: [('a', 1, True), ('b', 2, False), ('c', 3, True)]
# 字符串
str1 = 'abc'
str2 = '123'
result = list(zip(str1, str2))
print(result) # 输出: [('a', '1'), ('b', '2'), ('c', '3')]
应用:
# 并行迭代:在循环中同时遍历多个可迭代对象。
for char, num in zip('abc', [1, 2, 3]):
print(char, num)
# 输出:
# a 1
# b 2
# c 3
# 创建字典:将两个列表配对后创建字典。
keys = ['a', 'b', 'c']
values = [1, 2, 3]
result = dict(zip(keys, values))
print(result) # 输出: {'a': 1, 'b': 2, 'c': 3}
# 解包元组:将元组列表解包为多个列表。
pairs = [('a', 1), ('b', 2), ('c', 3)]
letters, numbers = zip(*pairs)
print(letters) # 输出: ('a', 'b', 'c')
print(numbers) # 输出: (1, 2, 3)
传参机制
Python 的参数传递机制实际上是一种“按对象传递”。
不可变对象
不可变对象(如字符串、数字、元组)在函数内部无法被修改。 这是因为不可变对象的值一旦创建就不能改变。 因此,即使你在函数内部“修改”了参数,实际上也只是创建了一个新的对象,并将参数引用指向了这个新对象。
def modify_string(s):
s = s + " world"
print("Inside function:", s)
original_string = "hello"
modify_string(original_string)
print("Outside function:", original_string)
可变对象
可变对象(如列表、字典、集合)在函数内部可以被修改。因为这些对象的值可以改变,所以函数内部对参数的修改会反映到原始对象上。
def modify_list(lst):
lst.append(4)
print("Inside function:", lst)
original_list = [1, 2, 3]
modify_list(original_list)
print("Outside function:", original_list)
常见问题
修改不可变数据带来的问题
Python代码中要修改不可变数据会出现什么问题?抛出什么异常?
Python数据类型特性,特别是不可变数据类型(如int、float、str、tuple、frozenset等)的机制,以及异常类型判断能力。
# 演示字符串的不可变性
print("### 字符串的不可变性 ###")
s = "hello"
print(f"原始字符串: {s}")
print(f"原始字符串的内存地址: {id(s)}")
# 尝试修改字符串的某个字符
try:
s[0] = "H"
except TypeError as e:
print(f"尝试修改字符串时抛出的异常: {e}")
# 通过创建新字符串来“修改”字符串
s = s.replace("h", "H")
print(f"修改后的字符串: {s}")
print(f"修改后字符串的内存地址: {id(s)}")
# 演示元组的不可变性
print("\n### 元组的不可变性 ###")
t = (1, 2, 3)
print(f"原始元组: {t}")
print(f"原始元组的内存地址: {id(t)}")
# 尝试修改元组的某个元素
try:
t[0] = 10
except TypeError as e:
print(f"尝试修改元组时抛出的异常: {e}")
# 通过创建新元组来“修改”元组
t = (10,) + t[1:]
print(f"修改后的元组: {t}")
print(f"修改后元组的内存地址: {id(t)}")
# 演示不可变类型的应用场景:作为字典的键
print("\n### 不可变类型作为字典的键 ###")
# 使用元组作为字典的键
tuple_key = (1, 2, 3)
my_dict = {tuple_key: "value"}
print(f"使用元组作为字典的键: {my_dict}")
# 尝试使用可变类型(如列表)作为字典的键
try:
list_key = [1, 2, 3]
my_dict[list_key] = "value"
except TypeError as e:
print(f"尝试使用列表作为字典的键时抛出的异常: {e}")
结果:
### 字符串的不可变性 ###
原始字符串: hello
原始字符串的内存地址: 140000000000000
尝试修改字符串时抛出的异常: 'str' object does not support item assignment
修改后的字符串: Hello
修改后字符串的内存地址: 140000000000001
### 元组的不可变性 ###
原始元组: (1, 2, 3)
原始元组的内存地址: 140000000000002
尝试修改元组时抛出的异常: 'tuple' object does not support item assignment
修改后的元组: (10, 2, 3)
修改后元组的内存地址: 140000000000003
### 不可变类型作为字典的键 ###
使用元组作为字典的键: {(1, 2, 3): 'value'}
尝试使用列表作为字典的键时抛出的异常: unhashable type: 'list'
Python设计不可变类型的原因是其具有线程安全性、可哈希性,且能减少内存开销。
通过创建新对象来“修改”不可变对象,如字符串拼接、切片操作、replace()方法等。
计算斐波那契数列第n项
可以使用递归或循环实现。
def fib(n):
if n <= 2:
return 1
return fib(n - 1) + fib(n - 2)
字符串逆序
可以使用切片、reversed函数或手写循环。
s = "hello world"
print(s[::-1]) # 切片方式
print(''.join(reversed(s))) # 使用reversed函数
result = ''
for i in range(len(s) - 1, -1, -1):
result += s[i] # 手写循环
找出列表中重复的元素
使用集合记录已见过的元素,遍历列表时检查是否重复。
def find_dupes(lst):
seen = set()
dupes = []
for x in lst:
if x in seen:
dupes.append(x)
seen.add(x)
return dupes
# dict.fromkeys() 方法来实现去重,同时保持元素的相对顺序
def remove_duplicates(lst):
return list(dict.fromkeys(lst))
# 示例
original_list = [1, 2, 3, 2, 1, 4, 5, 4, 6]
print("原始列表:", original_list)
print("去重后列表:", remove_duplicates(original_list))
def dedupe(lst):
seen = set()
return [x for x in lst if not (x in seen or seen.add(x))]
判断回文字符串
去掉标点和空格,全部变小写,然后比较字符串与其逆序是否相同。
def is_palindrome(s):
s = ''.join(c.lower() for c in s if c.isalnum())
return s == s[::-1]
合并两个有序列表
使用双指针法,依次比较两个列表的元素,按顺序添加到结果列表。
def merge_sorted_lists(a, b):
result = []
i = j = 0
while i < len(a) and j < len(b):
if a[i] <= b[j]:
result.append(a[i])
i += 1
else:
result.append(b[j])
j += 1
result.extend(a[i:])
result.extend(b[j:])
return result
判断两个字符串是否为变位词
将两个字符串排序后比较是否相同。
def is_anagram(s1, s2):
return sorted(s1.lower()) == sorted(s2.lower())
快速排序实现
选择一个基准值,将数组分为小于基准值和大于基准值的两部分,递归排序。
def quicksort(arr):
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 quicksort(left) + middle + quicksort(right)
求两个列表的交集
将两个列表转换为集合,求交集后转换回列表。
def intersection(lst1, lst2):
return list(set(lst1) & set(lst2))
sort() 和 sorted()
sort()
sort() 是列表(list)的一个方法,用于对列表中的元素进行排序。
直接修改原列表,不会返回新的列表。只适用于列表:不能对其他可迭代对象(如元组、字典)进行排序。
my_list = [3, 1, 4, 1, 5, 9, 2, 6]
my_list.sort()
print(my_list) # 输出: [1, 1, 2, 3, 4, 5, 6, 9]
sorted()
sorted() 是一个内置函数,用于对任何可迭代对象进行排序,并返回一个新的列表。
- 返回新列表:不会修改原可迭代对象,而是返回一个新的排序后的列表。
- 适用于任何可迭代对象:可以对列表、元组、字典等进行排序。
- 对字典排序:默认按字典的键(key)进行排序,返回一个排序后的键列表。
my_list = [3, 1, 4, 1, 5, 9, 2, 6]
sorted_list = sorted(my_list)
print(sorted_list) # 输出: [1, 1, 2, 3, 4, 5, 6, 9]
my_tuple = (3, 1, 4, 1, 5, 9, 2, 6)
sorted_tuple = sorted(my_tuple)
print(sorted_tuple) # 输出: [1, 1, 2, 3, 4, 5, 6, 9]
my_dict = {'a': 3, 'b': 1, 'c': 4}
sorted_keys = sorted(my_dict)
print(sorted_keys) # 输出: ['a', 'b', 'c']
# 对字典排序
my_dict = {'a': 3, 'b': 1, 'c': 4}
sorted_keys = sorted(my_dict)
print(sorted_keys) # 输出: ['a', 'b', 'c']
# 按值排序
sorted_by_value = sorted(my_dict.items(), key=lambda item: item[1])
print(sorted_by_value) # 输出: [('b', 1), ('a', 3), ('c', 4)]
Cpython下的数字缓存
a, b, c, d = 1, 1, 1000, 1000
print(a is b, c is d) # 输出:True True
def foo():
e = 1000
f = 1000
print(e is f, e is d) # 输出:True False
g = 1
print(g is a) # 输出:True
foo()
小整数缓存(-5 到 256):
- a is b 为 True:1 在小整数缓存范围内,所有引用共享同一对象。
- g is a 为 True:函数内的 g=1 复用全局缓存对象。
同一代码块的大整数优化:
- 全局作用域的 c is d 为 True:同一行赋值的 1000 会被合并为同一对象。
- 函数内的 e is f 为 True:函数代码块中的多个 1000 被视为同一常量对象。
跨作用域的大整数隔离:
- e is d 为 False:函数内的 e=1000 和全局的 d=1000 属于不同作用域的独立对象。
如果:
c = 1000
d = 1000
print(c is d) # 输出:False(分行的 `1000` 创建不同对象)
说明:PyPy 的输出全部为 True:PyPy 的 JIT 优化更激进,所有相同值的整数(无论大小)都可能被复用。
正则表达式
match方法和search方法有什么区别?
默认
- match():仅在字符串开头匹配模式,若开头不匹配则返回None。
- search():扫描整个字符串,返回第一个匹配的位置,无论其是否在开头。
import re
s = "abc123def"
pattern = r'\d+' # 匹配连续数字
# 使用match() - 开头无数字,返回None
match_result = re.match(pattern, s)
print(match_result) # 输出: None
# 使用search() - 找到中间的数字"123"
search_result = re.search(pattern, s)
print(search_result.group()) # 输出: '123'
模式包含^时的行为
当正则表达式以^(匹配开头)锚定时,search()的行为与match()一致,均需从开头匹配。 关键点:^强制匹配字符串开头,无论使用哪种方法。
s = "123abc"
pattern = r'^\d+' # 匹配开头的连续数字
# match()成功匹配开头的"123"
print(re.match(pattern, s).group()) # 输出: '123'
# search()同样成功,因模式强制从开头匹配
print(re.search(pattern, s).group()) # 输出: '123'
# 若字符串开头不匹配,两者均返回None
s = "abc123"
print(re.match(pattern, s)) # None
print(re.search(pattern, s)) # None
跨多行匹配(re.MULTILINE标志)
在多行模式下,^会匹配每行的开头(而非整个字符串开头)。 match()仍仅匹配整个字符串的开头,而search()可匹配任意行的开头。
s = "abc\n123\n456"
pattern = r'^\d+'
multi_pattern = re.compile(pattern, re.MULTILINE)
# match()仅匹配整个字符串的开头(非数字),返回None
print(multi_pattern.match(s)) # None
# search()匹配第一行后的"123"
print(multi_pattern.search(s).group()) # 输出: '123'
高级用法
指定起始和结束位置 两种方法均支持pos(起始位置)和endpos(结束位置)参数。 match(pos=n):从位置n开始匹配,行为类似search()但仅在n处尝试。
s = "abc123def"
pattern = r'\d+'
# 从位置3开始匹配(字符'1')
match_result = re.match(pattern, s, pos=3)
print(match_result.group()) # 输出: '123'
# search()无需指定pos,直接全局查找
search_result = re.search(pattern, s)
print(search_result.group()) # 输出: '123'
性能优化 预编译正则表达式:频繁使用同一模式时,先用re.compile()创建对象以减少重复解析开销。
pattern = re.compile(r'\d+')
# 复用预编译对象
print(pattern.match("abc123")) # None
print(pattern.search("abc123").group()) # '123'
闭包产生的问题
def multiply():
return [lambda x: i * x for i in range(4)]
print([m(100) for m in multiply()])
在 Python 中,闭包捕获的是变量的引用,而不是变量的值。因此,当 lambda 函数被调用时,它们会使用 i 的最终值(即 3)。
- multiply() 返回一个包含 4 个 lambda 函数的列表。
- 每个 lambda 函数捕获了变量 i,而 i 的最终值是 3。
- 因此,调用这些 lambda 函数时,它们都会返回 3 * 100,即 300。
解决:
# 在lambda定义时,将i作为默认参数固定其当前值:
def multiply():
return [lambda x, i=i: i * x for i in range(4)]
print([m(100) for m in multiply()]) # 输出:[0, 100, 200, 300]
# 使用 yield
def multiply():
for i in range(4):
yield lambda x: x * i
print([m(100) for m in multiply()])
# 偏函数可以将 i 的值固定下来,从而避免闭包问题。partial(mul, i) 会创建一个新的函数,将 i 的值固定下来。
from functools import partial
from operator import mul
def multiply():
return [partial(mul, i) for i in range(4)]
print([m(100) for m in multiply()])
Python不支持函数重载
重载:即在同一个作用域内定义多个同名函数,通过参数类型或数量区分
函数重载的本质与静态语言需求
函数重载的核心目的是:
- 类型安全:根据参数类型选择对应逻辑。
- 接口统一:同名函数处理不同参数场景,提升代码可读性。
- 编译时多态:编译器在编译阶段确定调用哪个函数。
Python不支持三大原因:
动态类型与运行时绑定
- 动态类型特性:Python函数的参数无类型声明,无法在定义时区分参数类型。
- 运行时决议:函数调用在运行时动态解析,无法在编译时确定类型,导致无法静态分派。
灵活的参数机制
# 默认参数:处理参数缺失场景。
def connect(url, timeout=10):
pass
connect("http://a.com") # timeout=10
connect("http://b.com", 20) # timeout=20
# 可变参数(args与kwargs)
def calculate(*args):
if len(args) == 1:
return args * 2
elif len(args) == 2:
return args + args
calculate(5) # 10
calculate(3, 4) # 7
# 类型检查与动态逻辑:在函数内部根据参数类型/数量执行不同逻辑。
def handle_input(data):
if isinstance(data, int):
return data * 2
elif isinstance(data, str):
return data.upper()
设计:简洁性与显式性
- 显式优于隐式”:Python鼓励通过参数默认值、条件判断等显式方式处理不同场景,而非隐式的重载规则。
- 避免歧义:动态类型下,重载规则可能复杂化函数调用逻辑(如多参数类型的组合冲突)。
用Python代码实现Python内置函数max
- 接受一个可迭代对象(如列表、元组等)作为输入。
- 支持可选的 key 参数,用于指定一个函数,该函数用于从每个元素中提取比较的值。
- 支持可选的 default 参数,用于在可迭代对象为空时返回默认值。
def my_max(iterable, key=None, default=None):
# 检查可迭代对象是否为空
iterator = iter(iterable)
try:
# 获取第一个元素
max_value = next(iterator)
except StopIteration: # 当迭代器中没有更多元素时,next() 函数会抛出 StopIteration 异常。这是 Python 中迭代器协议的一部分,用于表示迭代器已经到达末尾。
# 如果可迭代对象为空,返回默认值
if default is not None:
return default
else:
raise ValueError("my_max() arg is an empty sequence") from None
# 如果提供了 key 函数,则使用 key 函数提取比较值
if key is None:
for value in iterator:
if value > max_value:
max_value = value
else:
max_key = key(max_value)
for value in iterator:
current_key = key(value)
if current_key > max_key:
max_value = value
max_key = current_key
return max_value
# 测试代码
if __name__ == "__main__":
# 测试基本功能
print(my_max([1, 3, 2, 5, 4])) # 输出 5
print(my_max((1, 3, 2, 5, 4))) # 输出 5
# 测试 key 参数
print(my_max(["apple", "banana", "cherry"], key=len)) # 输出 "banana"
# 测试默认值
print(my_max([], default="Empty")) # 输出 "Empty"
# 测试空序列且没有默认值
try:
print(my_max([]))
except ValueError as e:
print(e) # 输出 "my_max() arg is an empty sequence"
框架
架构和性能
参考:
On this page
- Python基础
- 基础
- GIL 全局解释器锁
- 影响
- 解决
- 深拷贝和浅拷贝
- 浅拷贝
- 深拷贝
- 高级
- 装饰器
- 实现
- 参数是函数
- 参数是类
- 应用场景
- 日志记录
- 性能测试
- 权限校验
- 缓存结果
- 迭代器
- 迭代器实现
- 使用内置的 iter() 函数
- 自定义迭代器
- 组合迭代器
- 迭代器链
- 无限迭代器
- 迭代器的懒加载特性
- 迭代器的并发处理
- itertools.tee() 复制迭代器
- itertools.groupby:分组迭代器
- itertools.product:笛卡尔积
- 排列组合
- itertools.starmap:对元组解包后应用函数
- itertools.filterfalse 和 itertools.takewhile
- itertools.zip_longest:对齐多个迭代器
- 生成器
- 特点
- 和函数区别
- 高级应用
- 生成无限序列
- 生成器表达式
- 使用send()方法
- close() 和 throw()
- 生成器的委托
- 异步生成器
- isxxx()类型方法
- isalpha()
- isascii()
- isidentifier()
- isprintable()
- isalnum()
- isdecimal()
- isdigit()
- isnumeric()
- islower()
- isupper()
- istitle()
- isspace()
- startswith()
- endswith()
- contains()
- Concatenation连接
- 字符串连接
- 列表连接
- 元组连接
- 其他类型
- zip()函数
- 传参机制
- 不可变对象
- 可变对象
- 常见问题
- 修改不可变数据带来的问题
- 计算斐波那契数列第n项
- 字符串逆序
- 找出列表中重复的元素
- 判断回文字符串
- 合并两个有序列表
- 判断两个字符串是否为变位词
- 快速排序实现
- 求两个列表的交集
- sort() 和 sorted()
- sort()
- sorted()
- Cpython下的数字缓存
- 正则表达式
- 默认
- 模式包含^时的行为
- 跨多行匹配(re.MULTILINE标志)
- 高级用法
- 闭包产生的问题
- Python不支持函数重载
- 动态类型与运行时绑定
- 灵活的参数机制
- 设计:简洁性与显式性
- 用Python代码实现Python内置函数max
- 框架
- 架构和性能