python 语法基础

文章目录

基础数据类型

字面量

  • 代码中被写下的固定的值

常用的值类型

字符串

字符串的格式化
  • 占位符号:

    s = "a = %d, b = %f" % (a, b)
    
  • format:

    s = "a = {}, b = {}".format(a, b)
    
  • f-string:

    s = f"a = {a}. b = {b}"
    
字符串的索引
  • 负索引:倒着数的第n个数,没有-0
字符串的切片
  • 普通用法:(默认 step 为 1)

    • s_cut = s[start:end] : 从 start 切到 end,不包括 end 位置的数据

    • 省略 start 时,从开头开始到 end

    • 省略 end 时,从start 开始到最后

    • start 与 end 为负数时代表从倒数相应位置切片

  • 特殊用法:s_cut = s[start:end:step]

    • step:正负表示切片的方向,每 step 个元素切出 1 个元素
字符串常规操作
  • 通常操作不会改变原字符串,而是返回新字符串
  • 内置函数 len(),求长度
大小写转换
  • 首字母转大写:new_str = s.capitalize()
  • 单词转大写:new_str = s.title(),单词:连续的英文字母序列
  • 全部字母变小写:new_str = s.lower()
  • 全部字母变大写:new_str = s.upper()
字符串的切割与替换
  • 去掉字符串左右两端的空白符:s.trip()

  • 字符串内容替换:s.replace(old,new)

  • 字符串切割:s.split(thing),用 thing 作为分割切

    • 字符串拼接:

      lst = ["a", "b"] # 列表元素只能为 str 类型
      s = "_".join(lst) # 用 thing 拼接,thing.join()
      
字符串查找和判断
  • 查找
    • s.find(thing),在 s 中找 thing,找到后返回 thing 在 s 中的位置(索引);没找到返回 -1
    • s.index(thing),在 s 中找 thing 出现的位置,返回 thing 在 s 中的位置(索引);没有 thing 报错
    • print(thing in s),True 则 s 中有 thing,False 则 s 中没有 thing(in 的条件判断,还有not in)
  • 判断字符串是否以 thing 开头:s.startsWith(thing),返回 bool 值
  • 判断字符串是否由整数组成:s.isdigit()

列表

像字符串一样,有索引、切片、len()for循环遍历

直接用索引进行修改和查询

向列表中增加元素
  • append(thing),追加元素
  • insert(index, thing),在位置 index 插入元素 thing
  • entend([...]),可以合并两个列表,批量添加元素
删除列表中的元素
  • thing = list.pop(index),给出被删除元素的索引,返回被删除的元素
  • list.remove(thing),无返回值
列表排序
  • 升序:list.sort()
  • 降序:list.sort(reverse=True)

元组(tuple)

元组可看作不可变的列表,可以进行切片、索引(注意:不可变的是元素的地址,而不是内容

t = (...)

  • 如果元组中只有一个元素,那么需要在元素后添加逗号

  • 当元组元素中有列表时,可以对列表进行操作

注意:解构(解包):元组和列表都可以执行的操作,a, b = (1, 2) -> a = 1, b = 2

集合(set)

内部元素无序,在装入元素时进行哈希计算,根据哈希值进行存储(可哈希的数据类型是不可变的数据类型)

可变的数据类型:list, dict, set

set = {...}

注意:直接 set = {} 时,set 的类型变为 dict,如果要创建空集合,则 s = set()

  • 添加元素:set.add(thing)
  • 删除元素:set.remove(thing)
  • 修改元素:删除前元素 -> 新增新元素
  • 集合的交集:set1 & set2 或者 set1.intersection(set2)
  • 集合的并集:set1 | set2 或者 set1.union(set2)
  • 集合的差集:set1 - set2 或者 set1.difference(set2)
  • 集合的去重:集合中不会有相同的元素,可以利用将列表转为集合的方式去除列表中重复的元素

字典(dict)

字典以键值对存储数据,表示为:dict = {key1:value1, key2:value2 ...}

拿数据:val = dict[key]key 必须可哈希,value无要求

  • 添加数据:dic[key] = value,如果该 key 已经被使用,那么新 value 覆盖原 value
  • 设置默认值:dict.setdefault(key:value),如果以前有该 key,则不起作用
  • 删除:根据 key 删,dict.pop(key)
  • 查询:根据 key 查询,dict[key](如果 key 不存在则报错) 或者 dict.get(key)(key 不存在返回 None)
  • 遍历:
    • for key in dict:
    • for key, value in dict.items():
  • 获取 key 的列表:list(dic.keys()),value 同理
  • 直接获取键值对:dict.items()

注意:字典在循环时不能删除元素,会报错

bytes

1. bytes 类型

定义和特点
  • bytes 是一种不可变的序列类型,用于存储二进制数据。
  • bytes 对象中的每个元素是一个整数,取值范围是 0 到 255。
创建 bytes 对象
  • 使用字节字面量:
    b = b'hello'
    
  • 使用 bytes() 构造函数:
    b = bytes([104, 101, 108, 108, 111])  # 通过整数列表创建
    b = bytes('hello', 'utf-8')  # 通过字符串和编码创建
    
操作 bytes 对象
  • 索引和切片:
    b = b'hello'
    print(b[1])  # 输出:101
    print(b[1:3])  # 输出:b'el'
    
  • 常用方法:
    b = b'hello world'
    print(b.split())  # 输出:[b'hello', b'world']
    print(b.replace(b'world', b'Python'))  # 输出:b'hello Python'
    

2. 字符编码与解码

字符编码是将字符串转换为字节序列的过程,解码是将字节序列转换回字符串的过程。

常见字符编码
  • ASCII:仅支持128个字符,适用于英语。
  • UTF-8:可变长度编码,兼容ASCII,广泛使用。
  • UTF-16UTF-32:定长编码,适用于包含大量非ASCII字符的场景。
  • GB2312GBKGB18030:中国常用编码。
编码(字符串 -> 字节序列)

使用 str.encode(encoding) 方法将字符串编码为字节序列:

s = 'hello'
b = s.encode('utf-8')
print(b)  # 输出:b'hello'
解码(字节序列 -> 字符串)

使用 bytes.decode(encoding) 方法将字节序列解码为字符串:

b = b'hello'
s = b.decode('utf-8')
print(s)  # 输出:'hello'

3. 编码与解码的应用

处理文件

读取和写入文件时指定编码:

with open('example.txt', 'w', encoding='utf-8') as f:
    f.write('你好,世界')

with open('example.txt', 'r', encoding='utf-8') as f:
    content = f.read()
    print(content)  # 输出:'你好,世界'
网络编程

在网络传输中常常需要将数据编码为字节序列:

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('example.com', 80))
request = 'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n'
s.send(request.encode('utf-8'))
response = s.recv(4096)
print(response.decode('utf-8'))
s.close()

4. 字节序列与其他数据类型的转换

bytearray 的区别
  • bytes 是不可变的,而 bytearray 是可变的。
  • 创建 bytearray
    b = bytearray(b'hello')
    b[0] = 104  # 可以修改
    print(b)  # 输出:bytearray(b'hello')
    
转换为整数

可以使用 int.from_bytes()int.to_bytes() 方法进行转换:

b = b'\x00\x10'
n = int.from_bytes(b, 'big')
print(n)  # 输出:16

n = 16
b = n.to_bytes(2, 'big')
print(b)  # 输出:b'\x00\x10'
Base64 编码

将二进制数据编码为Base64字符串:

import base64

b = b'hello world'
encoded = base64.b64encode(b)
print(encoded)  # 输出:b'aGVsbG8gd29ybGQ='

decoded = base64.b64decode(encoded)
print(decoded)  # 输出:b'hello world'

函数

函数的参数

1. 位置参数(Positional Arguments)

位置参数是按照参数在函数定义中的位置传递给函数的参数。

def greet(name, message):
    print(f"{message}, {name}!")

greet("Alice", "Hello")  # 输出:Hello, Alice!

2. 关键字参数(Keyword Arguments)

关键字参数是通过参数名称传递的参数,可以在调用函数时指定参数名称,从而不必按照定义时的顺序传递。

def greet(name, message):
    print(f"{message}, {name}!")

greet(message="Hello", name="Alice")  # 输出:Hello, Alice!

3. 默认参数(Default Arguments)

在函数定义时为参数提供默认值,如果调用函数时没有传递该参数,则使用默认值。

def greet(name, message="Hello"):
    print(f"{message}, {name}!")

greet("Alice")  # 输出:Hello, Alice!
greet("Bob", "Hi")  # 输出:Hi, Bob!

4. 可变参数列表(Variable-length Arguments)

Python允许函数接收可变数量的参数,分为两种类型:*args**kwargs

位置参数列表(*args

*args 用于接收任意数量的未命名参数,这些参数以元组的形式传递给函数。

def sum_all(*args):
    return sum(args)

print(sum_all(1, 2, 3, 4))  # 输出:10
关键字参数字典(**kwargs

**kwargs 用于接收任意数量的关键字参数,这些参数以字典的形式传递给函数。

def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=30, city="New York")
# 输出:
# name: Alice
# age: 30
# city: New York

5. 参数解包(Argument Unpacking)

可以使用 *** 运算符在函数调用时解包参数列表和参数字典。

def greet(name, message):
    print(f"{message}, {name}!")

args = ("Alice", "Hello")
kwargs = {"name": "Bob", "message": "Hi"}

greet(*args)  # 等同于:greet("Alice", "Hello")
greet(**kwargs)  # 等同于:greet(name="Bob", message="Hi")

6. 强制关键字参数(Keyword-only Arguments)

在函数定义中,可以使用 * 分隔参数列表,从而强制后面的参数必须以关键字形式传递。

def greet(name, *, message="Hello"):
    print(f"{message}, {name}!")

greet("Alice", message="Hi")  # 输出:Hi, Alice!

7. 参数的顺序

定义函数时,参数的顺序应该是:位置参数、默认参数、*args、强制关键字参数、**kwargs

def example(pos1, pos2, def1=3, *args, kw1, kw2=5, **kwargs):
    print(pos1, pos2, def1)
    print(args)
    print(kw1, kw2)
    print(kwargs)

example(1, 2, 4, 5, 6, 7, kw1="a", kw3="b", kw4="c")
# 输出:
# 1 2 4
# (5, 6, 7)
# a 5
# {'kw3': 'b', 'kw4': 'c'}

8. 函数注解(Function Annotations)

函数注解可以为函数的参数和返回值添加元数据,但对函数行为没有影响,仅用于说明和文档目的。

def greet(name: str, message: str = "Hello") -> None:
    print(f"{message}, {name}!")

greet("Alice")  # 输出:Hello, Alice!

函数的返回值

在Python中,函数的返回值是指函数执行完成后返回给调用者的数据。

1. 基本返回值

使用 return 语句

return 语句用于将函数的结果返回给调用者。如果没有显式的 return 语句,函数会默认返回 None

def add(a, b):
    return a + b

result = add(3, 5)
print(result)  # 输出:8
没有 return 语句的函数

如果函数中没有 return 语句,或者 return 语句没有跟随任何表达式,函数会返回 None

def do_nothing():
    pass

result = do_nothing()
print(result)  # 输出:None

2. 返回多个值

Python支持从函数中返回多个值,这些值会被自动打包成一个元组。

def get_name_and_age():
    name = "Alice"
    age = 30
    return name, age

name, age = get_name_and_age()
print(name)  # 输出:Alice
print(age)  # 输出:30

3. 返回列表或字典

函数可以返回任意类型的对象,包括列表、字典、元组、集合等。

返回列表
def create_list():
    return [1, 2, 3]

lst = create_list()
print(lst)  # 输出:[1, 2, 3]
返回字典
def create_dict():
    return {"name": "Alice", "age": 30}

d = create_dict()
print(d)  # 输出:{'name': 'Alice', 'age': 30}

4. 返回函数对象

函数可以返回另一个函数,这种特性在函数式编程和装饰器中非常有用。

def outer_function():
    def inner_function():
        print("Hello from inner function")
    return inner_function

f = outer_function()
f()  # 输出:Hello from inner function

5. 返回生成器

通过使用 yield 关键字,函数可以返回一个生成器对象,而不是一次性返回所有结果。这在处理大数据集时非常有用。

def generate_numbers(n):
    for i in range(n):
        yield i

gen = generate_numbers(5)
for num in gen:
    print(num)  # 输出:0 1 2 3 4

6. 类型提示

在Python 3.5及以上版本中,可以使用类型提示来指明函数的返回类型,这有助于提高代码的可读性和可维护性。

def add(a: int, b: int) -> int:
    return a + b

result = add(3, 5)
print(result)  # 输出:8

7. 高阶函数

高阶函数是指返回另一个函数或者接收一个或多个函数作为参数的函数。

def high_order_function(func):
    def wrapper():
        print("Before the function call")
        func()
        print("After the function call")
    return wrapper

def say_hello():
    print("Hello!")

wrapped_function = high_order_function(say_hello)
wrapped_function()
# 输出:
# Before the function call
# Hello!
# After the function call

8. 实践示例

函数返回字典
def get_user_info():
    return {"name": "Bob", "age": 25, "email": "bob@example.com"}

user_info = get_user_info()
print(user_info["name"])  # 输出:Bob
函数返回复杂对象
class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age

def create_user(name, age):
    return User(name, age)

user = create_user("Carol", 28)
print(user.name)  # 输出:Carol
print(user.age)  # 输出:28

通过理解和灵活使用函数的返回值,可以显著提升代码的功能性和灵活性,使代码更具可读性和可维护性。

匿名函数

在Python中,匿名函数(Anonymous Functions)是指没有使用def关键字来声明的函数,而是通过lambda关键字创建的函数。匿名函数通常用于简单的操作,可以在一行内定义并返回结果。

1. 基本语法

匿名函数使用lambda关键字定义,语法形式如下:

lambda 参数列表: 表达式
  • lambda:关键字,表示创建一个匿名函数。
  • 参数列表:匿名函数接收的参数,可以有多个,用逗号分隔。
  • 表达式:匿名函数的主体,计算结果并返回。

2. 示例

简单示例
# 定义一个匿名函数,计算两个数的和
add = lambda x, y: x + y
print(add(3, 5))  # 输出:8
无参数的匿名函数
# 定义一个无参数的匿名函数,返回一个固定值
greet = lambda: "Hello, World!"
print(greet())  # 输出:Hello, World!

3. 匿名函数的应用

map函数结合使用

map函数用于将指定函数应用于迭代器的每个元素,并返回一个迭代器。

numbers = [1, 2, 3, 4, 5]
squared = map(lambda x: x ** 2, numbers)
print(list(squared))  # 输出:[1, 4, 9, 16, 25]
filter函数结合使用

filter函数用于过滤序列,返回一个包含所有使函数返回值为真的元素的迭代器。

numbers = [1, 2, 3, 4, 5]
even = filter(lambda x: x % 2 == 0, numbers)
print(list(even))  # 输出:[2, 4]
reduce函数结合使用

reduce函数用于对序列中的元素进行累积操作。reduce函数在functools模块中。

from functools import reduce

numbers = [1, 2, 3, 4, 5]
summed = reduce(lambda x, y: x + y, numbers)
print(summed)  # 输出:15
sorted函数结合使用

sorted函数用于排序序列,可以通过key参数指定排序依据。

students = [("Alice", 25), ("Bob", 20), ("Charlie", 23)]
sorted_students = sorted(students, key=lambda x: x[1])
print(sorted_students)  # 输出:[('Bob', 20), ('Charlie', 23), ('Alice', 25)]

4. 匿名函数的局限性

匿名函数有一些局限性,主要包括:

  • 只能包含一个表达式:匿名函数的主体只能是一个表达式,不能包含多条语句。
  • 没有函数名:匿名函数没有名称,因此调试和重用变得困难。
  • 可读性较差:过度使用匿名函数可能导致代码可读性降低。

5. 匿名函数与普通函数的对比

匿名函数适用于简单的操作,而普通函数(使用def定义)适用于复杂的操作。

6. 高阶函数中的应用

匿名函数在高阶函数中非常有用。高阶函数是指接受函数作为参数或返回一个函数的函数。

def apply_func(f, x, y):
    return f(x, y)

result = apply_func(lambda x, y: x * y, 3, 5)
print(result)  # 输出:15

内置函数

1. format() 函数

功能

format() 函数用于格式化字符串。它可以通过位置或关键字参数,将指定的值插入到字符串中的特定位置。

基本语法
format(value, format_spec)
  • value:要格式化的值。
  • format_spec:格式说明符,用于指定格式化的细节。
示例
基本使用
# 使用位置参数
print("Hello, {}!".format("Alice"))  # 输出:Hello, Alice!

# 使用关键字参数
print("Hello, {name}!".format(name="Bob"))  # 输出:Hello, Bob!

# 使用位置和关键字参数的混合
print("Hello, {0}! You are {age} years old.".format("Charlie", age=30))  # 输出:Hello, Charlie! You are 30 years old.
格式说明符

格式说明符可以用于指定数值的格式,如十进制、小数点位数、填充字符、对齐方式等。

# 数字格式化
print("{:d}".format(42))  # 输出:42(十进制整数)
print("{:b}".format(42))  # 输出:101010(二进制)
print("{:o}".format(42))  # 输出:52(八进制)
print("{:x}".format(42))  # 输出:2a(十六进制)

# 浮点数格式化
print("{:.2f}".format(3.14159))  # 输出:3.14(保留两位小数)
print("{:e}".format(3.14159))    # 输出:3.141590e+00(科学计数法)

# 字符串对齐
print("{:<10}".format("left"))   # 输出:left      (左对齐)
print("{:>10}".format("right"))  # 输出:     right(右对齐)
print("{:^10}".format("center")) # 输出: center  (居中对齐)

2. ord() 函数

功能

ord() 函数用于返回单个字符的Unicode码点(整数表示)。

基本语法
ord(character)
  • character:一个字符。
示例
print(ord('A'))  # 输出:65
print(ord('a'))  # 输出:97
print(ord('0'))  # 输出:48
print(ord('$'))  # 输出:36

3. chr() 函数

功能

chr() 函数用于返回指定Unicode码点对应的字符。

基本语法
chr(i)
  • i:一个整数,表示Unicode码点。
示例
print(chr(65))  # 输出:A
print(chr(97))  # 输出:a
print(chr(48))  # 输出:0
print(chr(36))  # 输出:$

4. enumerate() 函数

功能

enumerate() 函数用于将一个可迭代对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用于在 for 循环中获取索引和对应的值。

基本语法
enumerate(iterable, start=0)
  • iterable:一个可迭代对象。
  • start:指定索引的起始值,默认为 0。
示例
基本使用
names = ['Alice', 'Bob', 'Charlie']
for index, name in enumerate(names):
    print(index, name)
# 输出:
# 0 Alice
# 1 Bob
# 2 Charlie
指定起始索引
names = ['Alice', 'Bob', 'Charlie']
for index, name in enumerate(names, start=1):
    print(index, name)
# 输出:
# 1 Alice
# 2 Bob
# 3 Charlie

5. any() 函数

功能

any() 函数用于判断可迭代对象中是否至少有一个元素为真。如果有,则返回 True;如果全为假,则返回 False

基本语法
any(iterable)
  • iterable:一个可迭代对象。
示例
列表中的布尔值
print(any([False, True, False]))  # 输出:True
print(any([False, False, False]))  # 输出:False
空列表
print(any([]))  # 输出:False
列表中的非零数值
print(any([0, 1, 0]))  # 输出:True
print(any([0, 0, 0]))  # 输出:False

6. all() 函数

功能

all() 函数用于判断可迭代对象中的所有元素是否都为真。如果是,则返回 True;如果有一个为假,则返回 False

基本语法
all(iterable)
  • iterable:一个可迭代对象。
示例
列表中的布尔值
print(all([True, True, True]))  # 输出:True
print(all([True, False, True]))  # 输出:False
空列表
print(all([]))  # 输出:True
列表中的非零数值
print(all([1, 2, 3]))  # 输出:True
print(all([1, 0, 3]))  # 输出:False

7. map() 函数

功能

map() 函数用于将指定函数应用于可迭代对象的每个元素,返回一个迭代器。

基本语法
map(function, iterable, ...)
  • function:应用于每个元素的函数。
  • iterable:一个或多个可迭代对象。
示例
将列表中的每个元素平方
numbers = [1, 2, 3, 4]
squared = map(lambda x: x ** 2, numbers)
print(list(squared))  # 输出:[1, 4, 9, 16]
将多个列表的对应元素相加
list1 = [1, 2, 3]
list2 = [4, 5, 6]
summed = map(lambda x, y: x + y, list1, list2)
print(list(summed))  # 输出:[5, 7, 9]

8. filter() 函数

功能

filter() 函数用于过滤可迭代对象,返回一个包含所有使函数返回值为 True 的元素的迭代器。

基本语法
filter(function, iterable)
  • function:用于测试每个元素的函数。
  • iterable:一个可迭代对象。
示例
过滤出列表中的偶数
numbers = [1, 2, 3, 4, 5, 6]
evens = filter(lambda x: x % 2 == 0, numbers)
print(list(evens))  # 输出:[2, 4, 6]
过滤出长度大于3的字符串
words = ["apple", "banana", "cherry", "date"]
long_words = filter(lambda word: len(word) > 3, words)
print(list(long_words))  # 输出:['apple', 'banana', 'cherry']

9. sorted() 函数

功能

sorted() 函数用于对可迭代对象进行排序,返回一个新的排序列表。

基本语法
sorted(iterable, key=None, reverse=False)
  • iterable:一个可迭代对象。
  • key:一个函数,指定排序依据。
  • reverse:布尔值,指定是否降序排序。
示例
对列表进行升序排序
numbers = [4, 1, 3, 2]
sorted_numbers = sorted(numbers)
print(sorted_numbers)  # 输出:[1, 2, 3, 4]
按字符串长度排序
words = ["apple", "banana", "cherry", "date"]
sorted_words = sorted(words, key=len)
print(sorted_words)  # 输出:['date', 'apple', 'banana', 'cherry']
逆序排序
numbers = [4, 1, 3, 2]
sorted_numbers = sorted(numbers, reverse=True)
print(sorted_numbers)  # 输出:[4, 3, 2, 1]

闭包

闭包(Closure)是指在一个内嵌函数中引用了其外部作用域中的变量,并且这个内嵌函数在其外部作用域之外被调用时,仍然能够访问这些变量。闭包是一个函数对象,它能够记住并访问创建它时存在于作用域中的变量,即使在闭包函数外部调用时,这些变量依然存在。

闭包的使用场景主要包括回调函数、工厂函数、装饰器等。

1. 闭包的基本概念

闭包需要满足以下三个条件:

  1. 存在一个内嵌函数。
  2. 内嵌函数引用了其外部作用域中的变量。
  3. 外部函数返回内嵌函数。

2. 闭包示例

  • 闭包可以让一部分变量常驻于内存
  • 可以避免全局变量被修改
简单示例
def outer_function(x):
    def inner_function(y):
        return x + y
    return inner_function

closure = outer_function(10)
print(closure(5))  # 输出:15

在这个例子中,inner_function 引用了外部函数 outer_function 中的变量 x。当 outer_function 返回 inner_function 时,inner_function 就成为了一个闭包,记住了 x 的值。

3. 闭包的应用场景

闭包用于记住状态

闭包可以用于记住函数的状态,例如创建计数器函数。

def make_counter():
    count = 0
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

counter1 = make_counter()
print(counter1())  # 输出:1
print(counter1())  # 输出:2

counter2 = make_counter()
print(counter2())  # 输出:1
print(counter2())  # 输出:2
闭包用于延迟计算

闭包可以用于延迟计算,直到需要结果时才进行计算。

def make_power(n):
    def power(x):
        return x ** n
    return power

square = make_power(2)
print(square(5))  # 输出:25

cube = make_power(3)
print(cube(5))  # 输出:125
闭包用于回调函数

闭包可以用于定义回调函数,使得回调函数能够记住并访问其定义时的环境。

def make_callback(message):
    def callback():
        print(message)
    return callback

hello_callback = make_callback("Hello, World!")
hello_callback()  # 输出:Hello, World!

4. 闭包与函数对象属性

闭包中的函数对象可以通过其 __closure__ 属性访问闭包的变量。

def outer_function(x):
    def inner_function(y):
        return x + y
    return inner_function

closure = outer_function(10)
print(closure.__closure__)  # 输出:(<cell at 0x...: int object at 0x...>,)
print(closure.__closure__[0].cell_contents)  # 输出:10

5. 闭包与装饰器

装饰器是Python中一个重要的应用闭包的场景。装饰器本质上是一个返回闭包的高阶函数,可以用来修改或扩展函数的行为。

def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func()
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()
# 输出:
# Something is happening before the function is called.
# Hello!
# Something is happening after the function is called.

在这个例子中,装饰器 my_decorator 返回了一个闭包 wrapper,它扩展了被装饰函数 say_hello 的行为。

6. 注意事项

  • 变量作用域:闭包中引用的变量必须在外部函数作用域内定义,不能是全局作用域或其他函数的局部变量。
  • 变量生命周期:闭包中引用的变量在闭包的整个生命周期内都有效。
  • 性能考虑:虽然闭包很强大,但过度使用可能会导致代码难以理解和维护,尤其是当闭包嵌套过深时。

装饰器

装饰器(Decorator)是Python中的一种设计模式,用于在不改变函数本身的情况下,动态地给函数添加功能。装饰器本质上是一个返回函数的高阶函数,它可以接受一个函数作为参数,并返回一个增强后的函数。装饰器通常用于日志记录、访问控制、性能计数器等场景。

1. 基本概念

装饰器的定义

装饰器是一个函数,它接受一个函数作为参数,并返回一个新的函数。

def decorator(func):
    def wrapper():
        # 在调用原始函数之前可以添加额外的代码
        print("Something is happening before the function is called.")
        func()  # 调用原始函数
        # 在调用原始函数之后可以添加额外的代码
        print("Something is happening after the function is called.")
    return wrapper
使用装饰器

装饰器可以通过 @ 语法糖应用于函数。

@decorator
def say_hello():
    print("Hello!")

say_hello()
# 输出:
# Something is happening before the function is called.
# Hello!
# Something is happening after the function is called.

2. 带参数的装饰器

装饰器本身可以接受参数,从而更灵活地控制其行为。

def decorator_with_args(arg):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(f"Decorator argument: {arg}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@decorator_with_args("test") # 此时的装饰器还是 decorator
def say_hello():
    print("Hello!")

say_hello()
# 输出:
# Decorator argument: test
# Hello!

3. 装饰器的实际应用

记录日志

装饰器可以用于记录函数的调用日志。

def log(func):
    def wrapper(*args, **kwargs):
        print(f"Calling function {func.__name__} with arguments {args} and {kwargs}")
        result = func(*args, **kwargs)
        print(f"Function {func.__name__} returned {result}")
        return result
    return wrapper

@log
def add(a, b):
    return a + b

add(1, 2)
# 输出:
# Calling function add with arguments (1, 2) and {}
# Function add returned 3
权限检查

装饰器可以用于检查用户权限。

def require_permission(permission):
    def decorator(func):
        def wrapper(user, *args, **kwargs):
            if user.has_permission(permission):
                return func(user, *args, **kwargs)
            else:
                raise PermissionError("Permission denied")
        return wrapper
    return decorator

class User:
    def __init__(self, name, permissions):
        self.name = name
        self.permissions = permissions
    
    def has_permission(self, permission):
        return permission in self.permissions

@require_permission("admin")
def delete_user(user, user_to_delete):
    print(f"User {user_to_delete.name} deleted by {user.name}")

admin = User("admin", ["admin"])
guest = User("guest", [])

delete_user(admin, guest)  # 成功
# delete_user(guest, admin)  # 抛出 PermissionError

4. 函数元数据的保留

使用 functools.wraps 可以保留原始函数的元数据(如函数名、文档字符串等),使装饰后的函数看起来更像原始函数。

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling function {func.__name__} with arguments {args} and {kwargs}")
        return func(*args, **kwargs)
    return wrapper

@log
def add(a, b):
    """This is the add function."""
    return a + b

print(add.__name__)  # 输出:add
print(add.__doc__)   # 输出:This is the add function.

5. 类装饰器

类装饰器是指使用类来实现装饰器。类装饰器通过实现 __call__ 方法,使得类实例可以像函数一样调用。

class Log:
    def __init__(self, func):
        self.func = func
    
    def __call__(self, *args, **kwargs):
        print(f"Calling function {self.func.__name__} with arguments {args} and {kwargs}")
        return self.func(*args, **kwargs)

@Log
def add(a, b):
    return a + b

add(1, 2)
# 输出:
# Calling function add with arguments (1, 2) and {}
# 3

6. 嵌套装饰器

可以对同一个函数应用多个装饰器,装饰器将从内到外依次应用。

def decorator1(func):
    def wrapper(*args, **kwargs):
        print("Decorator 1")
        return func(*args, **kwargs)
    return wrapper

def decorator2(func):
    def wrapper(*args, **kwargs):
        print("Decorator 2")
        return func(*args, **kwargs)
    return wrapper

@decorator1
@decorator2
def say_hello():
    print("Hello!")

say_hello()
# 输出:
# Decorator 1
# Decorator 2
# Hello!

迭代器

在Python中,迭代器(Iterator)是一种对象,支持在容器上逐个访问元素,且不需要一次性加载所有元素到内存中。迭代器是迭代器协议的一部分,该协议包括两个方法:__iter__()__next__()

1. 基本概念

迭代器协议
  • __iter__():返回迭代器对象本身。
  • __next__():返回容器的下一个元素。如果没有元素了,应该抛出 StopIteration 异常。
可迭代对象(Iterable)

可迭代对象是实现了 __iter__() 方法,返回一个迭代器的对象。常见的可迭代对象包括列表、元组、字典、集合和字符串。

iterable = [1, 2, 3]
iterator = iter(iterable)  # 获取迭代器对象
print(next(iterator))  # 输出:1
print(next(iterator))  # 输出:2
print(next(iterator))  # 输出:3
print(next(iterator))  # 抛出 StopIteration 异常

2. 自定义迭代器

创建一个简单的迭代器

可以通过定义一个类并实现 __iter__()__next__() 方法来自定义迭代器。

class MyIterator:
    def __init__(self, data):
        self.data = data
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index < len(self.data):
            result = self.data[self.index]
            self.index += 1
            return result
        else:
            raise StopIteration

# 使用自定义迭代器
my_iter = MyIterator([1, 2, 3])
for item in my_iter:
    print(item)
# 输出:
# 1
# 2
# 3

3. 生成器(Generators)

生成器是创建迭代器的便捷方式,它通过 yield 关键字生成值。生成器函数返回一个生成器对象,具有 __iter__()__next__() 方法。

生成器函数
def simple_generator():
    yield 1
    yield 2
    yield 3

gen = simple_generator()
for value in gen:
    print(value)
# 输出:
# 1
# 2
# 3
生成器表达式

生成器表达式与列表推导式类似,但使用圆括号创建生成器。

gen = (x ** 2 for x in range(5))
for value in gen:
    print(value)
# 输出:
# 0
# 1
# 4
# 9
# 16

4. 内置函数与迭代器

iter()

iter() 函数用于获取可迭代对象的迭代器。

lst = [1, 2, 3]
iterator = iter(lst)
print(next(iterator))  # 输出:1
next()

next() 函数用于获取迭代器的下一个元素。

iterator = iter([1, 2, 3])
print(next(iterator))  # 输出:1

5. 实际应用

文件读取

使用迭代器逐行读取文件,可以有效节省内存。

with open('example.txt', 'r') as file:
    for line in file:
        print(line.strip())
无限序列

使用生成器可以创建无限序列。

def infinite_sequence():
    num = 0
    while True:
        yield num
        num += 1

gen = infinite_sequence()
for _ in range(5):
    print(next(gen))
# 输出:
# 0
# 1
# 2
# 3
# 4

6. itertools 模块

itertools 模块提供了许多用于操作迭代器的有用工具,如 count(), cycle(), repeat(), chain(), islice(), tee(), zip_longest(), 等。

示例:itertools.count()
import itertools

counter = itertools.count(start=10, step=2)
for _ in range(5):
    print(next(counter))
# 输出:
# 10
# 12
# 14
# 16
# 18

7. 使用迭代器的最佳实践

  • 惰性求值:迭代器和生成器实现了惰性求值(Lazy Evaluation),可以在需要时逐个生成值,节省内存。
  • 无限序列:使用迭代器生成无限序列时要特别小心,避免意外的无限循环。
  • 避免过度复杂:虽然自定义迭代器和生成器很强大,但在实现复杂逻辑时要注意代码的可读性和维护性。

推导式与生成器表达式

在Python中,列表推导式(List Comprehensions)是一种简洁而强大的生成列表的方法。它可以从一个可迭代对象创建一个新的列表,语法简单,代码可读性高。

1. 基本语法

[expression for item in iterable if condition]
  • expression: 对每个元素进行处理的表达式。
  • item: 迭代过程中每个元素。
  • iterable: 要迭代的可迭代对象。
  • condition(可选): 一个可选的过滤条件,只有满足条件的元素才会被包含在生成的列表中。

2. 示例

生成平方数列表
squares = [x ** 2 for x in range(10)]
print(squares)  # 输出:[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
过滤偶数
evens = [x for x in range(10) if x % 2 == 0]
print(evens)  # 输出:[0, 2, 4, 6, 8]
转换字符串为大写
words = ["hello", "world"]
upper_words = [word.upper() for word in words]
print(upper_words)  # 输出:['HELLO', 'WORLD']
嵌套循环
pairs = [(x, y) for x in range(3) for y in range(3)]
print(pairs)  # 输出:[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]

3. 列表推导式的优势

  • 简洁性: 使用一行代码即可生成复杂的列表。
  • 可读性: 相比于使用 for 循环和 append() 方法,列表推导式更加直观和简洁。
  • 性能: 通常情况下,列表推导式比传统的循环方式更快。

4. 注意事项

  • 可读性: 虽然列表推导式很强大,但在使用时应注意不要让表达式过于复杂,以免影响代码的可读性。
  • 内存使用: 列表推导式会在内存中生成整个列表,对于非常大的数据集,可以考虑使用生成器表达式。

5. 列表推导式与其他数据结构

集合推导式
unique_squares = {x ** 2 for x in range(10)}
print(unique_squares)  # 输出:{0, 1, 4, 9, 16, 25, 36, 49, 64, 81}
字典推导式
square_dict = {x: x ** 2 for x in range(5)}
print(square_dict)  # 输出:{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
生成器表达式
gen = (x ** 2 for x in range(10))
print(list(gen))  # 输出:[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

通过理解和使用列表推导式,可以编写出更加简洁和高效的Python代码,特别是在需要对数据进行转换和过滤时。

补充

补充——变量

在Python编程中,变量的作用域(Scope)决定了变量的可见性和生命周期。

1. 局部变量(Local Variables)

定义和作用域

局部变量是在函数内部定义的变量,只能在该函数内部访问和使用。局部变量在函数执行完毕后被销毁。

def my_function():
    x = 10  # x 是局部变量
    print(x)

my_function()  # 输出:10
print(x)  # 错误:NameError: name 'x' is not defined
局部变量的优先级

在函数内部,如果局部变量与全局变量同名,局部变量会优先使用。

x = 5  # 全局变量

def my_function():
    x = 10  # 局部变量
    print(x)  # 输出:10

my_function()
print(x)  # 输出:5

2. 全局变量(Global Variables)

定义和作用域

全局变量是在函数外部定义的变量,可以在整个模块内访问和使用。全局变量在程序运行期间存在。

x = 5  # 全局变量

def my_function():
    print(x)  # 输出:5

my_function()
print(x)  # 输出:5

3. 使用 global 关键字

在函数内部,如果需要修改全局变量的值,需要使用 global 关键字来声明该变量是全局变量。

x = 5  # 全局变量

def my_function():
    global x
    x = 10  # 修改全局变量
    print(x)  # 输出:10

my_function()
print(x)  # 输出:10

4. 嵌套函数和 nonlocal 关键字

在嵌套函数中,可以使用 nonlocal 关键字来声明变量是最近的外层函数的局部变量。

def outer_function():
    x = 5  # 外层函数的局部变量

    def inner_function():
        nonlocal x
        x = 10  # 修改外层函数的局部变量
        print(x)  # 输出:10

    inner_function()
    print(x)  # 输出:10

outer_function()

5. 全局变量的最佳实践

虽然可以在函数中修改全局变量,但在实际编程中,尽量避免这样做,以减少代码的耦合和副作用。推荐使用函数参数和返回值来传递数据。

x = 5  # 全局变量

def modify_value(val):
    val = 10
    return val

x = modify_value(x)
print(x)  # 输出:10

6. 作用域链(Scope Chain)

Python在查找变量时,会按照 LEGB 规则(Local, Enclosing, Global, Built-in)查找变量:

  • L(Local):当前函数的局部作用域。
  • E(Enclosing):嵌套函数的外层函数的局部作用域。
  • G(Global):全局作用域,即模块的顶层作用域。
  • B(Built-in):内置作用域,即Python内置函数和变量的作用域。

补充——引用类型

在Python中,引用类型(Reference Types)是指通过引用(而非复制)传递的对象类型。这些对象包括列表(list)、字典(dict)、集合(set)、元组(tuple)等。

1. 引用类型的基本概念

什么是引用类型?

引用类型的变量存储的是对象的引用(地址),而不是对象本身。这意味着多个变量可以引用同一个对象,对该对象的修改会影响所有引用该对象的变量。

引用类型的特点
  • 引用类型的数据在内存中动态分配。
  • 变量存储的是对象的内存地址。
  • 复制引用类型的变量只会复制引用(地址),而不会复制对象本身。

2. 列表(list)

列表是Python中最常用的引用类型。列表是可变的,可以包含任意类型的元素。

a = [1, 2, 3]
b = a  # b 引用 a
b.append(4)
print(a)  # 输出:[1, 2, 3, 4]
print(b)  # 输出:[1, 2, 3, 4]

3. 字典(dict)

字典是存储键值对的无序集合,也是引用类型。字典的键必须是不可变类型,而值可以是任意类型。

a = {"name": "Alice", "age": 30}
b = a  # b 引用 a
b["age"] = 31
print(a)  # 输出:{'name': 'Alice', 'age': 31}
print(b)  # 输出:{'name': 'Alice', 'age': 31}

4. 集合(set)

集合是一个无序且不重复的元素集合。集合也是引用类型,常用于去重和集合操作。

a = {1, 2, 3}
b = a  # b 引用 a
b.add(4)
print(a)  # 输出:{1, 2, 3, 4}
print(b)  # 输出:{1, 2, 3, 4}

5. 元组(tuple)

元组是不可变的序列类型。虽然元组的元素不可变,但元组本身是引用类型。

a = (1, 2, 3)
b = a  # b 引用 a
# 虽然元组是不可变的,但 b 仍然引用 a
print(a)  # 输出:(1, 2, 3)
print(b)  # 输出:(1, 2, 3)

6. 自定义对象(Custom Objects)

所有的类实例都是引用类型。通过类创建的对象在赋值时也是通过引用传递的。

class MyClass:
    def __init__(self, value):
        self.value = value

a = MyClass(10)
b = a  # b 引用 a
b.value = 20
print(a.value)  # 输出:20
print(b.value)  # 输出:20

7. 深拷贝与浅拷贝

浅拷贝(Shallow Copy)

浅拷贝创建一个新的对象,但只复制对象的引用,而不复制嵌套对象。可以使用 copy 模块的 copy() 函数。

import copy

a = [1, 2, [3, 4]]
b = copy.copy(a)
b[2][0] = 5
print(a)  # 输出:[1, 2, [5, 4]]
print(b)  # 输出:[1, 2, [5, 4]]
深拷贝(Deep Copy)

深拷贝创建一个新的对象,并递归地复制所有嵌套对象。可以使用 copy 模块的 deepcopy() 函数。

import copy

a = [1, 2, [3, 4]]
b = copy.deepcopy(a)
b[2][0] = 5
print(a)  # 输出:[1, 2, [3, 4]]
print(b)  # 输出:[1, 2, [5, 4]]

8. 函数参数传递

在Python中,函数参数传递使用的是“共享传递”模型(也称为按对象传递)。这意味着函数参数是通过引用传递的,但对于不可变对象,无法修改其值。

def modify_list(lst):
    lst.append(4)

a = [1, 2, 3]
modify_list(a)
print(a)  # 输出:[1, 2, 3, 4]

def modify_int(n):
    n += 1

b = 5
modify_int(b)
print(b)  # 输出:5

补充——列表生成式

列表生成式(List Comprehensions)是Python中的一种简洁而强大的生成列表的方法,它可以从一个已有的可迭代对象(如列表、元组、字符串等)创建一个新的列表。使用列表生成式可以减少代码的行数,使代码更加简洁和易读。

基本语法

列表生成式的基本语法如下:

[expression for item in iterable if condition]
  • expression:要放入新列表的表达式,可以是对 item 的处理。
  • item:从 iterable(可迭代对象)中依次取出的每个元素。
  • iterable:一个可迭代对象,比如列表、元组、字符串、range对象等。
  • condition(可选):一个可选的过滤条件。如果 condition 为真,才会对 item 进行 expression 处理并放入新列表。

示例

  1. 简单示例:生成平方数列表

    squares = [x * x for x in range(10)]
    print(squares)  # 输出:[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    
  2. 带条件的示例:生成偶数平方列表

    even_squares = [x * x for x in range(10) if x % 2 == 0]
    print(even_squares)  # 输出:[0, 4, 16, 36, 64]
    
  3. 嵌套循环:生成笛卡尔积

    cartesian_product = [(x, y) for x in range(3) for y in range(3)]
    print(cartesian_product)  # 输出:[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
    
  4. 字符串操作:将字符串中的字符转为大写

    original = "hello"
    uppercased = [char.upper() for char in original]
    print(uppercased)  # 输出:['H', 'E', 'L', 'L', 'O']
    

复杂的表达式

列表生成式不仅可以包含简单的表达式,还可以包含复杂的函数调用或其他表达式。例如:

def some_function(x):
    return x * x + 1

complex_list = [some_function(x) for x in range(10)]
print(complex_list)  # 输出:[1, 2, 5, 10, 17, 26, 37, 50, 65, 82]

优点

  • 简洁明了:减少代码行数,使代码更加简洁和易读。
  • 高效:相比传统的循环方式,列表生成式在某些情况下性能更高,因为它是在底层进行优化的。

注意事项

  • 列表生成式适合生成较小的列表,对于非常大的列表,使用生成器表达式(Generator Expressions)会更节省内存。
  • 避免在列表生成式中过度使用复杂的表达式,以保持代码的可读性。

补充——生成器表达式

生成器表达式(Generator Expressions)是Python中用于创建生成器的一种简洁方式。生成器是一种迭代器,可以逐个地产生值,而不是一次性生成整个序列。这使得生成器在处理大型数据集时非常高效,因为它们在需要时才生成数据,不会一次性占用大量内存。

基本语法

生成器表达式的语法与列表生成式非常相似,只是将方括号 [] 替换成圆括号 ()

(expression for item in iterable if condition)
  • expression:要计算的表达式。
  • item:从 iterable(可迭代对象)中依次取出的每个元素。
  • iterable:一个可迭代对象,比如列表、元组、字符串、range对象等。
  • condition(可选):一个可选的过滤条件。如果 condition 为真,才会对 item 进行 expression 处理并生成值。

示例

  1. 简单示例:生成平方数生成器

    squares_gen = (x * x for x in range(10))
    print(squares_gen)  # 输出:<generator object <genexpr> at 0x...>
    
    # 使用生成器
    for square in squares_gen:
        print(square)
    
  2. 带条件的生成器表达式:生成偶数平方生成器

    even_squares_gen = (x * x for x in range(10) if x % 2 == 0)
    
    for square in even_squares_gen:
        print(square)
    
  3. 嵌套生成器表达式:生成笛卡尔积生成器

    cartesian_product_gen = ((x, y) for x in range(3) for y in range(3))
    
    for pair in cartesian_product_gen:
        print(pair)
    
  4. 生成器与函数结合:将生成器作为函数参数

    def sum_of_squares(n):
        return sum(x * x for x in range(n))
    
    print(sum_of_squares(10))  # 输出:285
    

生成器表达式的优点

  • 节省内存:生成器表达式一次只生成一个值,不会将所有值都存储在内存中,适合处理大数据集。
  • 惰性求值:只有在需要时才会计算表达式的值,避免不必要的计算开销。

使用生成器表达式的场景

  1. 处理大数据:在处理大文件或大数据集时,生成器表达式非常有用。例如,逐行读取大文件:

    def read_large_file(file_path):
        with open(file_path, 'r') as file:
            for line in (line.strip() for line in file):
                yield line
    
    for line in read_large_file('large_file.txt'):
        print(line)
    
  2. 无穷序列:生成器可以用于创建无穷序列,比如斐波那契数列:

    def fibonacci():
        a, b = 0, 1
        while True:
            yield a
            a, b = b, a + b
    
    fib_gen = fibonacci()
    for _ in range(10):
        print(next(fib_gen))
    

生成器的常用方法

  • next(generator):获取生成器的下一个值。如果没有更多值,会引发 StopIteration 异常。
  • generator.send(value):向生成器发送一个值,并返回生成器生成的下一个值。
  • generator.close():关闭生成器,停止生成值。

注意事项

  • 生成器只能迭代一次。迭代完毕后,生成器将变得空,无法再生成值。
  • 在需要多次迭代相同数据时,考虑使用列表或其他数据结构。

补充——解包

在Python中,“解包”(Unpacking)是指将一个序列(如列表、元组、字典等)中的元素分解为多个变量的过程。

1. 序列解包

列表和元组解包

可以将列表或元组中的元素直接赋值给多个变量。

# 列表解包
lst = [1, 2, 3]
a, b, c = lst
print(a, b, c)  # 输出:1 2 3

# 元组解包
tup = (4, 5, 6)
x, y, z = tup
print(x, y, z)  # 输出:4 5 6
解包部分元素

可以使用星号 * 来解包部分元素,将剩余的元素放入一个列表中。

numbers = [1, 2, 3, 4, 5]
a, *b, c = numbers
print(a)  # 输出:1
print(b)  # 输出:[2, 3, 4]
print(c)  # 输出:5

2. 字典解包

函数参数解包

在函数调用时,可以使用 ** 运算符将字典解包为关键字参数。

def greet(name, message):
    print(f"{message}, {name}!")

params = {"name": "Alice", "message": "Hello"}
greet(**params)  # 等同于:greet(name="Alice", message="Hello")
字典项解包

可以使用星号 * 来解包字典的键和值。

# 解包字典的键
d = {'a': 1, 'b': 2, 'c': 3}
keys = [*d]
print(keys)  # 输出:['a', 'b', 'c']

# 解包字典的值
values = [*d.values()]
print(values)  # 输出:[1, 2, 3]

# 解包字典的项
items = [*d.items()]
print(items)  # 输出:[('a', 1), ('b', 2), ('c', 3)]

3. 在函数定义中使用解包

*args**kwargs

在函数定义时,可以使用 *args**kwargs 来接收任意数量的位置参数和关键字参数。

def func(*args, **kwargs):
    print("args:", args)
    print("kwargs:", kwargs)

func(1, 2, 3, a=4, b=5)
# 输出:
# args: (1, 2, 3)
# kwargs: {'a': 4, 'b': 5}

4. 返回多个值

函数可以返回多个值,这些值会被打包成一个元组,可以在调用函数时解包这些返回值。

def get_coordinates():
    return (1, 2)

x, y = get_coordinates()
print(x, y)  # 输出:1 2

5. 嵌套解包

对于嵌套的序列结构,可以使用嵌套的解包。

data = ("Alice", (30, "New York"))
name, (age, city) = data
print(name)  # 输出:Alice
print(age)  # 输出:30
print(city)  # 输出:New York

6. 交换变量值

解包可以方便地交换变量的值,而不需要借助临时变量。

a = 1
b = 2
a, b = b, a
print(a, b)  # 输出:2 1

补充——GC

Python的垃圾回收机制(Garbage Collection, GC)用于自动管理内存,通过释放不再使用的对象来避免内存泄漏。Python的垃圾回收机制主要依赖于引用计数和循环垃圾收集(Cycle Garbage Collection)。

1. 引用计数(Reference Counting)

基本原理

每个对象都有一个引用计数器,记录了引用该对象的数量。当对象的引用计数为零时,表明没有任何变量引用该对象,此时可以释放该对象的内存。

引用计数的增加
  • 创建对象时。
  • 赋值给变量时。
  • 作为参数传递给函数时。
  • 被包含在容器(如列表、元组、字典等)中时。
a = [1, 2, 3]  # 引用计数 +1
b = a          # 引用计数 +1
c = a          # 引用计数 +1
引用计数的减少
  • 变量被重新赋值时。
  • 变量被删除时。
  • 变量超出作用域时。
  • 容器对象被销毁或从容器中移除对象时。
a = [1, 2, 3]  # 引用计数 = 1
b = a          # 引用计数 = 2
del a          # 引用计数 = 1
b = None       # 引用计数 = 0,对象被销毁

2. 循环垃圾收集(Cycle Garbage Collection)

基本原理

引用计数机制无法处理循环引用的情况。例如,两个对象相互引用对方,导致它们的引用计数永远不为零。为了处理这种情况,Python提供了循环垃圾收集机制。

循环引用示例
class Node:
    def __init__(self, value):
        self.value = value
        self.next = None

a = Node(1)
b = Node(2)
a.next = b
b.next = a  # 形成循环引用
垃圾收集器

Python的垃圾收集器能够检测和清理循环引用。它会定期扫描对象图,查找不可达的对象并释放它们的内存。

import gc

gc.collect()  # 手动触发垃圾收集

3. gc 模块

Python提供了 gc 模块来控制垃圾回收机制。可以使用 gc 模块手动触发垃圾收集,调整垃圾收集的参数,或禁用垃圾收集。

常用函数
  • gc.collect(): 手动触发垃圾收集。
  • gc.get_count(): 获取当前引用计数。
  • gc.get_threshold(): 获取垃圾收集器的阈值。
  • gc.set_threshold(threshold0, threshold1, threshold2): 设置垃圾收集器的阈值。
  • gc.disable(): 禁用垃圾收集。
  • gc.enable(): 启用垃圾收集。
  • gc.isenabled(): 检查垃圾收集是否启用。
import gc

print(gc.get_count())  # 输出当前引用计数
gc.collect()  # 手动触发垃圾收集
gc.set_threshold(700, 10, 10)  # 设置垃圾收集器的阈值
gc.disable()  # 禁用垃圾收集
gc.enable()  # 启用垃圾收集
print(gc.isenabled())  # 输出:True

4. 内存管理优化

避免循环引用

尽量避免使用循环引用。如果不可避免,可以使用 weakref 模块创建弱引用(Weak Reference),它不增加引用计数。

import weakref

class Node:
    def __init__(self, value):
        self.value = value
        self.next = None

a = Node(1)
b = Node(2)
a.next = weakref.ref(b)  # 使用弱引用避免循环引用
b.next = weakref.ref(a)
使用上下文管理器

使用上下文管理器可以确保及时释放资源。

with open('file.txt', 'r') as file:
    data = file.read()
  • 12
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值