目录
使用 __getattr__ 和 __setattr__ 时的无限递归
__all__ 变量对 from module import * 的影响
86. 自定义类的 __hash__ 和 __eq__ 一致性问题(进阶)
87. 异步编程中的 asyncio.run() 多次调用问题
简介
Python 作为一门广受欢迎且功能强大的编程语言,在实际使用过程中却常常让开发者们遭遇各种意想不到的 “陷阱”。本系列文章精心总结了 100 个 Python 中常见的易错知识点,内容覆盖从基础语法到高级特性,从变量数据类型到并发编程等多个方面。无论是初入编程世界的新手,还是经验丰富的开发者,都能从这些深入剖析与详细讲解中,清晰分辨易错点,掌握正确的编程方法,从而避开常见错误,提升 Python 编程技能与代码质量。
一下是Python 中100个常用易错知识点的总结:
1. 类方法命名中的下划线
- 单下划线开头 (
_name
):- 按照约定,以单下划线开头的属性或方法是 “protected”(受保护的)。这意味着它们不应该在类外部直接访问,虽然 Python 并没有真正的访问限制。例如:
class MyClass:
def __init__(self):
self._protected_var = 42
def _protected_method(self):
return "This is a protected method"
obj = MyClass()
# 虽然可以访问,但不建议这么做
print(obj._protected_var)
print(obj._protected_method())
- 双下划线开头 (
__name
):- 以双下划线开头的属性或方法会触发名称改写(name mangling)机制。Python 会在这些属性或方法名前加上
_类名
,以避免在子类中命名冲突。例如:
- 以双下划线开头的属性或方法会触发名称改写(name mangling)机制。Python 会在这些属性或方法名前加上
class MyClass:
def __init__(self):
self.__private_var = 42
def __private_method(self):
return "This is a private method"
obj = MyClass()
# 直接访问会报错
# print(obj.__private_var)
# 实际可以通过改写后的名称访问,但强烈不推荐
print(obj._MyClass__private_var)
print(obj._MyClass__private_method())
2. 函数形参中的 *
和 **
- 仅限关键字参数 星号 ( *, arg )
在函数定义中, 把形参标记为 仅限关键字,表明必须以关键字参数形式传递该形参,应在参数列表中第一个 仅限关键字 形参前添加 *
。例如:
# 这里arg必须是关键字参数
def kwd_only_arg(*, arg):
print(arg)
特定形参可以标记为 仅限位置。仅限位置 时,形参的顺序很重要,且这些形参不能用关键字传递。仅限位置形参应放在
/
(正斜杠)前。/
用于在逻辑上分割仅限位置形参与其它形参。如果函数定义中没有/
,则表示没有仅限位置形参。
/
后可以是 位置或关键字 或 仅限关键字 形参。更多信息参考: 4. 更多控制流工具 — Python 3.12.9 文档
- 单星号 (
*args
):- 在函数定义中,
*args
用于收集所有位置参数(positional arguments)到一个元组(tuple)中。例如:
- 在函数定义中,
def sum_numbers(*args):
total = 0
for num in args:
total += num
return total
result = sum_numbers(1, 2, 3, 4)
print(result) # 输出: 10
- 双星号 (
**kwargs
):- 在函数定义中,
**kwargs
用于收集所有关键字参数(keyword arguments)到一个字典(dictionary)中。例如:
- 在函数定义中,
def print_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Alice", age=30, city="New York")
3. 函数实参中的 *
- 在函数调用时使用单星号 (
*
):- 可以将一个可迭代对象(如列表、元组等)解包为位置参数。例如:
def multiply(a, b):
return a * b
nums = [3, 4]
result = multiply(*nums)
print(result) # 输出: 12
4. 变量作用域
- 局部变量和全局变量:
- 在函数内部定义的变量默认是局部变量,作用域仅限于函数内部。如果要在函数内部修改全局变量,需要使用
global
关键字声明。例如:
- 在函数内部定义的变量默认是局部变量,作用域仅限于函数内部。如果要在函数内部修改全局变量,需要使用
global_var = 10
def modify_global():
global global_var
global_var = 20
modify_global()
print(global_var) # 输出: 20
5. 浅拷贝和深拷贝
- 浅拷贝 (
copy.copy()
):- 创建一个新对象,该对象的内容是原对象元素的引用。如果原对象包含可变对象(如列表、字典),修改原对象中的可变对象会影响浅拷贝对象。例如:
import copy
original_list = [[1, 2], [3, 4]]
shallow_copy = copy.copy(original_list)
original_list[0][0] = 99
print(shallow_copy) # 输出: [[99, 2], [3, 4]]
- 深拷贝 (
copy.deepcopy()
):- 创建一个新对象,递归地复制原对象及其所有子对象。修改原对象不会影响深拷贝对象。例如:
import copy
original_list = [[1, 2], [3, 4]]
deep_copy = copy.deepcopy(original_list)
original_list[0][0] = 99
print(deep_copy) # 输出: [[1, 2], [3, 4]]
6. 默认参数的陷阱
- 默认参数在函数定义时被求值,而不是在函数调用时。如果默认参数是可变对象(如列表、字典),可能会导致意外行为。例如:
def append_to_list(value, my_list=[]):
my_list.append(value)
return my_list
result1 = append_to_list(1)
result2 = append_to_list(2)
print(result1) # 输出: [1, 2]
print(result2) # 输出: [1, 2]
- 正确的做法是将默认参数设为
None
,并在函数内部检查并初始化可变对象:
def append_to_list(value, my_list=None):
if my_list is None:
my_list = []
my_list.append(value)
return my_list
result1 = append_to_list(1)
result2 = append_to_list(2)
print(result1) # 输出: [1]
print(result2) # 输出: [2]
7. 迭代器和生成器相关
迭代器使用后耗尽
迭代器只能遍历一次,遍历完后再次使用会没有结果。例如:
numbers = [1, 2, 3]
iterator = iter(numbers)
# 第一次遍历
for num in iterator:
print(num)
# 第二次遍历,没有输出
for num in iterator:
print(num)
生成器表达式和列表推导式混淆
生成器表达式使用圆括号 ()
,列表推导式使用方括号 []
。生成器表达式是惰性求值的,而列表推导式会立即创建一个完整的列表。例如:
# 生成器表达式
gen_expr = (i for i in range(3))
print(type(gen_expr)) # <class 'generator'>
# 列表推导式
list_comp = [i for i in range(3)]
print(type(list_comp)) # <class 'list'>
8. 异常处理相关
捕获异常范围过大
如果使用 except
语句不指定具体的异常类型,会捕获所有异常,这可能会掩盖代码中的错误。例如:
try:
result = 1 / 0
except:
print("An error occurred")
更好的做法是指定具体的异常类型:
try:
result = 1 / 0
except ZeroDivisionError:
print("Division by zero!")
异常处理中的 finally
子句
finally
子句中的代码无论是否发生异常都会执行,即使在 try
或 except
子句中有 return
语句。例如:
def test():
try:
return 1
finally:
print("This will be printed before returning")
result = test()
print(result)
9. 多线程和多进程相关
全局解释器锁(GIL)
在 CPython 中,全局解释器锁(GIL)使得同一时刻只有一个线程可以执行 Python 字节码。因此,对于 CPU 密集型任务,使用多线程并不能提高性能,反而可能因为线程切换带来额外开销。对于 CPU 密集型任务,建议使用多进程。例如:
import threading
import time
# CPU 密集型任务
def cpu_intensive_task():
num = 0
for i in range(10**7):
num += i
# 使用多线程
threads = []
for _ in range(2):
t = threading.Thread(target=cpu_intensive_task)
threads.append(t)
t.start()
for t in threads:
t.join()
误解多线程性能提升
Python 的全局解释器锁(GIL)使得同一时刻只有一个线程可以执行 Python 字节码。对于 CPU 密集型任务,使用多线程并不能提高性能,反而可能因为线程切换带来额外开销。只有对于 I/O 密集型任务,多线程才能发挥作用。
import threading
# CPU 密集型任务
def cpu_intensive():
result = 0
for i in range(10**7):
result += i
threads = []
for _ in range(2):
t = threading.Thread(target=cpu_intensive)
threads.append(t)
t.start()
for t in threads:
t.join()
# 此多线程执行 CPU 密集型任务不会提升性能
多进程中的资源共享问题
在多进程中,每个进程都有自己独立的内存空间,不能像多线程那样直接共享全局变量。如果需要在进程间共享数据,需要使用 multiprocessing
模块提供的共享内存对象。例如:
import multiprocessing
def increment(counter):
for _ in range(1000):
with counter.get_lock():
counter.value += 1
if __name__ == '__main__':
counter = multiprocessing.Value('i', 0)
processes = []
for _ in range(2):
p = multiprocessing.Process(target=increment, args=(counter,))
processes.append(p)
p.start()
for p in processes:
p.join()
print(counter.value)
10. 字符串编码问题
编码和解码错误
在 Python 中,字符串有不同的编码方式(如 UTF - 8、GBK 等)。如果在编码和解码过程中使用了不匹配的编码方式,会导致 UnicodeEncodeError
或 UnicodeDecodeError
。例如:
text = "你好"
# 错误的编码方式
try:
encoded = text.encode('ascii')
except UnicodeEncodeError as e:
print(f"Encoding error: {e}")
正确的做法是使用合适的编码方式, 建议统一使用 utf-8编码:
text = "你好"
encoded = text.encode('utf-8')
decoded = encoded.decode('utf-8')
print(decoded)
11. 模块导入相关
循环导入问题
当两个或多个模块相互导入时,会出现循环导入问题,可能导致 AttributeError
等错误。例如:
module_a.py
:
from module_b import func_b
def func_a():
print("Function A")
func_b()
module_b.py
:
from module_a import func_a
def func_b():
print("Function B")
func_a()
可以通过重构代码,将导入语句移到函数内部或者重新组织模块结构来解决循环导入问题。
12. 装饰器相关
装饰器丢失元数据
使用装饰器时,如果不使用 functools.wraps
来保留原函数的元数据(如函数名、文档字符串等),会导致元数据丢失。例如:
def my_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def my_function():
"""This is a docstring."""
pass
print(my_function.__name__) # 输出: wrapper
使用 functools.wraps
可以解决这个问题:
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def my_function():
"""This is a docstring."""
pass
print(my_function.__name__) # 输出: my_function
丢失原函数元数据
当使用装饰器包装函数时,如果不使用 functools.wraps
,原函数的元数据(如函数名、文档字符串等)会丢失,这会影响代码的可读性和调试。
import functools
# 错误示例:未使用 wraps
def simple_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@simple_decorator
def original_function():
"""这是原函数的文档字符串"""
pass
print(original_function.__name__) # 输出 'wrapper',而不是 'original_function'
# 正确示例:使用 wraps
def better_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@better_decorator
def another_function():
"""这是另一个原函数的文档字符串"""
pass
print(another_function.__name__) # 输出 'another_function'
13. 列表操作相关
切片赋值的理解误区
在进行列表切片赋值时,如果赋值的对象和切片长度不匹配,会有不同的效果。例如:
my_list = [1, 2, 3, 4, 5]
# 切片长度和赋值对象长度相同
my_list[1:3] = [6, 7]
print(my_list) # 输出: [1, 6, 7, 4, 5]
# 切片长度和赋值对象长度不同
my_list[1:3] = [8, 9, 10]
print(my_list) # 输出: [1, 8, 9, 10, 4, 5]
列表的 sort()
方法和 sorted()
函数混淆
sort()
方法是列表对象的方法,它会直接修改原列表,返回值为 None
;而 sorted()
函数会返回一个新的已排序列表,原列表保持不变。例如:
my_list = [3, 1, 2]
# 使用 sort() 方法
result = my_list.sort()
print(result) # 输出: None
print(my_list) # 输出: [1, 2, 3]
my_list = [3, 1, 2]
# 使用 sorted() 函数
new_list = sorted(my_list)
print(new_list) # 输出: [1, 2, 3]
print(my_list) # 输出: [3, 1, 2]
14. 字典操作相关
字典键的不可变性
字典的键必须是不可变对象(如整数、字符串、元组等),如果使用可变对象(如列表)作为键,会引发 TypeError
。例如:
try:
my_dict = {[1, 2]: 'value'}
except TypeError as e:
print(f"Error: {e}")
字典的 get()
方法和直接访问键的区别
直接访问字典中不存在的键会引发 KeyError
,而使用 get()
方法可以指定默认值,避免这种错误。例如:
my_dict = {'a': 1}
# 直接访问不存在的键
try:
value = my_dict['b']
except KeyError as e:
print(f"KeyError: {e}")
# 使用 get() 方法
value = my_dict.get('b', 0)
print(value) # 输出: 0
15. 时间处理相关
时区问题
在处理时间时,如果不考虑时区,可能会导致时间计算错误。Python 的 datetime
模块默认创建的是无时区对象,使用 pytz
等第三方库可以处理时区问题。例如:
import datetime
import pytz
# 创建无时区时间
naive_time = datetime.datetime.now()
print(naive_time)
# 创建有时区时间
utc = pytz.UTC
aware_time = utc.localize(naive_time)
print(aware_time)
时间格式化错误
在使用 strftime()
方法进行时间格式化时,如果格式字符串使用错误,会导致输出不符合预期。例如:
import datetime
now = datetime.datetime.now()
# 错误的格式字符串
try:
formatted = now.strftime('%Y-%m-%d %H:%M:%S %Z') # %Z 在某些情况下可能无法正确显示时区名称
print(formatted)
except ValueError as e:
print(f"Formatting error: {e}")
16. 类和继承相关
多重继承的菱形问题
在多重继承中,如果出现菱形继承结构(一个子类继承自两个父类,而这两个父类又继承自同一个祖父类),可能会导致方法调用顺序混乱。Python 使用方法解析顺序(MRO)来解决这个问题,但理解起来可能比较复杂。例如:
class A:
def method(self):
print("A's method")
class B(A):
def method(self):
print("B's method")
class C(A):
def method(self):
print("C's method")
class D(B, C):
pass
d = D()
d.method() # 输出: B's method,根据 MRO 顺序调用
类属性和实例属性混淆
类属性是类的所有实例共享的属性,而实例属性是每个实例独有的属性。如果不小心修改了类属性,可能会影响所有实例。例如:
class MyClass:
class_attr = 0
def __init__(self):
self.instance_attr = 0
obj1 = MyClass()
obj2 = MyClass()
# 修改类属性
MyClass.class_attr = 1
print(obj1.class_attr) # 输出: 1
print(obj2.class_attr) # 输出: 1
17. 布尔运算相关
布尔运算的短路特性
在 Python 中,and
和 or
运算符具有短路特性。and
运算符在第一个操作数为 False
时不会计算第二个操作数;or
运算符在第一个操作数为 True
时不会计算第二个操作数。例如:
def func1():
print("Function 1 called")
return False
def func2():
print("Function 2 called")
return True
# and 运算的短路特性
result = func1() and func2() # 只会调用 func1()
print(result)
# or 运算的短路特性
result = func2() or func1() # 只会调用 func2()
print(result)
18. 递归函数相关
递归深度限制
Python 对递归调用的深度有一定的限制,默认情况下最大递归深度约为 1000 层。如果递归函数没有正确的终止条件,可能会导致 RecursionError
。例如:
def recursive_function():
return recursive_function()
try:
recursive_function()
except RecursionError as e:
print(f"Recursion error: {e}")
可以使用 sys.setrecursionlimit()
来修改递归深度限制,但不建议过度修改,因为可能会导致栈溢出。
19. 闭包相关
闭包中变量的延迟绑定
在闭包中,内部函数引用的外部函数变量是在调用内部函数时才进行绑定的,而不是在定义内部函数时。这可能导致结果与预期不符。
def create_multipliers():
return [lambda x: i * x for i in range(5)]
for multiplier in create_multipliers():
print(multiplier(2))
预期输出可能是 0, 2, 4, 6, 8
,但实际输出是 8, 8, 8, 8, 8
。这是因为当调用内部函数时,i
的值已经变成了 4
。可以通过将变量作为默认参数传递来解决这个问题:
def create_multipliers():
return [lambda x, i=i: i * x for i in range(5)]
for multiplier in create_multipliers():
print(multiplier(2))
20. 集合操作相关
集合的可变性和哈希问题
集合(set
)是可变对象,不能作为另一个集合的元素或字典的键,因为集合需要其元素是可哈希的(不可变)。而冻结集合(frozenset
)是不可变的,可以作为集合的元素或字典的键。
try:
my_set = {1, 2, {3, 4}}
except TypeError as e:
print(f"Error: {e}")
frozen_set = frozenset({3, 4})
my_set = {1, 2, frozen_set}
print(my_set)
集合运算的边界情况
在进行集合的交集、并集、差集等运算时,要注意空集和特殊元素的情况。例如,空集与任何集合的交集都是空集。
set_a = set()
set_b = {1, 2, 3}
intersection = set_a & set_b
print(intersection) # 输出: set()
21. 文件操作相关
文件未正确关闭
在使用 open()
函数打开文件后,如果不手动关闭文件或使用 with
语句,可能会导致资源泄漏。特别是在处理大量文件或长时间运行的程序中,这可能会成为严重问题。
# 错误示例,未关闭文件
file = open('test.txt', 'r')
content = file.read()
# 忘记调用 file.close()
# 正确示例,使用 with 语句
with open('test.txt', 'r') as file:
content = file.read()
# 文件会在 with 语句块结束时自动关闭
文件编码问题
在读取或写入文件时,如果不指定正确的编码方式,可能会导致乱码或 UnicodeDecodeError
、UnicodeEncodeError
等错误。
# 错误示例,未指定编码
try:
with open('test.txt', 'r') as file:
content = file.read()
except UnicodeDecodeError as e:
print(f"Encoding error: {e}")
# 正确示例,指定编码
with open('test.txt', 'r', encoding='utf-8') as file:
content = file.read()
22. 元组操作相关
单元素元组的定义
定义单元素元组时,必须在元素后面加上逗号,否则会被解释为普通的括号表达式。
# 错误示例,不是元组
single_value = (1)
print(type(single_value)) # 输出: <class 'int'>
# 正确示例,单元素元组
single_tuple = (1,)
print(type(single_tuple)) # 输出: <class 'tuple'>
23. 数据类型转换相关
浮点数和整数转换的精度问题
在将浮点数转换为整数时,Python 会直接截断小数部分,而不是四舍五入。如果需要四舍五入,可以使用 round()
函数。
num = 3.9
int_num = int(num)
print(int_num) # 输出: 3
rounded_num = round(num)
print(rounded_num) # 输出: 4
字符串和数字转换的错误处理
在将字符串转换为数字时,如果字符串不是有效的数字格式,会引发 ValueError
。在进行转换时,最好进行错误处理。
try:
num = int('abc')
except ValueError as e:
print(f"Conversion error: {e}")
24. 异步编程相关
异步函数和普通函数调用混淆
在 Python 的异步编程中,异步函数(使用 async def
定义)需要使用 await
关键字调用,不能像普通函数一样直接调用。如果直接调用异步函数,它只会返回一个协程对象,而不会执行函数体。
import asyncio
async def async_function():
print("Async function running")
# 错误示例,直接调用异步函数
coroutine = async_function()
print(coroutine) # 输出: <coroutine object async_function at 0x...>
# 正确示例,使用 await 调用
async def main():
await async_function()
asyncio.run(main())
异步任务管理和并发控制
在处理多个异步任务时,需要正确管理任务的创建、启动和等待。如果处理不当,可能会导致任务无法正常执行或资源耗尽。例如,使用 asyncio.gather()
可以并发运行多个协程,但要注意任务数量和资源限制。
import asyncio
async def task(i):
await asyncio.sleep(1)
print(f"Task {i} completed")
async def main():
tasks = [task(i) for i in range(10)]
await asyncio.gather(*tasks)
asyncio.run(main())
25. 装饰器带参数问题
多层嵌套理解困难
当装饰器需要接受参数时,需要使用多层嵌套函数,这可能会让初学者感到困惑。例如,一个带参数的装饰器:
def repeat(num_times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def say_hello():
print("Hello!")
say_hello()
这里的 repeat
函数接受参数,返回一个装饰器函数 decorator
,decorator
再返回实际的包装函数 wrapper
。理解和实现这种多层嵌套的逻辑需要一定的练习。
26. 数据比较相关
is
和 ==
的区别
is
用于比较两个对象是否是同一个对象(即是否具有相同的内存地址),而 ==
用于比较两个对象的值是否相等。初学者容易混淆这两个运算符。
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # 输出: True,值相等
print(a is b) # 输出: False,不是同一个对象
c = a
print(a is c) # 输出: True,是同一个对象
27. 装饰器与类方法结合使用
类方法装饰器参数传递问题
当装饰器应用于类方法时,需要特别注意装饰器如何处理类方法的参数。类方法的第一个参数通常是 cls
,如果装饰器没有正确处理这个参数,就会导致调用出错。
def my_decorator(func):
def wrapper(*args, **kwargs):
# 这里如果没有正确处理类方法参数可能会出错
print("Before method call")
result = func(*args, **kwargs)
print("After method call")
return result
return wrapper
class MyClass:
@my_decorator
def my_method(cls):
print("Inside class method")
# 错误调用方式(如果装饰器未正确处理参数)
# obj = MyClass()
# obj.my_method()
# 正确使用类方法的调用方式
MyClass.my_method()
装饰器修改类方法行为的意外后果
使用装饰器修改类方法的行为时,可能会影响到类的继承和多态特性。比如,装饰器改变了方法的返回值类型,可能会导致子类在调用该方法时出现兼容性问题。
28. 上下文管理器相关
__enter__
和 __exit__
方法实现错误
自定义上下文管理器时,需要正确实现 __enter__
和 __exit__
方法。__enter__
方法负责进入上下文时的初始化操作,__exit__
方法负责离开上下文时的清理操作。如果 __exit__
方法返回值处理不当,可能会影响异常的传播。
class MyContextManager:
def __enter__(self):
print("Entering context")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("Exiting context")
# 如果返回 True,异常将被抑制;返回 False,异常将继续传播
return False
with MyContextManager():
raise ValueError("An error occurred")
嵌套上下文管理器的使用
在使用多个嵌套的上下文管理器时,需要注意它们的执行顺序和资源管理。如果嵌套逻辑复杂,可能会导致资源释放不及时或重复释放的问题。
with open('file1.txt', 'r') as f1, open('file2.txt', 'w') as f2:
# 操作文件
pass
29. 动态属性和方法相关
使用 __getattr__
和 __setattr__
时的无限递归
在自定义类中重写 __getattr__
和 __setattr__
方法时,如果处理不当,很容易引发无限递归错误。__getattr__
在访问不存在的属性时被调用,__setattr__
在设置属性时被调用。
class MyClass:
def __getattr__(self, name):
# 错误示例,会导致无限递归
# return self.name
return f"Attribute {name} not found"
def __setattr__(self, name, value):
# 错误示例,会导致无限递归
# self.name = value
super().__setattr__(name, value)
obj = MyClass()
print(obj.some_attr)
obj.new_attr = 10
动态添加方法的绑定问题
在运行时动态地为类或实例添加方法时,需要注意方法的绑定问题。如果直接将一个函数赋值给实例属性,该函数不会自动绑定到实例上。
def my_function(self):
print("This is a dynamically added method")
class MyClass:
pass
obj = MyClass()
# 错误方式,函数未正确绑定
# obj.my_method = my_function
# obj.my_method()
# 正确方式,使用 types.MethodType 绑定
import types
obj.my_method = types.MethodType(my_function, obj)
obj.my_method()
30. 元类相关
元类的复杂行为理解困难
元类是创建类的类,Python 中默认的元类是 type
。使用元类可以在类创建时动态修改类的行为,但元类的概念比较抽象,其实现和使用容易出错。
class MyMeta(type):
def __new__(cls, name, bases, attrs):
# 在类创建时修改属性
attrs['new_attribute'] = 'Added by metaclass'
return super().__new__(cls, name, bases, attrs)
class MyClass(metaclass=MyMeta):
pass
obj = MyClass()
print(obj.new_attribute)
更多元类信息参考 Python 元类:编程魔法的深度揭秘与实战应用-CSDN博客
元类与类继承的交互问题
当使用元类的类参与继承时,可能会出现一些意想不到的行为。例如,子类继承使用元类的父类时,需要确保元类的行为在继承体系中正确传递和处理。
31. 正则表达式相关
正则表达式的贪婪匹配与非贪婪匹配
正则表达式默认是贪婪匹配,即尽可能多地匹配字符。如果需要非贪婪匹配,需要使用 ?
修饰符。初学者容易忽略这一点,导致匹配结果不符合预期。
import re
text = '<html><body><h1>Hello</h1></body></html>'
# 贪婪匹配
greedy_match = re.search(r'<.*>', text)
print(greedy_match.group()) # 输出整个字符串
# 非贪婪匹配
non_greedy_match = re.search(r'<.*?>', text)
print(non_greedy_match.group()) # 输出 '<html>'
正则表达式中的转义字符问题
在正则表达式中,某些字符具有特殊含义,如 .
、*
、+
等。如果需要匹配这些字符本身,需要使用反斜杠进行转义。同时,Python 字符串本身也使用反斜杠进行转义,这可能会导致混淆。
import re
# 匹配点号字符
text = 'abc.def'
# 错误示例,没有正确转义
# match = re.search(r'.', text)
# 正确示例
match = re.search(r'\.', text)
print(match.group())
32. 内存管理相关
循环引用导致的内存泄漏
当对象之间存在循环引用时,Python 的垃圾回收机制可能无法及时回收这些对象,从而导致内存泄漏。例如,两个对象相互引用对方。
class A:
def __init__(self):
self.b = None
class B:
def __init__(self):
self.a = None
a = A()
b = B()
a.b = b
b.a = a
# 此时 a 和 b 形成循环引用,即使不再使用,也可能不会被及时回收
大对象占用内存问题
处理大文件、大列表或大字典等大对象时,如果不注意内存使用情况,可能会导致内存耗尽。例如,一次性将大文件内容全部读入内存。
# 错误示例,可能会耗尽内存
# with open('large_file.txt', 'r') as f:
# content = f.read()
# 正确示例,逐行处理
with open('large_file.txt', 'r') as f:
for line in f:
# 处理每一行
pass
33. 命名空间和作用域相关
局部命名空间和全局命名空间的混淆
在函数内部,如果没有正确区分局部变量和全局变量,可能会导致意外的结果。在函数内部尝试修改全局变量时,如果不使用 global
关键字声明,Python 会默认创建一个局部变量。
x = 10
def modify_x():
x = 20 # 这里创建了一个局部变量 x,而不是修改全局的 x
print(x)
modify_x()
print(x) # 输出 10,全局变量 x 未被修改
def modify_global_x():
global x
x = 20 # 使用 global 关键字,修改全局变量 x
print(x)
mo