1

流畅的python--第五章/第六章

数据类构建器

一个简单的类,表示地理位置的经纬度。
使用nametuple构建Coordinate类。namedtuple是一个工厂方法,使用指定的名称和字段构建tuple的子类。

典型的具名元组

collections.namedtuple是一个工厂函数,用于构建增强的tuple子类,具有字段名称、类名和提供有用的信息的__repr__方法。
namedtuple构建的类可在任何需要元组的地方使用。
示例1:定义一个具名元组,存储一个城市的信息

除了从tuple继承,具名元组还有几个额外的属性和方法。

示例2:构建一个具名元组,为字段指定默认值

@dataclass 示例:都柏林核心模式

实际使用中通常需要更多的字段。根据都柏林核心(Dublin Core)模式,使用 @dataclass 构建一个
更复杂的类。都柏林核心模式是一小组术语,可用于描述数字资源(视频、图
像、网页等),也可用于描述物理资源,例如图书、CD 和艺术品
等对象。

关键字模式

import typing
class City(typing.NamedTuple):
    continent: str
    name: str
    country: str
cities = [
City('Asia', 'Tokyo', 'JP'),
City('Asia', 'Delhi', 'IN'),
City('North America', 'Mexico City', 'MX'),
City('North America', 'New York', 'US'),
City('South America', 'São Paulo', 'BR'),
]

通过函数返回列表中位于亚洲的城市。

对象引用、可变性和垃圾回收

变量不是盒子


通过变量b可以看出,如果认为b是盒子,存储盒子a的副本,那么结果就对不上了。更合理的理解是把变量视作便利贴。
因此,b=a语句不是把a盒子中的内容复制到b盒子中,而是在标注为a对象上再贴一个标注b

同一性、相等性和别名


==is之间的选择

==运算符比较两个对象的值(对象存储的数据),而is比较对象的标识。
最常使用is检查变量绑定的值是不是None。

x  is None

否定的写法是

x is not None

元组的相对不可变性

元组与多数python容器(列表、字典、集合)一样,存储的是对象的引用。如果引用的项是可变的,即便元组本身不可变,项依然可以更改。也就是说
,元组的不可变性其实是指tuple数据结构的物理内容(即存储的引用)不可变,与引用的对象无关。

🚩相比之下,strbytesarray.array等扁平序列存储的不是引用,而是在连续的内存中存储内容的本身(字符、字节序列和数值)。


复制对象时,相等性和同一性之间的区别有更深层的影响。副本与源对
象相等,但是 ID 不同。

默认做浅拷贝

复制列表(或多数内置的可变容器)最简单的方式是使用内置的类型构造函数。例如:

对列表和其他可变序列来说,还可以使用简洁的 l2 = l1[:] 语句创建副本。
然而,构造函数或 [:] 做的是浅拷贝(即复制最外层容器,副本中的
项是源容器中项的引用)。如果所有项都是不可变的,那么这种行为没
有问题,而且还能节省内存。但是,如果有可变的项,可能就会导致意
想不到的问题。

🚩浅拷贝的副本,可变对象跟着变化,而对象本身的变化及不可变的对象发生改变,则副本不发生变化。

为任意对象做浅拷贝和深拷贝

有时需要深拷贝(即副本不共享内部对象的引用),copy模块提供的copydeepcopy函数分别对任意对象做浅拷贝和深拷贝。
示例1:校车乘客在途中有上有下

class Bus:
    def __init__(self, passengers=None):
        if passengers is None:
            self.passengers = []
        else:
            self.passengers = list(passengers)
    def pick(self, name):
        self.passengers.append(name)
    def drop(self, name):
        self.passengers.remove(name)

copy deepcopy产生的不同效果.

🚩浅拷贝对于可变对象,浅拷贝的副本也会跟着变化,而深拷贝引用另一个列表,因此不会变

示例2:循环引用:b引用a,又把b追加到a中;deepcopy会想办法复制a

🚩深拷贝有时可能太深了。例如对象可能会引用不该复制的外部资源或单列。

函数的参数是引用时

Python 唯一支持的参数传递模式是共享传参(call by sharing)。多数面
向对象语言采用这一模式,包括 JavaScriptRubyJavaJava 的引用
类型是这样,原始类型按值传参)。共享传参指函数的形参获得实参引
用的副本。也就是说,函数内部的形参是实参的别名。这种模式的结果是,函数可能会修改作为参数传入的可变对象,但是无
法修改那些对象的标识(也就是说,不能把一个对象彻底替换成另一个
对象)
示例1:函数可能会修改接收到的任何可变对象

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

可选参数可以有默认值,这是 Python 函数定义的一个很棒的特性,这
样我们的 API 在演进的同时能保证向后兼容。然而,应该避免使用可变的对象作为参数的默认值
示例2:可变默认值的危险性

class HauntedBus:
    """一个受幽灵乘客折磨的校车模型"""
        
    #  如果没有传入 passengers 参数,则绑定默认的列表对象(一开始是空列表)  
    def __init__(self, passengers=[]): # 
        self.passengers = passengers 
        # 这个赋值语句把 self.passengers 变成 passengers 的别名。没有
        # 提供 passengers 参数时,passengers 是默认列表的别名
        
    def pick(self, name):
        self.passengers.append(name) 
        # 在 self.passengers 上调用 .remove() 和 .append() 方法,修改
        # 的其实是默认列表,它是函数对象的一个属性。
    def drop(self, name):
        self.passengers.remove(name)


问题在于,没有指定初始乘客的 HauntedBus 实例共享同一个乘客列表。
实例化 HauntedBus 时,如果
传入乘客,则一切正常。但是,不为 HauntedBus 指定乘客的话,奇怪
的事就会发生,这是因为 self.passengers 变成了 passengers 参数
默认值的别名。出现这个问题的根源是,默认值在定义函数时求解(通
常在加载模块时),因此默认值变成了函数对象的属性。所以,如果默
认值是可变对象,而且修改了它的值,那么后续的函数调用都会受到影
响。

防御可变参数

如果你定义的函数接收可变参数,那就应该谨慎考虑调用方是否期望修
改传入的参数。例如,如果函数接收一个字典,而且在处理的过程中要修改它,那么这
个副作用要不要体现到函数外部?具体问题具体分析。这其实需要函数
的编写者和调用方达成共识。

del和垃圾回收

对象绝不会自行销毁;然而,对象不可达时,可能会被当作垃圾回
收。
首先,你可能觉得奇怪,del 不是函数而是语句,写作 del x 而不是
del(x)
其次,del 语句删除引用,而不是对象。del 可能导致对象被当作垃圾
回收,但是仅当删除的变量保存的是对象的最后一个引用时。重新绑定
也可能导致对象的引用数量归零,致使对象被销毁。

示例:没有指向对象的引用时,监控对象生命结束时的情形

上面的示例明确指出del不删除对象,但是执行del操作后可能会导致对象不可达,从而使得对象被删除。

python对不可变类型的操作

示例1:使用一个元组构建一个元组,得到的其实是同一个元组

strbytesfrozenset 实例也有这种行为。
示例2:字符串字面量可能会创建共享的对象

共享字符串字面量是一种优化措施,称为驻留(interning)。CPython
还会在小的整数上使用这个优化措施,防止重复创建“热门”数值,例如
0、1、-1 等。注意,CPython 不会驻留所有字符串和整数,驻留的条件
是实现细节,而且没有文档说明。

posted @ 2024-05-28 17:34  Bonne_chance  阅读(7)  评论(0编辑  收藏  举报
1