要构建符合Python风格的对象,就要观察真正的Python对象的行为。
对象表示形式
获取对象表示形式的标准方法
repr()
以便于开发者
理解的方式返回对象的字符串表示形式
str()
以便于用户
理解的方式返回对象的字符串表示形式
classmethod与staticmethod
classmethod(非常有用)
定义 操作类,而不是操作实例的方法。
最常见的用途是定义备选构造方法
staticmethod(不是特别有用)
静态方法就是普通的类,只是碰巧在类的定义体中,而不是在模块层定义
虽然函数不处理类,但是函数的功能与类紧密相关,因此把它放在近处。
可散列
只需要正确事项__hash__
和__eq__
方法即可。
实例的散列值绝不应该变化。
书中Vector2d例子
# -*- coding: utf-8 -*-
from array import array
import math
class Vector2d:
typecode = 'd' # 类属性
def __init__(self, x, y):
"""
将x与y转换为浮点数,尽早捕获错误
:param x:
:param 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):
return math.hypot(self.x, self.y)
def __bool__(self):
return bool(abs(self))
def __format__(self, format_spec=''):
if format_spec.endswith('p'):
format_spec = format_spec[:-1]
coords = (abs(self), self.angle())
outer_fmt = '<{}, {}>'
else:
coords = self
outer_fmt = '({}, {})'
components = (format(c, format_spec) for c in coords)
return outer_fmt.format(*components)
def __hash__(self):
return hash(self.x) ^ hash(self.y)
def angle(self):
"""
计算函数角度
:return:
"""
return math.atan2(self.y, self.x)
@classmethod
def frombytes(cls, octets):
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(*memv)
if __name__ == '__main__':
v1 = Vector2d(3, 4)
print(v1.x, v1.y) # 3.0 4.0
x, y = v1
print(x, y) # 3.0 4.0
print(v1) # (3.0, 4.0)
v1_clone = eval(repr(v1))
print(v1 == v1_clone) # True
octets = bytes(v1)
print(octets) # b'd\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@'
print(abs(v1)) # 5.0
print(bool(v1)) # True
print(bool(Vector2d(0, 0))) # False
v1_clone = Vector2d.frombytes(bytes(v1))
print(v1_clone) # (3.0, 4.0)
print(v1 == v1_clone) # True
print(format(v1)) # (3.0, 4.0)
print(format(v1, '.2f')) # (3.00, 4.00)
print(format(v1, '.3e')) # (3.000e+00, 4.000e+00)
print(Vector2d(0, 0).angle()) # 0.0
print(Vector2d(1, 0).angle()) # 0.0
epsilon = 10 ** -8
print(abs(Vector2d(0, 1).angle() - math.pi / 2) < epsilon) # True
print(abs(Vector2d(1, 1).angle() - math.pi / 4) < epsilon) # True
print(format(Vector2d(1, 1), 'p')) # <1.4142135623730951, 0.7853981633974483>
print(format(Vector2d(1, 1), '.3ep')) # <1.414e+00, 7.854e-01>
print(format(Vector2d(1, 1), '0.5fp')) # <1.41421, 0.78540>
v1 = Vector2d(3, 4)
v2 = Vector2d(3.1, 4.2)
print(hash(v1)) # 7
print(hash(v2)) # 384307168202284039
print(len(set([v1, v2]))) # 2
私有属性
使用双下划线开头的属性会存入到__dict__
属性中,并且会在前面加上一个下划线和类名。
class A():
def __init__(self, x, y):
self.__x = x
self.__y = y
if __name__ == '__main__':
a = A(1,2)
print(a.__dict__) # {'_A__x': 1, '_A__y': 2}
目的是为了避免意外访问,不能防止故意做错事。
单下划线开头的属性不会有上述变化,但是规定也是将这种变量规定为私有属性。
当使用from xxx import *
导入的时候,下划线开头的名称不会被导入
使用`slots`类属性节省空间
在类中定义__slots__
的目的就是告诉解释器:这个类中的所有实例属性都在这儿了。
实例中不能再有__slots__
中所列名称之外的其他属性。
__slots__
是用于优化的,不是为了约束程序员。
问题
每个子类都需要定义
slots__`,因为解释器会忽略集成的`__slots
属性实例只能拥有
slots__`中列出的属性,除非把`_dict_`加入`__slots
中,不过这样就失去了节省内存的功效如果不把
weakref__`加入`__slots
,实例就不能作为弱引用的目标。