python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python赋值传递深浅拷贝

一文详解Python赋值传递中的深浅拷贝问题

作者:看风起长林

还在为Python赋值、浅拷贝和深拷贝的区别头疼吗,本文用通俗例子讲透三者的核心差异,帮你理解可变对象共享引用的坑,掌握copy.copy和copy.deepcopy的适用场景,从此轻松应对面试和开发中的数据复制难题,需要的朋友可以参考下

一、开篇:赋值、浅拷贝、深拷贝——三个容易混淆的概念

上一篇文章我们搞懂了可变类型与不可变类型。但那只是故事的一半——知道"可变对象会被共享修改"还不够,你还得知道如何正确地"复制"数据,在需要的时候切断这种共享。

来看一个在实际项目中经常遇到的场景:

# 你有一个模板配置
template_config = {
    'host': 'localhost',
    'port': 8080,
    'database': {
        'host': 'db.local',
        'port': 5432,
        'pool': {'min': 1, 'max': 10}
    },
    'cache_servers': ['redis://cache1:6379', 'redis://cache2:6379']
}

# 你需要为三个不同的环境创建配置
dev_config = template_config   # 直接赋值——对吗?
dev_config['host'] = 'dev.server.com'
dev_config['database']['host'] = 'dev-db.local'

print(template_config['host'])           # dev.server.com —— 被修改了!
print(template_config['database']['host'])  # dev-db.local —— 也被修改了!

上面的输出你能准确预测吗?如果你脱口而出"还是原来的值",那就中招了。直接赋值没有创建任何副本——dev_config和template_config指向的是同一个字典对象。

今天我们要彻底搞懂Python中的三种"数据传递"方式:赋值(Assignment)、浅拷贝(Shallow Copy)和深拷贝(Deep Copy)。这三者之间的区别,是Python面试中最高频的考点之一,也是实际开发中最容易踩坑的地方。

二、基础:变量不是盒子,是标签

2.1 重新理解赋值

在深入拷贝之前,必须先建立正确的Python变量模型:

# Python的变量不是"装着数据的盒子"
# 而是"贴在对象上的标签"

# 赋值 = 贴标签,不创建新对象
a = [1, 2, 3]
b = a        # 只是给同一个列表多贴了一个标签
print(f'a is b: {a is b}')  # True——同一个对象
print(f'id(a): {id(a)}, id(b): {id(b)}')  # 相同

# 修改a就修改了b(因为它们指向同一个对象)
a.append(4)
print(f'a: {a}')  # [1, 2, 3, 4]
print(f'b: {b}')  # [1, 2, 3, 4]

# 图示:
# a = [1,2,3]  :  a → [1, 2, 3]
# b = a        :  a → [1, 2, 3] ← b
# a.append(4)  :  a → [1, 2, 3, 4] ← b

2.2 对不可变类型,赋值是"安全"的

# 不可变类型没有"原地修改"的问题
x = 100
y = x        # y指向同一个100对象
x = 200      # x指向新的200对象,y还指向100
print(f'x: {x}, y: {y}')  # x: 200, y: 100

# 初始时 x → 100 ← y
# 重新赋值后 x → 200   100 ← y
# 没有"共享修改"的问题,因为根本没修改——只是让x指向了新对象

这引出了一个关键观点:对于不可变类型,赋值通常就是"够用的"。浅拷贝和深拷贝主要是为了解决可变类型的共享引用问题。

三、浅拷贝(Shallow Copy)

3.1 什么是浅拷贝

浅拷贝创建一个新的容器对象,但容器中的元素仍然是对原对象元素的引用。

用人话说:你复制了一个列表的"壳",但壳里面的东西还是原来的。

import copy

# 原列表
original = [1, 2, [10, 20]]
print(f'原始: {original}')

# 浅拷贝
shallow = copy.copy(original)
print(f'浅拷贝: {shallow}')
print(f'是同一个对象吗: {original is shallow}')  # False——容器本身是新的

# 但是!嵌套的可变元素还是共享的
print(f'内部列表是同一个吗: {original[2] is shallow[2]}')  # True!

# 图解:
# original  → [1, 2, ●]——→ [10, 20]
# shallow   → [1, 2, ●]——→ 同上(同一个[10, 20])

# 所以修改嵌套元素会同时影响两者
original[2].append(30)
print(f'原始: {original}')   # [1, 2, [10, 20, 30]]
print(f'浅拷贝: {shallow}')  # [1, 2, [10, 20, 30]]——也被影响了!

# 但是修改顶层元素不会相互影响
original[0] = 999
print(f'原始: {original}')   # [999, 2, [10, 20, 30]]
print(f'浅拷贝: {shallow}')  # [1, 2, [10, 20, 30]]——不受影响

3.2 创建浅拷贝的N种方式

import copy

original = [1, 2, 3, 4, 5]

# 方式一:copy.copy()——最显式
shallow1 = copy.copy(original)

# 方式二:list()构造函数
shallow2 = list(original)

# 方式三:切片 [:]
shallow3 = original[:]

# 方式四:copy()方法(列表、字典、集合都支持)
shallow4 = original.copy()

# 方式五:列表推导式
shallow5 = [x for x in original]

# 验证都是浅拷贝
for name, s in [('copy.copy', shallow1), ('list()', shallow2),
                ('[:]', shallow3), ('.copy()', shallow4), ('推导式', shallow5)]:
    print(f'{name}: 不同对象={original is not s}, 元素相同={original == s}')

# 注意:copy.copy()是通用方法,而.copy()是类型的实例方法
# 对于不知道类型的情况(如函数参数),使用copy.copy()更稳妥

3.3 字典和集合的浅拷贝

import copy

# 字典的浅拷贝
original_dict = {
    'name': '小明',
    'scores': [85, 92, 78],  # 可变元素
    'profile': {'age': 25, 'city': '北京'}  # 嵌套可变元素
}

# 创建浅拷贝的各种方式
d1 = original_dict.copy()       # dict的copy方法
d2 = dict(original_dict)        # dict()构造函数
d3 = {**original_dict}          # 字典解包(Python 3.5+)
d4 = copy.copy(original_dict)   # copy模块

# 验证:容器是新的,嵌套可变元素是共享的
print(f'd1是新的: {d1 is not original_dict}')
print(f'scores是共享的: {d1["scores"] is original_dict["scores"]}')
print(f'profile是共享的: {d1["profile"] is original_dict["profile"]}')

# 修改嵌套元素——会同时影响
original_dict['scores'].append(100)
print(f'原始scores: {original_dict["scores"]}')  # [85, 92, 78, 100]
print(f'拷贝scores: {d1["scores"]}')              # [85, 92, 78, 100]——也变了!


# 集合的浅拷贝
original_set = {1, 2, frozenset([3, 4])}
s1 = original_set.copy()       # set的copy方法
s2 = set(original_set)         # set()构造函数
s3 = copy.copy(original_set)   # copy模块

print(f'\ns1是新的: {s1 is not original_set}')  # True
print(f's1 == original_set: {s1 == original_set}')  # True

3.4 浅拷贝的"浅"到底体现在哪里

# 用一个三层嵌套结构来直观展示
original = [
    [1, 2],          # 索引0:第一层嵌套
    [3, [4, 5]],     # 索引1:第二层嵌套
    {'a': [6, 7]}   # 索引2:第二层嵌套(字典里的列表)
]

import copy
shallow = copy.copy(original)

# 第一层:容器本身——不共享
print(f'顶层不同对象: {original is not shallow}')  # True

# 第二层:第一层的元素——共享!
print(f'original[0]共享: {original[0] is shallow[0]}')  # True
print(f'original[1]共享: {original[1] is shallow[1]}')  # True
print(f'original[2]共享: {original[2] is shallow[2]}')  # True

# 第三层:第二层中的元素——也是共享的
print(f'original[1][1]共享: {original[1][1] is shallow[1][1]}')  # True
print(f'original[2]["a"]共享: {original[2]["a"] is shallow[2]["a"]}')  # True

# 修改影响范围示例
# 修改第一层——互不影响
original[0] = 'changed'
print(f'改顶层后: original[0]={"changed"}, shallow[0]={shallow[0]}')
# 原来的[1,2]还在shallow[0]中

# 修改第二层——互相影响!
original[1].append(999)
print(f'改第二层后: original[1]={original[1]}, shallow[1]={shallow[1]}')
# 两边都看到了999

# ⚠️ 关键结论:浅拷贝只拷贝一层
# "一层"指的是容器本身这一层
# 容器内的所有元素(不管嵌套多少层)都还是共享的

四、深拷贝(Deep Copy)

4.1 什么是深拷贝

深拷贝递归地复制所有层级的对象。原对象和拷贝对象之间没有任何共享的子对象。

import copy

original = [1, 2, [10, 20, [100, 200]]]
deep = copy.deepcopy(original)

# 验证:每一层都是独立的新对象
print(f'顶层不同: {original is not deep}')           # True
print(f'original[2]不同: {original[2] is not deep[2]}')  # True
print(f'original[2][2]不同: {original[2][2] is not deep[2][2]}')  # True

# 现在怎么修改都不会影响对方
original[2].append(30)
original[2][2].append(300)
print(f'修改后 original: {original}')   # [1, 2, [10, 20, [100, 200, 300], 30]]
print(f'修改后 deep:     {deep}')       # [1, 2, [10, 20, [100, 200]]]——完全不受影响

# 图解:
# original → [1, 2, ●] → [10, 20, ●] → [100, 200]
# deep     → [1, 2, ▲] → [10, 20, ▲] → [100, 200]
# ●和▲指向不同的对象——每一层都是独立的副本

4.2 深拷贝的递归行为

import copy

# 复杂嵌套结构
class Person:
    def __init__(self, name, friends=None):
        self.name = name
        self.friends = friends if friends is not None else []
    
    def __repr__(self):
        return f'Person({self.name}, friends={[f.name for f in self.friends]})'


# 构建一个小的社交网络
alice = Person('Alice')
bob = Person('Bob')
charlie = Person('Charlie')

alice.friends = [bob, charlie]
bob.friends = [alice]
charlie.friends = [alice, bob]

# 深拷贝Alice
alice_deep = copy.deepcopy(alice)

print('=== 深拷贝后 ===')
print(f'alice is alice_deep: {alice is alice_deep}')  # False
print(f'alice.name: {alice.name}, alice_deep.name: {alice_deep.name}')

# 深拷贝连friends列表中的Person对象也复制了
print(f'alice.friends[0] is alice_deep.friends[0]: '
      f'{alice.friends[0] is alice_deep.friends[0]}')  # False!

# 验证:修改deep拷贝不影响原对象
alice_deep.name = 'Alice_copy'
alice_deep.friends[0].name = 'Bob_copy'
print(f'\nalice.name: {alice.name}')           # Alice——不受影响
print(f'bob.name: {bob.name}')                 # Bob——不受影响
print(f'alice_deep.friends[0].name: {alice_deep.friends[0].name}')  # Bob_copy

4.3 深拷贝处理循环引用

import copy

# 循环引用:a包含b,b包含a
a = []
b = [a]
a.append(b)

# 这形成了一个循环:a → [b] → [a] → [b] → ...
print(f'a: {a}')
# 输出:a: [[[...]]]

# 深拷贝处理循环引用——不会无限递归
a_deep = copy.deepcopy(a)
print(f'a_deep: {a_deep}')

# 拷贝后的循环引用是独立的
print(f'a is a_deep: {a is a_deep}')  # False
# 但拷贝后的结构保持了循环引用
print(f'a_deep[0][0] is a_deep: {a_deep[0][0] is a_deep}')  # True

# 💡 deepcopy内部维护了一个memo字典
# 记录已经拷贝过的对象,遇到循环引用就直接复用
# 这就是为什么deepcopy不会陷入无限递归

五、赋值 vs 浅拷贝 vs 深拷贝——全面对比

5.1 三者的核心区别

import copy

original = [1, 2, [3, 4]]

# 1. 赋值——不同变量,同一对象
assigned = original

# 2. 浅拷贝——不同对象,但共享内部可变元素
shallow = copy.copy(original)  # 或 original.copy()、original[:]

# 3. 深拷贝——完全独立的对象
deep = copy.deepcopy(original)

print('=== 初始状态 ===')
print(f'original: {original}')
print(f'assigned: {assigned}')
print(f'shallow:  {shallow}')
print(f'deep:     {deep}')

print('\n=== 修改 original[0](顶层元素) ===')
original[0] = 999
print(f'original: {original}')   # [999, 2, [3, 4]]
print(f'assigned: {assigned}')   # [999, 2, [3, 4]] ← 受影响!
print(f'shallow:  {shallow}')    # [1, 2, [3, 4]]    ← 不受影响
print(f'deep:     {deep}')       # [1, 2, [3, 4]]    ← 不受影响

print('\n=== 修改 original[2](嵌套可变元素) ===')
original[2].append(5)
print(f'original: {original}')   # [999, 2, [3, 4, 5]]
print(f'assigned: {assigned}')   # [999, 2, [3, 4, 5]] ← 受影响!
print(f'shallow:  {shallow}')    # [1, 2, [3, 4, 5]]   ← 受影响!
print(f'deep:     {deep}')       # [1, 2, [3, 4]]      ← 不受影响!

5.2 一图看懂

# 初始状态
original = [1, 2, [3, 4]]

# 内存模型:
#
# 赋值 (assigned = original):
#   original → [1, 2, ●] → [3, 4]
#   assigned ↗            ↗ (共享一切)
#
# 浅拷贝 (shallow = copy.copy(original)):
#   original → [1, 2, ●] → [3, 4]
#   shallow  → [1, 2, ●] ↗ (共享嵌套)
#
# 深拷贝 (deep = copy.deepcopy(original)):
#   original → [1, 2, ●] → [3, 4]
#   deep     → [1, 2, ▲] → [3, 4] (完全独立)

5.3 速查对比表

操作创建新容器复制嵌套可变对象修改顶层互不影响修改嵌套互不影响
赋值 b = a❌❌❌❌
浅拷贝 copy.copy(a)✅❌✅❌
深拷贝 copy.deepcopy(a)✅✅✅✅

六、各种数据结构的拷贝行为

6.1 列表、元组、字典、集合的拷贝

import copy

# === 列表 ===
lst = [1, [2, 3]]
lst_shallow = lst.copy()       # 浅拷贝
lst_deep = copy.deepcopy(lst)  # 深拷贝

# === 字典 ===
d = {'a': 1, 'b': [2, 3]}
d_shallow = d.copy()           # 浅拷贝
d_deep = copy.deepcopy(d)      # 深拷贝

# === 集合 ===
s = {1, frozenset([2, 3])}
s_shallow = s.copy()           # 浅拷贝
s_deep = copy.deepcopy(s)      # 深拷贝

# === 元组 ===
# ⚠️ 注意:元组是不可变的
t = (1, [2, 3])

# 浅拷贝元组——因为元组不可变,浅拷贝通常返回元组本身
t_shallow = copy.copy(t)
print(f't is t_shallow: {t is t_shallow}')  # True!返回了同一个对象

# 但是深拷贝会创建新元组——因为内部有可变元素需要递归拷贝
t_deep = copy.deepcopy(t)
print(f't is t_deep: {t is t_deep}')  # False
print(f't[1] is t_deep[1]: {t[1] is t_deep[1]}')  # False——内部列表也被拷贝了

6.2 自定义对象的拷贝行为

import copy

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

obj = MyClass([1, 2, 3])
obj_shallow = copy.copy(obj)
obj_deep = copy.deepcopy(obj)

print(f'obj is shallow_copy: {obj is obj_shallow}')  # False——新对象
print(f'obj.value is shallow_copy.value: {obj.value is obj_shallow.value}')  # True——内部属性共享
print(f'obj.value is deep_copy.value: {obj.value is obj_deep.value}')  # False——内部属性独立

# 修改内部属性验证
obj.value.append(999)
print(f'修改后 obj.value: {obj.value}')
print(f'修改后 shallow.value: {obj_shallow.value}')  # 也变了
print(f'修改后 deep.value: {obj_deep.value}')        # 没变

七、掌控拷贝行为:copy__和__deepcopy

7.1 自定义浅拷贝行为

import copy

class Config:
    def __init__(self, data):
        self.data = data
        self.created_at = "2025-01-01"
    
    def __copy__(self):
        """自定义浅拷贝行为"""
        print('  __copy__ 被调用')
        new = Config(self.data.copy())  # 浅拷贝了data
        new.created_at = self.created_at
        return new
    
    def __repr__(self):
        return f'Config(data={self.data}, created_at={self.created_at})'


config = Config({'host': 'localhost', 'port': 8080})
print(f'原始: {config}')

# 浅拷贝
config_copy = copy.copy(config)
print(f'拷贝: {config_copy}')
print(f'是否同一对象: {config is config_copy}')  # False
print(f'data是否共享: {config.data is config_copy.data}')  # False(因为__copy__里做了.copy())

7.2 自定义深拷贝行为

import copy

class DatabaseConnection:
    """模拟一个数据库连接——不能被深拷贝"""
    
    def __init__(self, connection_string):
        self.connection_string = connection_string
        self._connected = True
    
    def __deepcopy__(self, memo):
        """
        自定义深拷贝行为。
        对于数据库连接,我们不想复制连接——而是共享同一个连接。
        """
        print('  __deepcopy__ 被调用——共享连接')
        return self  # 直接返回自身,不创建副本
    
    def __repr__(self):
        return f'DB(conn={self.connection_string[:15]}...)'


class AppConfig:
    def __init__(self, name, db_connection, settings):
        self.name = name
        self.db = db_connection       # 数据库连接(不希望被深拷贝)
        self.settings = settings      # 普通配置(需要被深拷贝)
    
    def __deepcopy__(self, memo):
        """自定义深拷贝——只深拷贝settings,共享db连接"""
        print('  AppConfig.__deepcopy__ 被调用')
        new = AppConfig(
            name=self.name,
            db_connection=self.db,  # 数据库连接——共享!
            settings=copy.deepcopy(self.settings, memo)  # 深拷贝settings
        )
        return new


# 使用示例
db = DatabaseConnection('postgresql://user:pass@localhost:5432/mydb')
config = AppConfig('MyApp', db, {
    'debug': False,
    'cache': {'host': 'redis://localhost', 'port': 6379}
})

print('原始:')
print(f'  {config}')

print('\n深拷贝:')
config_deep = copy.deepcopy(config)
print(f'  {config_deep}')

print(f'\n验证:')
print(f'  config.db is config_deep.db: {config.db is config_deep.db}')  # True——共享
print(f'  config.settings is config_deep.settings: {config.settings is config_deep.settings}')  # False——独立

八、实战场景

8.1 配置模板与环境配置

import copy

class ConfigManager:
    """管理多环境配置——深拷贝的经典应用场景"""
    
    def __init__(self, template):
        self._template = template
    
    def create_config(self, **overrides):
        """基于模板创建新配置,并用overrides覆盖"""
        config = copy.deepcopy(self._template)
        for key, value in overrides.items():
            config[key] = value
        return config


# 模板配置(包含嵌套结构)
template = {
    'host': 'localhost',
    'port': 8080,
    'database': {
        'host': 'db.local',
        'port': 5432,
        'pool': {'min': 1, 'max': 10}
    },
    'cache_servers': ['redis://cache1:6379'],
    'features': {'dark_mode': False, 'beta': False}
}

manager = ConfigManager(template)

# 创建三个环境的配置
dev = manager.create_config(
    host='dev.myapp.com',
    features={'dark_mode': True, 'beta': True}
)

staging = manager.create_config(
    host='staging.myapp.com',
    port=8443,
)

prod = manager.create_config(
    host='myapp.com',
    port=443,
    database={'host': 'prod-db.myapp.com', 'pool': {'min': 5, 'max': 100}},
    cache_servers=['redis://cache1:6379', 'redis://cache2:6379', 'redis://cache3:6379'],
)

# 验证:修改一个环境不影响其他
dev['database']['host'] = 'dev-db.myapp.com'
dev['cache_servers'].append('redis://dev-cache:6379')

print('开发环境 database.host:', dev['database']['host'])
print('生产环境 database.host:', prod['database']['host'])  # 不受影响
print('开发环境 cache_servers:', dev['cache_servers'])
print('模板 cache_servers:', template['cache_servers'])    # 模板也不受影响

8.2 撤销/重做(Undo/Redo)系统

import copy

class UndoableList:
    """带撤销功能的列表——用深拷贝保存状态快照"""
    
    def __init__(self, initial=None):
        self._data = list(initial) if initial else []
        self._undo_stack = []
        self._redo_stack = []
    
    def _save_state(self):
        """保存当前状态到撤销栈"""
        self._undo_stack.append(copy.deepcopy(self._data))
        self._redo_stack.clear()  # 新操作后,重做栈失效
    
    def append(self, item):
        self._save_state()
        self._data.append(item)
    
    def insert(self, index, item):
        self._save_state()
        self._data.insert(index, item)
    
    def remove(self, item):
        self._save_state()
        self._data.remove(item)
    
    def pop(self, index=-1):
        self._save_state()
        return self._data.pop(index)
    
    def undo(self):
        """撤销上一步操作"""
        if not self._undo_stack:
            print('没有可撤销的操作')
            return False
        self._redo_stack.append(copy.deepcopy(self._data))
        self._data = self._undo_stack.pop()
        return True
    
    def redo(self):
        """重做被撤销的操作"""
        if not self._redo_stack:
            print('没有可重做的操作')
            return False
        self._undo_stack.append(copy.deepcopy(self._data))
        self._data = self._redo_stack.pop()
        return True
    
    def __repr__(self):
        return str(self._data)


# 使用示例
ul = UndoableList([1, 2, 3])
print(f'初始: {ul}')

ul.append(4)
print(f'append(4): {ul}')

ul.append(5)
print(f'append(5): {ul}')

ul.undo()
print(f'undo: {ul}')     # [1, 2, 3, 4]——撤销了append(5)

ul.undo()
print(f'undo: {ul}')     # [1, 2, 3]——撤销了append(4)

ul.redo()
print(f'redo: {ul}')     # [1, 2, 3, 4]——重做了append(4)

8.3 游戏存档系统

import copy

class GameState:
    """游戏状态管理——用深拷贝实现存档功能"""
    
    def __init__(self):
        self.player = {
            'name': 'Hero',
            'hp': 100,
            'mp': 50,
            'position': [0, 0],
            'inventory': [
                {'name': 'Sword', 'damage': 10, 'enchantments': ['fire']},
                {'name': 'Potion', 'effect': 'heal', 'amount': 50},
            ],
            'skills': {
                'slash': {'level': 1, 'damage': 25},
                'fireball': {'level': 2, 'damage': 40},
            }
        }
        self.enemies = [
            {'name': 'Goblin', 'hp': 30, 'position': [5, 3]},
            {'name': 'Orc', 'hp': 80, 'position': [10, 8]},
        ]
    
    def save(self):
        """创建当前状态的深拷贝作为存档"""
        return copy.deepcopy({
            'player': self.player,
            'enemies': self.enemies,
        })
    
    def load(self, save_data):
        """从存档恢复状态"""
        self.player = copy.deepcopy(save_data['player'])
        self.enemies = copy.deepcopy(save_data['enemies'])


# 使用示例
game = GameState()
print(f"初始HP: {game.player['hp']}, 位置: {game.player['position']}")

# 保存进度
save_point = game.save()

# 游戏过程中修改状态
game.player['hp'] -= 30
game.player['position'] = [3, 4]
game.player['inventory'].append({'name': 'Shield', 'defense': 15})
game.enemies[0]['hp'] -= 25

print(f"战斗后HP: {game.player['hp']}, 位置: {game.player['position']}")

# 从存档恢复
game.load(save_point)
print(f"读档后HP: {game.player['hp']}, 位置: {game.player['position']}")
# HP恢复到100,位置恢复到[0, 0]

九、切片赋值:一种特殊的拷贝相关操作

# 切片赋值和拷贝有一些微妙的关系

# 替换整个列表内容——不创建新列表对象
original = [1, 2, 3]
print(f'替换前 id: {id(original)}')
original[:] = [4, 5, 6]  # 原地替换内容
print(f'替换后 id: {id(original)}')  # id不变
print(f'内容: {original}')  # [4, 5, 6]

# 对比:original = [4,5,6] 会创建新对象
original2 = [1, 2, 3]
print(f'\n赋值前 id: {id(original2)}')
original2 = [4, 5, 6]  # 指向新列表
print(f'赋值后 id: {id(original2)}')  # id变了

# 这在实际开发中有什么用?
# 当你需要清空或替换一个共享列表的内容时:

def update_in_place(shared_list):
    """修改共享列表的内容而不改变其身份"""
    shared_list[:] = [x * 2 for x in shared_list]

data = [1, 2, 3]
ref_to_data = data  # 另一个引用
update_in_place(data)
print(f'data: {data}')         # [2, 4, 6]
print(f'ref_to_data: {ref_to_data}')  # [2, 4, 6]——能看到修改

# ⚠️ 但如果用 data = [x*2 for x in data]:
data2 = [1, 2, 3]
ref2 = data2
data2 = [x * 2 for x in data2]  # data2指向了新列表!
print(f'data2: {data2}')         # [2, 4, 6]
print(f'ref2: {ref2}')           # [1, 2, 3]——还是旧的!

十、常见陷阱与最佳实践

10.1 陷阱一:* 操作符的浅拷贝特性

# ❌ 经典陷阱:[[]] * n
matrix = [[0] * 3] * 3
print(matrix)  # [[0, 0, 0], [0, 0, 0], [0, 0, 0]]

matrix[0][0] = 999
print(matrix)  # [[999, 0, 0], [999, 0, 0], [999, 0, 0]]
# 三行都变了!因为每一行都是对同一个列表的引用

# ✅ 正确方式
matrix = [[0] * 3 for _ in range(3)]
matrix[0][0] = 999
print(matrix)  # [[999, 0, 0], [0, 0, 0], [0, 0, 0]]——正确

# 同理,字典的dict.fromkeys也有这个问题
# ❌ 所有键共享同一个列表值
d = dict.fromkeys(['a', 'b', 'c'], [])
d['a'].append(1)
print(d)  # {'a': [1], 'b': [1], 'c': [1]}——所有值都变了

# ✅ 用字典推导式
d = {k: [] for k in ['a', 'b', 'c']}
d['a'].append(1)
print(d)  # {'a': [1], 'b': [], 'c': []}——正确

10.2 陷阱二:可变默认参数

# ❌ 经典错误——可变默认参数
class TaskManager:
    def __init__(self, tasks=[]):
        self.tasks = tasks  # 所有实例共享同一个列表!
    
    def add(self, task):
        self.tasks.append(task)

m1 = TaskManager()
m2 = TaskManager()
m1.add('task1')
m2.add('task2')
print(m1.tasks)  # ['task1', 'task2']——m1的数据被污染了!

# ✅ 正确写法
class TaskManagerSafe:
    def __init__(self, tasks=None):
        self.tasks = list(tasks) if tasks is not None else []
    
    def add(self, task):
        self.tasks.append(task)

10.3 最佳实践总结

import copy

# 实践一:函数参数需要防御性拷贝
def process_data(data):
    """如果函数需要修改参数,且调用者不希望参数被修改,做深拷贝"""
    data = copy.deepcopy(data)
    data['processed'] = True
    return data

# 实践二:从方法返回内部可变数据时,返回副本
class Cache:
    def __init__(self):
        self._data = {}
    
    def get_all(self):
        """返回所有缓存数据的副本——防止外部修改内部状态"""
        return copy.deepcopy(self._data)
    
    def get(self, key):
        """返回单个值的副本——如果是可变类型"""
        value = self._data.get(key)
        if isinstance(value, (list, dict, set)):
            return copy.deepcopy(value)
        return value

# 实践三:工厂函数每次都创建新对象
def create_default_config():
    """每次调用都创建全新的配置——避免共享可变默认值"""
    return {
        'host': 'localhost',
        'port': 8080,
        'allowed_origins': [],
        'database': {'host': 'db.local', 'pool': {'min': 1, 'max': 10}}
    }

# 实践四:使用不可变类型避免拷贝的烦恼
# 如果数据不需要修改,直接用不可变类型
CONFIG = (
    ('host', 'localhost'),
    ('port', 8080),
)
# 任何人都无法意外修改

十一、性能考量

import copy
import time

small_list = [1, 2, 3, 4, 5]
large_list = list(range(10000))
nested_list = [list(range(100)) for _ in range(100)]

def benchmark(label, func, data, iterations=10000):
    start = time.perf_counter()
    for _ in range(iterations):
        func(data)
    elapsed = time.perf_counter() - start
    print(f'{label}: {elapsed:.4f}秒 ({iterations}次)')

print('=== 小列表 (5个元素) ===')
benchmark('赋值', lambda x: x, small_list)
benchmark('浅拷贝(copy.copy)', copy.copy, small_list)
benchmark('浅拷贝(list.copy)', list.copy, small_list)
benchmark('浅拷贝([:])', lambda x: x[:], small_list)
benchmark('深拷贝', copy.deepcopy, small_list)

print('\n=== 大列表 (10000个元素) ===')
benchmark('赋值', lambda x: x, large_list)
benchmark('浅拷贝', copy.copy, large_list)
benchmark('深拷贝', copy.deepcopy, large_list)

print('\n=== 嵌套列表 (100个列表,每个100元素) ===')
benchmark('浅拷贝', copy.copy, nested_list)
benchmark('深拷贝', copy.deepcopy, nested_list)

# 💡 性能经验:
# 1. 赋值是最快的(几乎没有开销)
# 2. 浅拷贝开销中等(需要创建新容器和复制引用)
# 3. 深拷贝最慢(需要递归复制所有层级)
# 4. 对于简单数据(没有嵌套可变结构),浅拷贝通常就够用了
# 5. 深拷贝只在确实需要独立嵌套结构时才使用

十二、综合案例:安全缓存系统

import copy
import time
from threading import Lock


class SafeCache:
    """
    一个线程安全、防数据污染的缓存系统。
    
    设计原则:
    1. 内部存储用可变类型(方便管理)
    2. 所有对外暴露的数据都返回深拷贝(防止外部污染)
    3. 所有外部写入的数据都做深拷贝(防止外部引用污染内部)
    """
    
    def __init__(self, max_size=1000, default_ttl=3600):
        self._max_size = max_size
        self._default_ttl = default_ttl
        self._store = {}           # {key: (value, expire_time)}
        self._lock = Lock()
        self._hits = 0
        self._misses = 0
    
    def set(self, key, value, ttl=None):
        """设置缓存值。对value做深拷贝,防止外部后续修改影响缓存。"""
        if ttl is None:
            ttl = self._default_ttl
        
        expire_time = time.time() + ttl if ttl > 0 else float('inf')
        
        with self._lock:
            stored_value = copy.deepcopy(value)
            self._store[key] = (stored_value, expire_time)
            
            if len(self._store) > self._max_size:
                oldest_key = min(self._store.keys(), key=lambda k: self._store[k][1])
                del self._store[oldest_key]
    
    def get(self, key, default=None):
        """获取缓存值。返回深拷贝,防止外部修改影响缓存内部数据。"""
        with self._lock:
            if key in self._store:
                value, expire_time = self._store[key]
                
                if time.time() > expire_time:
                    del self._store[key]
                    self._misses += 1
                    return default
                
                self._hits += 1
                return copy.deepcopy(value)
            else:
                self._misses += 1
                return default
    
    @property
    def stats(self):
        """获取缓存统计信息"""
        with self._lock:
            total = self._hits + self._misses
            hit_rate = self._hits / total if total > 0 else 0
            return {
                'size': len(self._store),
                'hits': self._hits,
                'misses': self._misses,
                'hit_rate': f'{hit_rate:.2%}',
            }


# 使用示例
cache = SafeCache(max_size=5, default_ttl=60)

user_profile = {
    'name': '小明',
    'tags': ['Python', 'Web'],
    'settings': {'theme': 'dark', 'notifications': True}
}

cache.set('user:1', user_profile)

# 外部修改原始数据——不影响缓存
user_profile['tags'].append('数据库')
user_profile['settings']['theme'] = 'light'
print('外部修改后原始数据:', user_profile)
print('缓存中的数据:', cache.get('user:1'))
# 缓存中的数据没变!

# 从缓存获取后修改——也不影响缓存
cached = cache.get('user:1')
cached['tags'].append('HACKED')
cached['name'] = '被篡改的名字'
print('\n获取后尝试污染...')
print('被修改的副本:', cached)
print('缓存中仍然是:', cache.get('user:1'))
# 缓存数据安全!

print('\n缓存统计:', cache.stats)

十三、本篇小结

Python中的三种数据"传递"方式及其核心区别:

赋值(=):

浅拷贝(copy.copy):

深拷贝(copy.deepcopy):

选择建议:

以上就是一文详解Python赋值传递中的深浅拷贝问题的详细内容,更多关于Python赋值传递深浅拷贝的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文