《Fluent Python》 CH.09_面向对象_符合Python风格的对象
其他
- 转换命令: jupyter nbconvert --to markdown E:\PycharmProjects\TianChiProject\00_山枫叶纷飞\competitions\013_fluent_python\CH.09_面向对象_符合Python风格的对象.ipynb
主要内容
本章讨论
- 支持用于生成对象的其他的表示形式的内置函数(如 repr()、bytes(),等等)
- 使用一个类方法实现备选构造方法 (重载构造器)
- 扩展内置的 format() 函数和 str.format() 方法使用的格式微语言
- 实现只读属性
- 把对象变为可散列的,以便在集合中及作为 dict 的键使用
- 利用 slots 节省内存
我们将开发一个简单的二维欧几里得向量类型,在这个过程中涵盖上述
全部话题。
在实现这个类型的中间阶段,我们会讨论两个概念: 如何以及何时使用 @classmethod 和 @staticmethod 装饰器 Python 的私有属性和受保护属性的用法、约定和局限 我们从对象表示形式函数开始。
内容补充
- Python 占位符格式,r : 获取传入对象的__repr__方法的返回值,并将其格式化到指定位置。
- 间接证明所有类的父类都是object。(java里是这样子,Python应该也是的)
9.1 对象表示形式
Python 提供了两种方式。
- repr() 以便于开发者理解的方式返回对象的字符串表示形式。
- str()以便于用户理解的方式返回对象的字符串表示形式。
正如你所知,我们要实现 repr 和 str 特殊方法,为 repr() 和 str() 提供支持。
为了给对象提供其他的表示形式,还会用到另外两个特殊方 法:bytes 和 format。
bytes 方法与 str 方法类 似:bytes() 函数调用它获取对象的字节序列表示形式。
而 format 方法会被内置的 format() 函数和 str.format() 方法调 用,使用特殊的格式代码显示对象的字符串表示形式。
9.2 再谈示例的向量类
from array import array
import math
class Vector2d:
typecode = 'd'
def __init__(self, x, y):
self.x = float(x)
self.y = float(y)
def __iter__(self):
return (i for i in (self.x, self.y))
def __repr__(self):
class_name = type(self).__name__
return '{}({!r}, {!r})'.format(class_name, *self)
def __str__(self): return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)]) + bytes(array(self.typecode, self)))
def __eq__(self, other):
return tuple(self) == tuple(other)
def __abs__(self): # 模是 x 和 y 分量构成的直角三角形的斜边长
return math.hypot(self.x, self.y)
def __bool__(self): # __bool__ 方法使用 abs(self) 计算模,然后把结果转换成布尔 值,因此,0.0 是 False,非零值是 True。
return bool(abs(self))
v1 = Vector2d(3, 4)
v1
Vector2d(3.0, 4.0)
v1.__repr__()
'Vector2d(3.0, 4.0)'
v2 = Vector2d(3, 4)
v1==v2
True
间接证明所有类的父类都是object
isinstance(v1, object)
True
isinstance(str, object)
True
9.3 备选构造方法 (从字节进行反序列化)
使用传入的 octets 字节序列创建一个 memoryview,然后使用 typecode 转换。
然后拆包转换后的 memoryview,得到构造方法所需的一对参数。
@classmethod
def frombytes(cls, octets): # cls是类实例
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(*memv)
9.4 classmethod与staticmethod
-
classmethod,通过类名进行调用,常用于进行类的构造方法的重载时使用。(类行为)
-
staticmethod,静态方法,普通的函数。(没地方放的普通方法,不用强制传入self的方法)
class Demo:
@classmethod
def my_classmethod(*args):
return args
@staticmethod
def my_staticmethod(*args):
return args
Demo.my_classmethod()
(__main__.Demo,)
Demo.my_staticmethod()
()
9.5 格式化显示
内置的 format() 函数和 str.format() 方法把各个类型的格式化方式 委托给相应的 .format(format_spec) 方法。format_spec 是格 式说明符,它是: format(my_obj, format_spec) 的第二个参数,或者 str.format() 方法的格式字符串,{} 里代换字段中冒号后面的部 分.
后续略过.
9.6 实现可散列的类 Vector2d
重写类的__eq__方法和__hash__ 方法。
v1 = Vector2d(3, 4)
hash(v1)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-12-d77c92cec0f2> in <module>
1 v1 = Vector2d(3, 4)
----> 2 hash(v1)
3
4
TypeError: unhashable type: 'Vector2d'
v1.x = 7
v1
Vector2d(7, 4.0)
重写一版可以hash的二维向量类
class Vector2d:
typecode = 'd'
def __init__(self, x, y):
self.x = float(x)
self.y = float(y)
# @property
# def x(self): # 使用__把 属性标记为私有的。
# return self.__x
# @property
# def y(self): # 使用__把 属性标记为私有的。
# return self.__y
def __iter__(self):
return (i for i in (self.x, self.y))
def __repr__(self):
class_name = type(self).__name__
return '{}({!r}, {!r})'.format(class_name, *self)
def __str__(self): return str(tuple(self))
def __bytes__(self):
return (bytes([ord(self.typecode)]) + bytes(array(self.typecode, self)))
def __eq__(self, other):
return tuple(self) == tuple(other)
def __abs__(self): # 模是 x 和 y 分量构成的直角三角形的斜边长
return math.hypot(self.x, self.y)
def __bool__(self): # __bool__ 方法使用 abs(self) 计算模,然后把结果转换成布尔 值,因此,0.0 是 False,非零值是 True。
return bool(abs(self))
def __hash__(self): # 虽然会hash碰撞,但是eq并不相等
return hash(self.x) ^ hash(self.y)
v3 = Vector2d(3, 4)
v4 = Vector2d(4, 3)
v3==v4
False
hash(v3) == hash(v4)
True
st = set()
st.add(v3)
st.add(v4)
st
{Vector2d(3.0, 4.0), Vector2d(4.0, 3.0)}
9.7 Python的私有属性和“受保护的”属性
Python 不能像 Java 那样使用 private 修饰符创建私有属性,但是 Python 有个简单的机制,能避免子类意外覆盖“私有”属性。
这个语言特性叫名称改写,name mangling。
作用:
为了避免子类覆盖掉父类的属性的情况,如果以 __mood 的形式(两个前导下划线,尾部没有或最多有一个下划线)命名实例属性,Python 会把属性名存入实例的 dict 属性中,而且会在前面加上一个下划线和类名。因此,对 Dog 类来说,__mood 会变成 _Dog__mood;对 Beagle 类来说,会变成 _Beagle__mood。
#### 示例 9-10 私有属性的名称会被“改写”,在前面加上下划线和类名
class Vector2d:
typecode = 'd'
def __init__(self, x, y):
self.__x = float(x)
self.__y = float(y)
@property
def x(self): # 使用__把 属性标记为私有的。
return self.__x
@property
def y(self): # 使用__把 属性标记为私有的。
return self.__y
def __iter__(self):
return (i for i in (self.x, self.y))
def __repr__(self):
class_name = type(self).__name__
return '{}({!r}, {!r})'.format(class_name, *self)
def __str__(self): return str(tuple(self))
v1 = Vector2d(3, 4)
v1.__dict_
{'_Vector2d__x': 3.0, '_Vector2d__y': 4.0}
print('直接改写会引发异常!')
v1.x = 344
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-30-e00c4012d440> in <module>
----> 1 v1.x = 344
2
AttributeError: can't set attribute
print('使用名称改写的方式还是可以进行私有化属性的改写')
v1._Vector2d__x = 344
v1._Vector2d__x
344
9.8 使用 __slots__ 类属性节省空间
默认情况下,Python 在各个实例中名为 dict 的属性(字典)里存储实例属性。
为了使用底层的散列表提升访问速度,字典会消 耗大量内存。如果要处理数百万个属性不多的实例,通过 slots 类属性,能节省大量内存,方法是让解释器在元组中存储实例属性,而 不用字典。
示例 9-11 vector2d_v3_slots.py:只在 Vector2d 类中添加了 slots 属性
class Vector2d:
__slots__ = ('__x', '__y')
typecode = 'd'
在类中定义 slots 属性的目的是告诉解释器:“这个类中的所有实 例属性都在这儿了!”这样,Python 会在各个实例中使用类似元组的结 构存储实例变量,从而避免使用消耗内存的 dict 属性。如果有数 百万个实例同时活动,这样做能节省大量内存。
slots 属性有些需要注意的地方
- 不能滥用,不能使用 它限制用户能赋值的属性。
- 处理列表数据时 slots 属性最有用, 例如模式固定的数据库记录,以及特大型数据集。
- 每个子类都要定义 slots 属性,因为解释器会忽略继承的 slots 属性。
- 实例只能拥有 slots 中列出的属性,除非把 'dict' 加 入 slots 中(这样做就失去了节省内存的功效)。
- 如果不把 'weakref' 加入 slots,实例就不能作为弱引 用的目标。
9.9 覆盖类属性
Python 有个很独特的特性:类属性可用于为实例属性提供默认 值。
这个Java也支持啊!