文章目录
- 1. 对象表示形式
- 2. 可散列的类
- 3. 私有属性的利弊
- 4. `__slots__` 类属性节省空间
- 5. 覆盖类属性
learn from 《流畅的python》
from array import array
import mathclass Vector2D:typecode = 'd' # 类属性def __init__(self, x, y):self.x = float(x)self.y = float(y)@classmethod # 装饰器, 函数不需要传入 self 参数,需要cls 传入类本身# classmethod 最常见的用途是 定义备选构造方法# @staticmethod 就是定义在类中的普通函数def frombytes(cls, octets):typecode = chr(octets[0])memv = memoryview(octets[1:]).cast(typecode)return cls(*memv) # 构造类对象@staticmethoddef hello():print("hello world!")def __iter__(self): # 可迭代对象,才能拆包 x,y = my_vectorreturn (i for i in (self.x, self.y)) # 生成器表达式def __repr__(self):class_name = type(self).__name__return '{}({!r},{!r})'.format(class_name, *self)# {!r} 获取 *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):return math.hypot(self.x, self.y)def __bool__(self):return bool(abs(self))
1. 对象表示形式
repr()
返回便于 开发者 理解的对象字符串形式,须实现__repr()__
str()
返回便于 用户 理解的对象字符串形式,须实现__str()__
__format()__
会被内置的format()
,str.format()
调用
>>> brl = 1/2.43
>>> brl
0.4115226337448559
>>> format(brl, "0.4f")
'0.4115'
>>> format(brl, ".4f")
'0.4115'
>>> "1 BRL = {rate:0.2f} USD".format(rate=brl)
'1 BRL = 0.41 USD'
https://docs.python.org/3/library/string.html#formatspec
v1 = Vector2D(315687,4)
print("test str {0.x:5.3e}".format(v1))
# test str 3.157e+05
{ 变量 :格式说明符 }
包含两部分
>>> format(8, 'b') # 二进制
'1000'
>>> format(1/3, '.2%') # %百分比
'33.33%'
- 如果类没有定义
__format__
方法,从 object 继承的方法会返回str(my_object)
,调用__str__()
print(format(v1)) # (315687.0, 4.0)
print(format(v1, '.3f')) # TypeError: unsupported format string passed to Vector2D.__format__
为了解决该问题,在类中添加方法:
def __format__(self, fmt_spec=""):components = (format(c, fmt_spec) for c in self)# 使用内置的 format 把格式应用到各个分量上,构成一个可迭代的字符串return "({}, {})".format(*components) # 格式化字符串
print(format(v1, '.3f')) # (315687.000, 4.000)
- 自定义极坐标表示
def angle(self):return math.atan2(self.y, self.x)def __format__(self, fmt_spec=""):if fmt_spec.endswith('p'): # 以 p 结尾的用极坐标fmt_spec = fmt_spec[:-1]coords = (abs(self), self.angle())outer_fmt = "<{}, {}>"else:coords = selfouter_fmt = "({}, {})"components = (format(c, fmt_spec) for c in coords)return outer_fmt.format(*components)
print(format(Vector2D(1, 1), 'p'))
print(format(Vector2D(1, 1), '.3ep'))
print(format(Vector2D(1, 1), '0.5fp'))
print(format(Vector2D(1, 1), '0.2f'))<1.4142135623730951, 0.7853981633974483>
<1.414e+00, 7.854e-01>
<1.41421, 0.78540>
(1.00, 1.00)
2. 可散列的类
hash(v1) # TypeError: unhashable type: 'Vector2D'
为了可以散列,需要实现__hash__()
, __eq__()
def __init__(self, x, y):self.__x = float(x)self.__y = float(y)@propertydef x(self):return self.__x@property # @property 装饰器把读值方法标记为特性def y(self):return self.__y
v1.__x = 100 # 值不可以变更
print(v1) # (315687.0, 4.0)
# v1.x = 100 # AttributeError: can't set attribute
- 添加
__hash__()
def __hash__(self):return hash(self.x) ^ hash(self.y)
v2 = Vector2D(10, 20)
v3 = Vector2D(10, 20)
print(hash(v1)) # 315683
print(hash(v2)) # 30
print(v2 is v3) # False
print(hash(v3)) # 30
print(set([v1, v2, v3])) # {Vector2D(315687.0,4.0), Vector2D(10.0,20.0)}
3. 私有属性的利弊
- 如果子类跟父类有相同的属性,子类会覆盖父类
- 以
__
or_
开头的属性将会被存在 实例的__dict__
属性内,且加上前缀_类名
print(v1.__dict__)
# {'_Vector2D__x': 315687.0, '_Vector2D__y': 4.0, '__x': 100}
print(v1._Vector2D__x) # 315687.0
名称改写是一种安全措施,不能保证万无一失:它的目的是避免意外访问,不能防止故意做错事
Python 解释器不会对使用 单个下划线 的属性名做特殊处理,不过这是很多 Python 程序员严格遵守的约定,他们不会在类外部访问这种属性。
print(v1._Vector2D__x) # 315687.0
v1._Vector2D__x = 100
print(v1._Vector2D__x) # 100
print(v1) # (100., 4.0)
并不能真正的实现 私有和不可变
4. __slots__
类属性节省空间
class Vector2d: __slots__ = ('__x', '__y')
等号右侧可以是可迭代
的对象,里面存储所有实例属性的名称的字符串
,从而避免使用消耗内存的 __dict__
属性
- 在类中定义
__slots__
属性之后,实例不能再有__slots__
中所列名称之外的其他属性 - 为了 让对象支持弱引用,必须有
__weakref__
属性。用户定义的类中 默认就有__weakref__
属性。
可是,如果类中定义了__slots__
属性,而且想把实例作为 弱引用 的目标,那么要把__weakref__
添加到__slots__
中 - 一般不要把
__dict__
加入到__slots__
中 - 每个子类都要定义
__slots__
属性,因为解释器会忽略继承的__slots__
属性
5. 覆盖类属性
print(v1.typecode) # d
print(v2.typecode) # d
print(bytes(v1)) # b'd\x00\x00\x00\x00\x00\x00Y@\x00\x00\x00\x00\x00\x00\x10@'
print(Vector2D.typecode) # dv1.typecode = 'f'
print(v1.typecode) # f
print(bytes(v1)) # b'f\x00\x00\xc8B\x00\x00\x80@'
print(Vector2D.typecode) # d
v1.typecode = 'd'
print(v1.typecode) # dVector2D.typecode = 'f'
print(Vector2D.typecode) # f
print(v1.typecode) # d
print(v2.typecode) # f
typecode 是类属性
,一旦实例对象赋值typecode
后,实际是创建了新的实例属性- 如果为不存在的 实例属性 赋值,会 新建 实例属性,类属性不受影响,但是实例属性会遮盖同名类属性
还可以订制类的数据属性:
class anOtherVec(Vector2D):typecode = 'f' # 只是修改子类的数据类型v4 = anOtherVec(3,4)
print(bytes(v4))