# 第九章 符合python风格的对象
"""
鸭子类型(duck typing):
一个动物只要走起路来像鸭子,叫起来像鸭子,那么我就说它是鸭子
一个对象只要按照预定行为实现了对象所需的方法即可
"""
"""
内容提要:
1.支持用于生成对象其他表示形式的内置函数repr/bytes
2.使用一个类方法实现备选构造方法
3.扩展内置的format()函数和str.format()方法使用的格式微语言
4.实现只读属性
5.把对象变为可散列的
6.利用__slots__节省内存
7.staticmethod和classmethod
"""
# 9.1 对象的表示形式
"""
对象的字符串表示形式
repr() :以便于开发者理解的方式返回对象的字符串表示形式
str() :以便于用于理解的方式返回对象的字符串表示形式
背后的功臣__repr__和__str__
为了给对象提供其他的表示形式还会用到__bytes__和__format__
__bytes__是调用内置函数bytes()时实际执行的代码,显示对象的字节序列表示形式
__format__会被内置的format()和str.format()调用,使用特殊的格式代码显示对象的字符串表示形式
"""
# 9.2 再谈向量类
# vector2d_v0.py
# 9.3备选构造方法
# vector2d_v1.py
# 9.4 classmethod和staticmethod
"""
classmethod最常见的用途是定义备选的构造方法
staticmethod也会改变方法的调用方式,但第一个参数不是特殊的值
其实静态方法就是普通的函数,只是碰巧在类的定义体中,而不是在模块层定义
"""
# 例9-4比较classmethod和staticmethod的行为
class Demo:
@classmethod
def klassmeth(*args):
return args # 返回全部位置参数
@staticmethod
def statmeth(*args):
return args # 返回全部位置参数
"""不管怎么调用klassmeth,它的第一个参数始终是Demo类"""
print(Demo.klassmeth())
# >>>(<class '__main__.Demo'>,)
print(Demo.klassmeth('spam'))
# >>>(<class '__main__.Demo'>, 'spam')
"""而Demo.statmeth和普通函数的行为相似"""
print(Demo.statmeth())
# >>>()
print(Demo.statmeth('spam'))
# >>>('spam',)
# 9.5 格式化的显示
"""
format()函数和str.format()把各个类型的格式化方式委托给
__format__(format_spec)方法
format_spec:格式说明符
"""
brl = 1/2.43 # BRL到USD的货币兑换比价'
print(brl) # 0.4115226337448559
print(format(brl, '0.4f')) # 0.4115
print('1 BRL = {rate:0.2f} USD'.format(rate=brl)) # 1 BRL = 0.41 USD
"""
'{0.mass:5.3e}'
'0.mass'是字段名
'5.3e'是格式说明符
格式说明符使用的表示法叫格式规范微语言
'b':二进制int类型
'x':十六进制的int类型
'f':小数形式的float类型
'%':百分比形式
整数的代码:'bcdoxXn'
浮点数的代码:'eEfFgGn'
字符串的代码:'s'
repr()形式返回的 : 'r'
"""
print(format(42, 'b')) # 101010
print(format(2 / 3, '.1%')) # 66.7%
"""
格式规范微语言是可扩展的,各个类可以自行解释format_spec参数
例如datetime模块中的类
他们的__format__方法使用的格式代码与strftime()一样
"""
# 下面是format和str.format()的示例
from datetime import datetime
now = datetime.now()
print(format(now, '%H:%M:%S')) # 10:26:47
print("It's now {:%I:%M:%p}".format(now)) # It's now 10:28:AM
"""
如果类没有定义__format__,从object继承的方法会返回str(my_object)
然而如果传入格式说明符,会报错
定义Vector2d类的__format__方法,使其支持极坐标显示,参看
vector2d_v1.py中的__format__方法,其中自定义了一个格式代码p
"""
# 9.6可散列的Vector2d
"""
要想创建可散列的类型,不一定要实现特性,只要正确实现__hash__和__eq__方法
如果定义的标量数值,可能还要实现__int__和__float__
还有用于支持comlex()的__complex__
代码在:vector2d_v3.py中
"""
# 9.7 python的私有属性和受保护的属性
"""
为了避免子类在不知情的情况下意外覆盖"私有"属性,python提供了一种机制名称改写
即在私有属性前加两个前导下划线
但这不是一种安全措施,而只是一种保护措施,它并不能消除主观作恶
例如,可以通过_类名.__私有属性的方法对私有属性进行改写
但python程序员会严格遵守这种约定,不在类外部访问这种属性
"""
from vector2d_v3 import Vector2d
v1 = Vector2d(3,4)
print(v1.__dict__)
print(v1._Vector2d__x)
v1._Vector2d__x = 5
print(v1) # (5, 4.0) 改写成功
# 9.8 使用__slots__类属性节省空间
"""
默认情况下,实例.__dict__的字典里存储实例属性
而字典的实现利用的散列表,如果要处理数百万个属性不多的实例,字典将会大占据大量内存
通过__slots__类属性可能节省大量内存
继承至超类的的__slots__属性没有效果
python只会使用各个类中定义的__slots__属性
定义__slots__的方式:
1.创建一个类属性__slots__
2.把它的值设为一个字符串构成的可迭代对象
3.其中各个元素表示各个实例属性
__slots__的问题:
1.每个子类都要定义__slots__属性,因为解释器会忽略掉继承的__slots__属性
2.实例只能拥有__slots__列出的属性,除非把__dict__添加进__slots__中
但这样就失去了节省内存的功能
3.如果不把__weakref__加入__slots__,实例就不能作为弱引用的目标
"""
# 9.9 覆盖类属性
"""
类属性可用于为实例属性提供默认值
vector2d类有一个类属性typecode
__bytes__方法两次用到了它
因为Vetor2d实例没有typecode属性,所有self.typecode默认获取的是Vetor2d.typecode类属性的值
如果为不存在的实例属性赋值,同名类属性不受影响
然而自此以后,实例读取的self.typecode就是实例属性typecode了
也就是说把类属性Vetor2d.typecode覆盖了
借助这个特性可以为不同的实例定制不同的typecode值
typecode = 'd' 8字节双精度浮点数表示向量的各个分量
typecode = 'f' 4字节单精度浮点数表示向量的各个分量
"""
v1 = Vector2d(1.1,2.2)
dumpd = bytes(v1)
print(dumpd)
print(len(dumpd)) # 17
v1.typecode = 'f'
dumpf = bytes(v1)
print(dumpf)
print(len(dumpf)) # 9
print(Vector2d.typecode) # 'd' 只有实例的typecode属性变了,而类属性没有变
"""
要修改Vector2d的类属性,可以显式地Vector2d = 'f'
但是更符合python风格的方式是:
创建一个子类,只用于定制类的数据属性
"""
# 例9-14 ShortVector2d是Vector2d的子类,只用于覆盖typecode的值
from vector2d_v3 import Vector2d
class ShortVector2d(Vector2d):
typecode = 'f'
sv = ShortVector2d(1/11,1/27)
print(repr(sv))
print(len(bytes(sv))) # 9
# 本章小结
"""
使用特殊方法和约定的结构,定义行为良好的符合python风格的类
所有用于获取字符串和字节序列表示形式的方法
__repr__
__str__
__format__
__bytes__
把对象转换成数字的几个方法:
__abs__
__bool__
__hash__
用于测试字节序列转换和支持散列的__eq__方法
备选的构造方法:frombytes()
@calssmethod和@staticmethod
私有属性和@property
__slots__节省内存
实例属性覆盖类属性
"""
vector2d_v0.py
# vevtor2d_v0.py 目前定义的都是特殊方法
from array import array
import math
class Vector2d:
typecode = 'd' # 类属性,在Vector2d实例和字节序列转化时使用
def __init__(self,x , y):
self.x = float(x) # 把x,y转换为浮点数,防止实例化的时候传入不当的参数
self.y = float(y)
def __iter__(self): # 变成可迭代对象,这样才能拆包
return (i for i in (self.x,self.y))
# 另一种实现
# yield self.x
# yield 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)])+ # 把typecode转换成字节序列
bytes(array(self.typecode,self))) # 迭代Vector2d实例,得到一个数组,再转换成二进制序列
def __eq__(self, other): # 这样做会存在问题 如Vector2d(3,4) == [3,4]
return tuple(self) == tuple(other)
def __abs__(self): # x,y为直角的三角形的斜边长
return math.hypot(self.x,self.y)
def __bool__(self): # 把abs的结果转换为bool型
return bool(abs(self))
def __add__(self,other):
x = self.x + other.x
y = self.y + other.y
return Vector2d(x,y)
def __mul__(self, scalar):
return Vector2d(self.x * scalar,self.y * scalar)
if __name__ == '__main__':
v1 = Vector2d(3,4)
print(v1.x,v1.y) # 实例的分量可以直接通过属性访问,
x,y = v1 # 可以拆包成变量元组
print(x, y)
v1 # repr函数调用vextor2d实例,得到的结果类似于构建实例的源码
v1_clone = eval(repr(v1)) # eval说明repr得到的是对构造方法的准确表述
print(v1 == v1_clone) # 支持==比较
print(v1) # print会调用__str__方法
octets = bytes(v1) # 调用__bytes__生成二进制表示形式
print(octets)
print(abs(v1)) # 调用__abs__返回Vector2d实例的模
bool(v1) # 调用__bool__,如果实例的模为0,返回False
bool(Vector2d(0,0))
vector2d_v1.py
# vevtor2d_v1.py 备选构造方法,添加了frombytes() __format__()
from array import array
import math
class Vector2d:
typecode = 'd' # 类属性,在Vector2d实例和字节序列转化时使用
def __init__(self,x , y):
self.x = float(x) # 把x,y转换为浮点数,防止实例化的时候传入不当的参数
self.y = float(y)
def __iter__(self): # 变成可迭代对象,这样才能拆包
return (i for i in (self.x,self.y))
# 另一种实现
# yield self.x
# yield 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)])+ # 把typecode转换成字节序列
bytes(array(self.typecode,self))) # 迭代Vector2d实例,得到一个数组,再转换成二进制序列
def __eq__(self, other): # 这样做会存在问题 如Vector2d(3,4) == [3,4]
return tuple(self) == tuple(other)
def __abs__(self): # x,y为直角的三角形的斜边长
return math.hypot(self.x,self.y)
def __bool__(self): # 把abs的结果转换为bool型
return bool(abs(self))
def __add__(self,other):
x = self.x + other.x
y = self.y + other.y
return Vector2d(x,y)
def __mul__(self, scalar):
return Vector2d(self.x * scalar,self.y * scalar)
#从字节序列转换成Vector2d实例
@classmethod # 类方法装饰器
def frombytes(cls,octets):
typecode = chr(octets[0]) # 从第一个字节读取typecode
# 使用传入的octets字节序列创建一个memoryview,然后使用typecode转换
memv = memoryview(octets[1:]).cast(typecode)
return cls(*memv)
# 使用格式说明符来格式化每一个分量
# def __format__(self, format_spec=''):
# components = (format(c,format_spec) for c in self)
# return '({},{})'.format(*components)
#在微语言中添加一个自定义的格式代码'p',在极坐标中显示向量,即<r,θ> r是模,θ是弧度
# 定义angel方法计算角度
def angel(self):
return math.atan2(self.y,self.x)
# 第二版__format__方法计算极坐标
def __format__(self, fmt_spec=''):
if fmt_spec.endswith('p'): # 如果格式代码以p结尾,使用极坐标
fmt_spec = fmt_spec[:-1] # 从fmt_spec中删除p
coords = (abs(self),self.angel()) # 构建元组表示极坐标
outer_fmt = '<{},{}>' # 把外层格式设为尖括号
else: # 不以p结尾的情况
coords = self
outer_fmt = '({},{})'
components = (format(c,fmt_spec) for c in coords) # 使用各个分量生成可迭代对象,构成格式化字符串
return outer_fmt.format(*components) # 带入外层格式
if __name__ == '__main__':
v1 = Vector2d(3,4)
print(v1.x,v1.y) # 实例的分量可以直接通过属性访问,
x,y = v1 # 可以拆包成变量元组
print(x, y)
v1 # repr函数调用vextor2d实例,得到的结果类似于构建实例的源码
v1_clone = eval(repr(v1)) # eval说明repr得到的是对构造方法的准确表述
print(v1 == v1_clone) # 支持==比较
print(v1) # print会调用__str__方法
octets = bytes(v1) # 调用__bytes__生成二进制表示形式
print(octets)
print(abs(v1)) # 调用__abs__返回Vector2d实例的模
bool(v1) # 调用__bool__,如果实例的模为0,返回False
bool(Vector2d(0,0))
v3 = Vector2d.frombytes(b'd\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@')
print(v3)
# 如果类没有定义__format__, 从object继承的方法会返回str(my_object)
# 然而如果传入格式说明符, 会报错
print(format(v3))
print(format(v3,'.2f')) # TypeError: unsupported format string passed to Vector2d.__format__
# 定义了__format__以后的结果 (3.00,4.00)
print(format(Vector2d(1,1), 'p'))
print(format(Vector2d(1,1), '.2ep'))
print(format(Vector2d(1,1), '0.5fp'))
hash(Vector2d(3,4))
vector2d_v3.py
# vevtor2d_v3.py 增加不可变的属性 以实现__hash__()
from array import array
import math
class Vector2d:
# python会在各个实例中使用类似元组的结构存储实例变量
# __slots__ = ('__x','__y') # 这个类的所有实例属性都在这儿了
typecode = 'd' # 类属性,在Vector2d实例和字节序列转化时使用
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 __hash__(self):
return hash(self.x) ^ hash(self.y)
def __iter__(self): # 变成可迭代对象,这样才能拆包
return (i for i in (self.x,self.y))
# 另一种实现
# yield self.x
# yield 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)])+ # 把typecode转换成字节序列
bytes(array(self.typecode,self))) # 迭代Vector2d实例,得到一个数组,再转换成二进制序列
def __eq__(self, other): # 这样做会存在问题 如Vector2d(3,4) == [3,4]
return tuple(self) == tuple(other)
def __abs__(self): # x,y为直角的三角形的斜边长
return math.hypot(self.x,self.y)
def __bool__(self): # 把abs的结果转换为bool型
return bool(abs(self))
def __add__(self,other):
x = self.x + other.x
y = self.y + other.y
return Vector2d(x,y)
def __mul__(self, scalar):
return Vector2d(self.x * scalar,self.y * scalar)
#从字节序列转换成Vector2d实例
@classmethod # 类方法装饰器
def frombytes(cls,octets):
typecode = chr(octets[0]) # 从第一个字节读取typecode
# 使用传入的octets字节序列创建一个memoryview,然后使用typecode转换
memv = memoryview(octets[1:]).cast(typecode)
return cls(*memv)
# 使用格式说明符来格式化每一个分量
# def __format__(self, format_spec=''):
# components = (format(c,format_spec) for c in self)
# return '({},{})'.format(*components)
#在微语言中添加一个自定义的格式代码'p',在极坐标中显示向量,即<r,θ> r是模,θ是弧度
# 定义angel方法计算角度
def angel(self):
return math.atan2(self.y,self.x)
# 第二版__format__方法计算极坐标
def __format__(self, fmt_spec=''):
if fmt_spec.endswith('p'): # 如果格式代码以p结尾,使用极坐标
fmt_spec = fmt_spec[:-1] # 从fmt_spec中删除p
coords = (abs(self),self.angel()) # 构建元组表示极坐标
outer_fmt = '<{},{}>' # 把外层格式设为尖括号
else: # 不以p结尾的情况
coords = self
outer_fmt = '({},{})'
components = (format(c,fmt_spec) for c in coords) # 使用各个分量生成可迭代对象,构成格式化字符串
return outer_fmt.format(*components) # 带入外层格式
def __complex__(self):
'''用于支持complex(my_vector2d)'''
real = self.x
img = self.y
return complex(real,img)
if __name__ == '__main__':
v1 = Vector2d(3,4)
print(v1.x,v1.y) # 实例的分量可以直接通过属性访问,
x,y = v1 # 可以拆包成变量元组
print(x, y)
v1 # repr函数调用vextor2d实例,得到的结果类似于构建实例的源码
v1_clone = eval(repr(v1)) # eval说明repr得到的是对构造方法的准确表述
print(v1 == v1_clone) # 支持==比较
print(v1) # print会调用__str__方法
octets = bytes(v1) # 调用__bytes__生成二进制表示形式
print(octets)
print(abs(v1)) # 调用__abs__返回Vector2d实例的模
bool(v1) # 调用__bool__,如果实例的模为0,返回False
bool(Vector2d(0,0))
v3 = Vector2d.frombytes(b'd\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@')
print(v3)
# 如果类没有定义__format__, 从object继承的方法会返回str(my_object)
# 然而如果传入格式说明符, 会报错
print(format(v3))
print(format(v3,'.2f')) # TypeError: unsupported format string passed to Vector2d.__format__
# 定义了__format__以后的结果 (3.00,4.00)
print(format(Vector2d(1,1), 'p'))
print(format(Vector2d(1,1), '.2ep'))
print(format(Vector2d(1,1), '0.5fp'))
print(hash(Vector2d(3, 4)))
print(hash(Vector2d(3, 4.1)))
print(complex(Vector2d(3, -5)))
35岁学Python,也不知道为了啥?