2020-11-27

变量不是盒子

a = [1,2,3]
b = a
a.append(4)
print(b) # [1, 2, 3, 4]

如果把变量想象为盒子,那么无法解释Python中的赋值;应该把变量视作便利贴,上述的例子就可以理解了。
变量不是盒子
创建了对象之后,才会把变量分给对象

class Gizmo:
    def __init__(self):
        print('Gizmo id:'+str(id(self)))

x = Gizmo() # Gizmo id:2818379134344
y = Gizmo()*10  # 在乘法运算中使用Gizmo实例会抛出异常。
# Gizmo id:1795695359368  # 这里表明,在尝试求积之前其实会创建一个新的Gizmo实例。
# Traceback (most recent call last):
#   File "D:/GZRobort/General_DBAI/test.py", line 21, in <module>
#     y = Gizmo()*10
TypeError: unsupported operand type(s) for *: 'Gizmo' and 'int'
print(dir())  # ['Gizmo', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'x']; 但是,肯定不会创建变量y,因为在对赋值语句的右边进行求值时抛出了异常。

为了理解Python中的赋值语句,应该始终先读右边。对象在右边创建或获取,在此之后左边的变量才会绑定到对象上,这就像为对象贴上标注。因为变量只不过是标注,所以无法阻止为对象贴上多个标注。贴的多个标注,就是别名

标识、相等性和别名

charles = {'name':'Charles','born':1832}
lewis = charles  # lewis是charles的别名
print(lewis is charles)
print(id(lewis), id(charles))  # is运算符和id函数确认了这一点
lewis['balence'] = 950  # 向lewis中添加一个元素相当于向charles中添加一个元素
print(charles)

alex = charles = {'name':'Charles','born':1832,'balence':950}  # alex指代的对象与赋值给charles的对象内容一样
print(alex == charles)  # 比较两个对象,结果相等,这是因为dict类的__eq__方法就是这样实现的
print(alex is not charles)  # 但它们是不同的对象。这是Python说明标识不同的方式:a is not b
输出:
True
2844253519160 2844253519160
{'name': 'Charles', 'born': 1832, 'balence': 950}

True
False

在这段代码中,lewis和charles是别名,即两个变量绑定同一个对象。而alex不是charles的别名,因为二者绑定的是不同的对象。alex和charles绑定的对象具有相同的值(==比较的就是值),但是它们的标识不同。
每个变量都有标识、类型和值。对象一旦创建,它的标识绝不会变;你可以把标识理解为对象在内存中的地址。is运算符比较两个对象的标识;id( )函数返回对象标识的整数表示。

在==和is之间选择

运算符比较两个对象的值(对象中保存的数据),而is比较对象的标识。
is运算符比速度快,因为它不能重载,所以Python不用寻找并调用特殊方法,而是直接比较两个整数ID。

元组的相对不可变性

元组与多数Python集合(列表、字典、集,等等)一样,保存的是对象的引用。如果引用的元素是可变的,即便元组本身不可变,元素依然可变。元组的值会随着引用的可变对象的变化而变。元组中不可变的是元素的标识。

t1 = (1,2,[30,40]) # t1不可变,但是t1[-1]可变
t2 = (1,2,[30,40]) # 构建元组t2,它的元素与t1一样
print(t1 == t2)  # 虽然t1和t2是不同的对象,但是二者相等——与预期相符
print(id(t1[-1])) # 查看t1[-1]列表的标识
t1[-1].append(99) # 就地修改t1[-1]列表
print(t1)
print(id(t1[-1])) # t1[-1]的标识没变,只是值变了
print(t1 == t2) # t1和t2不相等
输出:
True
2676098823304
(1, 2, [30, 40, 99])
2676098823304
False

默认做浅复制

t1 = [3,[55,44],(7,8,9)]
t2 = list(t1) # list(l1)创建l1的副本
print(t2)
print(t2 == t1) # 副本与源列表相等
print(t2 is t1) # 但是二者指代不同的对象。对列表和其他可变序列来说,还能使用简洁的l2=l1[:]语句创建副本

t1.append(100) # 把100追加到t1中,对t2没有影响
t1[1].remove(55) # 把内部列表t1[1]中的55删除。这对t2有影响,因为t2[1]绑定的列表与t1[1]是同一个
print('t1:',t1)
print('t2:',t2)

t2[1]+= [33,22] # 对可变的对象来说,如t2[1]引用的列表,+=运算符就地修改列表。这次修改在t1[1]中也有体现,因为它是t2[1]的别名
t2[2]+= (10,11) # 对元组来说,+=运算符创建一个新元组,然后重新绑定给变量t2[2]。这等同于t2[2]=t2[2]+(10, 11)。现在,t1和t2中最后位置上的元组不是同一个对象
print('t1:',t1)
print('t2:',t2)
输出:
[3, [55, 44], (7, 8, 9)]
True
False

t1: [3, [44], (7, 8, 9), 100]
t2: [3, [44], (7, 8, 9)]

t1: [3, [44, 33, 22], (7, 8, 9), 100]
t2: [3, [44, 33, 22], (7, 8, 9, 10, 11)]

构造方法或[:]做的是浅复制(即复制了最外层容器,副本中的元素是源容器中元素的引用)。如果所有元素都是不可变的,那么这样没有问题,还能节省内存。但是,如果有可变的元素,可能就会导致意想不到的问题。

为任意对象做深复制和浅复制

浅复制没什么问题,但有时我们需要的是深复制(即副本不共享内部对象的引用)。copy模块提供的deepcopy和copy函数能为任意对象做深复制和浅复制。

函数的参数作为引用时

共享传参指函数的各个形式参数获得实参中各个引用的副本。也就是说,函数内部的形参是实参的别名。结果是,函数可能会修改作为参数传入的可变对象,但是无法修改那些对象的标识(即不能把一个对象替换成另一个对象)。

def f(a,b):
    a+= b
    return a

x,y = 1,2
print(f(x,y))
print(x,y)

a,b = [1,2],[3,4]
print(f(a,b))
print(a,b)

t,u = (10,20),(30,40)
print(f(t,u))
print(t,u)
输出:
3
1 2

[1, 2, 3, 4]
[1, 2, 3, 4] [3, 4]

(10, 20, 30, 40)
(10, 20) (30, 40)

不要使用可变类型作为参数的默认值

防御可变参数

class TwilightBus:
    '''让乘客销声匿迹的校车'''
    def __init__(self,passengers = None):
        if passengers is None:
            self.passengers = []
        else:
            self.passengers = passengers
    def pick(self,name):
        self.passengers.append(name)
    def drop(self,name):
        self.passengers.remove(name)

basketball_team = ['S','T','M','D','P']
bus = TwilightBus(basketball_team)
bus.drop('S')
bus.drop('T')
print(basketball_team)  # ['M', 'D', 'P']  ## 下车的学生从篮球队中消失了
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值