【语法篇】函数的参数调用文章一览 变量的别名 变量的作用域 不可变对象与可变对象 函数会对实参造成影响 避免用可变对象作
文章一览
Python的参数传递采用的是共享传参
,也就是形参获取实参引用的副本,换句话说,形参是实参的一个别名。
变量的别名
别名:是指多个变量指向同一个对象,其他变量就是这个变量的别名
a = [1, 2]
b = a
print(id(a), id(b))
print(a is b)
2595122111744 2595122111744
True
此时,变量a和变量b都是指向[1, 2]
这个列表对象的,只是变量名称不一样,我们可以说变量b是变量a的别名
,也可以说变量a是变量b的别名
。
变量的作用域
在参数传递过程中,我们需要先了解一下作用域的问题,Python关于作用域分为两个部分,全局作用域
和局部作用域
。
x = 10 # 全局作用域
def run():
y = 20
print(y) # 局部作用域
print(x)
run()
print(x)
print(y)
20
10
10
NameError: name 'y' is not defined
变量x
的作用域为全局
,也就是在任何地方都可以使用,变量y
的作用域是局部
的,也就是只能在run
函数内部去使用,当函数执行完毕之后,该变量也就被自动清理掉。
x = 10
def run():
x = 20
print(x)
run()
print(x)
20
10
通过这张图片,我们可以看到,虽然外部和函数内部的
变量名称
相同,但是实际上绑定的是两个不同的对象,而内部
的变量只有在进入到run
函数内部时才可以使用,其他时候是没有这个变量的。
不可变对象与可变对象
不可变对象
什么是不可变对象?不可变对象时对象中不提供增加
和删除
功能的对象,最为常见的就是元组
了。
t = (1, 2, 3)
t += (4, 5, 6)
print(t)
(1, 2, 3, 4, 5, 6)
从这段代码的运行上看,元组明显是被改变了,为什么说元组不可变的呢?
所谓的不可变,是指变量名绑定的对象不可变,(1, 2, 3)这个对象是不可变的
t += (4, 5, 6)
这个过程中进行了三步操作。
- 通过t找到对象(1, 2, 3)
- 将对象(1, 2, 3)和对象(4, 5, 6)合并生成一个新的对象(1, 2, 3, 4, 5, 6)
- 将变量t重新指向新的对象(1, 2, 3, 4, 5, 6)
t = (1, 2, 3)
print("原始对象t:", id(t))
t += (4, 5, 6)
print("改变后的对象t:", id(t))
原始对象t: 2595145602048
改变后的对象t: 2595124188000
可变对象
常见的可变对象就是列表
了,它提供了元素的添加
和删除
。
t = [1, 2, 3]
print("原始对象t:", id(t))
t += [4, 5, 6]
print("改变后的对象t:", id(t))
原始对象t: 2595145719296
改变后的对象t: 2595145719296
可以看到,当可变对象添加元素时,对象并不会发生改变(变量指向的仍然是原始对象,没有新的对象产生)。
可变与不可变?
可变
是指对象内的元素是可以被改变的,元素
的改变并不会造成对象
的改变。不可变
是指对象一旦创建,里面的元素是不能被改变的,一旦改变了元素
,原始对象
也就不存在了,取而代之的是一个新的对象
。
函数会对实参造成影响
函数可能会修改作为参数传入的可变对象,但是无法修改那些对象的标识(也就是说,不能把一个对象彻底替换成另一个对象)。
所以,当我们的实参是一个不可变
对象时,我们是完全可以放心的。
def add(a, b):
a += b
return a
A = (1, 2, 3)
B = (4, 5, 6)
C = add(A, B)
print("A:", A)
print("B:", B)
print("C:", C)
print("对象A:", id(A))
print("对象B:", id(B))
print("对象C:", id(C))
A: (1, 2, 3)
B: (4, 5, 6)
C: (1, 2, 3, 4, 5, 6)
对象A: 2595145525632
对象B: 2595145602624
对象C: 2595124188000
通过这段代码我们可以看到,当传入的对象是不可变对象时,在函数内部无论做了什么操作
,都无法影响到外部的对象的。
但是,我们要提防实参是可变对象时的情况
。
def add(a, b):
a += b
return a
A = [1, 2, 3]
B = [4, 5, 6]
C = add(A, B)
print("A:", A)
print("B:", B)
print("C:", C)
print("对象A:", id(A))
print("对象B:", id(B))
print("对象C:", id(C))
A: [1, 2, 3, 4, 5, 6]
B: [4, 5, 6]
C: [1, 2, 3, 4, 5, 6]
对象A: 2595145682240
对象B: 2595145292928
对象C: 2595145682240
出现这种问题的原因就在于,形参是实参的别名
,也就是说形参与实参实际上指向的是同一个对象,当实参是不可变
对象时,我们可以放心的,因为不可变对象
是无法被改变的,而当实参是可变对象
时,我们需要提防对这个形参
的一些改变,因为会影响到外部。
避免用可变对象作为参数默认值
class Bus:
def __init__(self, students=[]):
self.students = students
def drive(self, student):
self.students.append(student)
def __str__(self):
return f"Student{self.students}"
B = Bus()
B.drive("张三")
B1 = Bus()
print(B1)
B1.drive("李四")
print(B)
Student['张三']
Student['张三', '李四']
可以看到,这段代码运行的逻辑是混乱的,明明B校车
里面只有张三
这个学生,B1
校车里面只有李四
这个学生,但是结果却和预期大相径庭
。
接下来我们来分析一下这个问题产生的原因
class Bus:
def __init__(self, students=[]):
print(id(students))
self.students = students
def drive(self, student):
self.students.append(student)
def __str__(self):
return f"Student{self.students}"
B = Bus()
B1 = Bus()
通过这张图,我们可以看到,
B
和B1
的students
属性指向的是同一个对象,至于为什么发生这样的情况,跟我们的默认值设计有关系,默认值会自动在内存中创建一个对象,这个对象是固定的
,所以,只要用到默认值的内容,那么用到的一定是同一个对象,由于又是可变类型,所以就会导致,当某个实例改变这个对象时,其他实例也会受到影响。
class Bus:
def __init__(self, students=()):
print(id(students))
self.students = students
def drive(self, student):
self.students.append(student)
def __str__(self):
return f"Student{self.students}"
B = Bus(["张三"])
B1 = Bus(["李四"])
B.drive("王五")
print(B)
print(B1)
2595145508928
2595123433280
Student['张三', '王五']
Student['李四']
当我们不使用默认值的时候,情况是没有问题的,但是这样我们需要对每一个使用者,亲自告诉他们,一定要传入参数,否则就无法保障实例的正常
。所以这样的做法并不是最优解,当我们在需求中一定要用到可变类型时,最好的方法如下:
class Bus:
def __init__(self, students=None):
if students is None:
self.students = []
else:
self.students = students
def drive(self, student):
self.students.append(student)
def __str__(self):
return f"Student{self.students}"
B = Bus()
B1 = Bus()
B.drive("王五")
print(B)
print(B1)
Student['王五']
Student[]
转载自:https://juejin.cn/post/7409148560777969715