Vanson's Eternal Blog

Python夯实基础 - 持续更新

Python basic.png
Published on
/45 mins read/---

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.futuresasyncio 模块,对迭代器中的数据进行并发处理。

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支持 yieldyield from
是否支持异步不支持异步操作支持异步生成器(async defyield
是否可重用函数可以多次调用每次调用生成器会创建一个新的生成器对象

高级应用

生成无限序列

生成器可以用于生成无限序列,因为它们不会一次性计算所有值。

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"
 
 

框架

架构和性能

参考:

https://mp.weixin.qq.com/s/KCzRbYIc2cCM2P1bG4hVaw