learn from 《流畅的python》
from array import array
import math
class 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) # 构造类对象
@staticmethod
def hello():
print("hello world!")
def __iter__(self): # 可迭代对象,才能拆包 x,y = my_vector
return (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))
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 = self
outer_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)
hash(v1) # TypeError: unhashable type: 'Vector2D'
为了可以散列,需要实现__hash__()
, __eq__()
def __init__(self, x, y):
self.__x = float(x)
self.__y = float(y)
@property
def 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)}
__
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)
并不能真正的实现 私有和不可变
__slots__
类属性节省空间class Vector2d:
__slots__ = ('__x', '__y')
等号右侧可以是可迭代
的对象,里面存储所有实例属性的名称的字符串
,从而避免使用消耗内存的 __dict__
属性
__slots__
属性之后,实例不能再有 __slots__
中所列名称之外的其他属性__weakref__
属性。用户定义的类中 默认就有 __weakref__
属性。
可是,如果类中定义了 __slots__
属性,而且想把实例作为 弱引用 的目标,那么要把 __weakref__
添加到 __slots__
中__dict__
加入到 __slots__
中__slots__
属性,因为解释器会忽略继承的 __slots__
属性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) # d
v1.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) # d
Vector2D.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))