网站404怎么解决seo站外优化最主要的是什么
Python实例化中默认值的行为及应用
适合初学者阅读
本文要点
- 使用可变对象作为默认参数会导致所有实例共享同一对象,引发意外的数据修改。
- 不可变对象作为默认参数时,每次实例化都会创建新的对象,不会共享数据。
- 推荐使用
None
作为默认值,并在__init__
方法中进行适当的初始化以避免共享问题。 - Python中的所有数据都是通过引用来访问的对象,理解这一点有助于正确处理默认参数。
引言
在Python中,理解类构造函数(__init__
方法)中的默认参数行为对于编写高效且无误的代码至关重要。本文将通过一个具体的例子来探讨当使用可变对象(如列表)作为默认参数时可能出现的问题,并提供解决这些问题的最佳实践。
问题描述
考虑以下Python代码:
class class_A:def __init__(self, name: str, a_default_list_value=[], a_default_none_value=None, a_default_str_value="~"):self.name = nameself.value_1 = a_default_list_valueself.value_2 = a_default_none_valueself.value_3 = a_default_str_valueif __name__ == "__main__":object_1 = class_A("object_1")object_2 = class_A("object_2")print(id(object_1), ": ", object_1.name, " ", id(object_1.value_1), ": ", repr(object_1.value_1), " ", id(object_1.value_2), ": ", repr(object_1.value_2), " ", id(object_1.value_3), ": ", repr(object_1.value_3))print(id(object_2), ": ", object_2.name, " ", id(object_2.value_1), ": ", repr(object_2.value_1), " ", id(object_2.value_2), ": ", repr(object_2.value_2), " ", id(object_2.value_3), ": ", repr(object_2.value_3))object_2.value_3 = "!"print(id(object_2.value_3), ": ",object_2.value_3)print(id(object_1.value_3), ": ",object_1.value_3)print(id("!"), ": ", "!")# 复杂数据结构object_2.value_2 = []print(id(object_2.value_2), ": ",object_2.value_2)print(id(object_1.value_2), ": ",object_1.value_2)object_1.value_2 = object_2.value_2object_2.value_2.append('...')print(id(object_2.value_2), ": ",object_2.value_2)print(id(object_1.value_2), ": ",object_1.value_2)object_2.value_1.append('._.')print(id(object_2.value_1), ": ",object_2.value_1)print(id(object_1.value_1), ": ",object_1.value_1)object_3 = class_A("object_3")print(id(object_3), ": ", object_3.name, " ", id(object_3.value_1), ": ", repr(object_3.value_1), " ", id(object_3.value_2), ": ", repr(object_3.value_2), " ", id(object_3.value_3), ": ", repr(object_3.value_3))
输出会是这样:
1308360223984 : object_1 1308367676864 : [] 140718652536784 : None 140718653645744 : '~'
1308367549648 : object_2 1308367676864 : [] 140718652536784 : None 140718653645744 : '~'
140718653641280 : !
140718653645744 : ~
140718653641280 : !
1308367714432 : []
140718652536784 : None
1308367714432 : ['...']
1308367714432 : ['...']
1308367676864 : ['._.']
1308367676864 : ['._.']
1308367547248 : object_3 1308367676864 : ['._.'] 140718652536784 : None 140718653645744 : '~'
-
从
value_3
可以看到:Python是将字符(串,Python中没有单字符)的值赋值给了
value3
(储存于value3
地址所指向的内存空间)。之前好像讲过,python的字符赋值方法,算是一种节约空间的办法。没记错的话数字(int)也是这样(这题贴个图)
-
从
value_2
和value_3
可以看到:最开始的None是指向同一个数据(地址)的,
object_2.value_2
指向的空列表被赋值给了object_1.value_2
后指向同一数据(地址),说明列表是引用类型(类比C语言)。那么这么看来,好像 python中包括数字、字符串等都是“引用类型”的数据。
-
从
value_1
可以看到:对
object_2.value_1
的修改也是对a_default_list_value
进行修改。所有实例共享同一个默认列表。修改其中一个实例的value_1
会影响到其他实例。
这就有问题了:object_3
的value_1
在生成时就被修改了。在实例上的修改会影响定义中的值,也就是定义的class_A
中的a_default_list_value
所指向的默认空列表被修改。
为什么会出现这样的情况?
在Python中,所有的数据都是对象,而这些对象是通过引用来访问的。不可变类型(如整数、字符串)在赋值时不会改变原始对象,而是创建新的对象;而对于可变类型(如列表、字典),我们可以通过引用直接修改其内容。因此,当使用可变对象作为默认参数时,如果该对象被修改,则所有使用该默认值的实例都会受到影响。
a_default_list_value
所指向的“列表”就相当于是全局变量,唯一。也可以说是class_A
中的全局变量(类变量):
class class_A:a_default_list_value_ = []def __init__(self, name: str, a_default_list_value=a_default_list_value_, a_default_none_value=None, a_default_str_value="~"):self.name = nameself.value_1 = a_default_list_valueself.value_2 = a_default_none_valueself.value_3 = a_default_str_value
最佳实践
为了避免上述问题,应该避免使用可变对象作为默认参数。推荐的做法是在__init__
方法内部进行适当的初始化。例如:
class ClassA:def __init__(self, name: str, a_default_list_value=None):self.name = nameif a_default_list_value is None:a_default_list_value = []self.value_1 = a_default_list_value
这样可以确保每个实例都有自己的独立副本,从而避免意外的数据共享。
应用场景
- 配置项:在某些情况下,可能希望多个实例共享某些配置信息。这时可以利用类变量或全局变量来实现。
- 缓存:利用共享的可变对象作为缓存,可以在不同实例间共享计算结果,提高性能。
结论
了解Python中对象引用的工作机制对于正确地使用默认参数非常重要。遵循最佳实践可以帮助开发者避免潜在的错误,并编写出更加健壮和高效的代码。