在python中万物皆为对象,包括变量、函数、类以及其实例化的对象。
python中所谓的赋值其实传的是对象指向的引用,也就是说传的是地址。
1. 可变变量和不可变变量
python中存在两大类型的对象,不可变对象(如基本类型中的元组、字符串、数值)和可变对象(如基本类型中的列表、字典、集合)。
对于不可变对象,我们可以理解为存在于连续内存空间的统一整体,其同生共死,不可修改。
而对于可变对象,我们可以理解为其每个元素实质上为对应不可变对象的引用,如果存在可变对象的嵌套,则重复引用直至最深层的不可变对象。 修改可变对象的某个元素,实质上是在内存中创建新的数据,同时将原可变对象对应位置的引用指向该内存。
a = [1, 2]
b = a # 传址,b和a指向同一内存地址
a[0] = 100 # 创建新的内存存放100,同时列表的第一个元素指向新的内存地址。注意:并非将原内存地址上的0修改为100
print(b) # [100,2] ,因为都是地址引用,所以同步更新
a = (1,2, [3,4])
a[2].append(5)
print(a) # (1, 2, [3, 4, 5]), 不可变对象a所指向的内存地址并没有变化
python中,在创建数值和字符串类型变量时,会检测原有内存空间内是否存在该数值/字符串,若存在则会将变量直接指向该内存,否则先创建内存存放数值/字符串,再将变量指向该内存。
a = 1
b = 1
print(id(a)==id(b)) # True
而创建元组、列表、字典、集合时,即使内部元素指向的内存地址完全一样,其数据结构本身也指向不同的引用(本质上是这些数据结构的实例化对象,因此不同)。
a = (1,2,3)
b = (1,2,3)
print(a[0] is b[0]) # True
print(a is b) # False
2. 函数对象
函数作为一种对象,其在生存后期内只会被创建和初始化一次。
def func(*args, **kwargs):
pass
if __name__ == '__main__':
a = func()
b = func()
print(a is b)
函数中的参数,可视为函数对象的属性。其一个潜在风险为默认参数值为可变变量。
def func(ls=[]):
ls.append(1)
print(ls)
if __name__ == '__main__':
print(id(func))
func() # [1]
print(id(func)) # 仍是同一个函数对象
func() # [1, 1]
我们可以这样理解上述过程:
(1)func()只被创建和初始化一次,属性ls指向一个空列表的引用;
(2)第一次func()调用的时,属性ls添加了一个元素1,变为[1];
(3)第二次func调用的时,属性ls再添加了一个元素1,变为[1,1]。
观察下函数对象的__default__
,我们可以明显的观察到属性ls默认值的变化情况:
if __name__ == '__main__':
print(func.__defaults__) # ([],)
func()
print(func.__defaults__) # ([1],)
func()
print(func.__defaults__) # ([1, 1],)
而对于不可变变量,则不会出现这个问题:
def func(ls=()):
ls += (1, ) # 注意此处的ls为函数内部临时变量,而非函数对象的属性,所以属性ls并未发生变化
print(ls)
3. 类及其实例化对象
类对象在生存周期为唯一对象(可视为单例,仅初始化一次),而类的每个实例化对象为不同的对象。
class Cat(object):
def __init__(self, *args, **kwargs):
pass
if __name__ == '__main__':
Cat1 = Cat
Cat2 = Cat
print(Cat1 is Cat2) # True
cat1 = Cat1()
cat2 = Cat2()
print(cat1 is cat2) # False
- 类中的辅助也为地址引用
class Cat(object):
def __init__(self):
self.start = 0
self.end = 0
self.time = (self.start, self.end) # 并不会同步更新
def setStart(self, value):
self.start = value
def setEnd(self, value):
self.end = value
if __name__ == '__main__':
cat = Cat()
cat.setStart(1)
cat.setStart(10)
print(cat.time) # (0, 0)
- 类对象只实例化一次
class Cat(object):
def __init__(self, *args, **kwargs):
x = []
print(x)
x.append(1)
if __name__ == '__main__':
Cat1 = Cat # 只初始化一次,因此只会打印一次 []
Cat2 = Cat
print(Cat2.x) # 只初始化一次,因此为 [1]
对于类属性,同样存在可变变量的问题。
class Cat(object):
x = []
def __init__(self):
self.x.append(1)
print(self.x)
if __name__ == '__main__':
cat1 = Cat() # [1]
cat2 = Cat() # [1,1]
cat3 = Cat() # [1,1,1]
综上,在python的函数定义和类对象定义中,尽量不要使用可变变量,以免发生潜在风险。