记录博客 ZH-BLOG

Python 风格对象

时间:2018-08-13 18:26:21分类:python

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):
        return math.hypot(self.x, self.y)


    def __bool__(self):
        return bool(abs(self))


    @classmethod
    def frombytes(cls, octets):
        typecode = chr(octets[0])
        memv = memoryview(octets[1:]).cast(typecode)
        return cls(*memv)

>>> v1 = Vector2d(3, 4)
>>> print(v1.x, v1.y)
3.0 4.0
>>> x, y = v1
>>> x, y
(3.0, 4.0)
>>> v1
Vector2d(3.0, 4.0)
>>> v1_clone = eval(repr(v1))
>>> v1 == v1_clone
True
>>> print(v1)
(3.0, 4.0)
>>> octets = bytes(v1)
>>> octets
b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@'
>>> abs(v1)
5.0
>>> bool(v1), bool(Vector2d(0, 0))

>>> from vector2d_v0 import Vector2d
>>> v1 = Vector2d(3, 4)
>>> v1
Vector2d(3.0, 4.0)
>>> vb = bytes(v1)
>>> vb
b'd\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@'
>>> v2 = Vector2d.frombytes(vb)
>>> v2
Vector2d(3.0, 4.0)
>>> v1 == v2
True
>>> v1 is v2
False

classmethod与staticmethod

classmethod 装饰器不用传入 self 参数, 相反,要通过 cls 传入类本身。

staticmethod 装饰器也会改变方法的调用方式,但是第一个参数不是特殊的值。而是普通的函数。

>>> class Demo:
	@classmethod
	def klassmeth(*args):
		return args
	@staticmethod
	def statmethod(*args):
		return args

	
>>> Demo.klassmeth()
(,)
>>> Demo.klassmeth('spam')
(, 'spam')
>>> Demo.statmethod()
()
>>> Demo.statmethod('spam')
('spam',)

格式化显示

>>> brl = 1/2.43
>>> brl
0.4115226337448559
>>> format(brl, '.4f')
'0.4115'
>>> '1 BRL = {rate:.2f} USD'.format(rate=brl)
'1 BRL = 0.41 USD'
>>> format(42, 'b')
'101010'
>>> format(2/3, '.1%')
'66.7%'
>>> from datetime import datetime
>>> now = datetime.now()
>>> format(now, '%y-%m-%d %H:%M:%S')
'18-08-13 17:03:56'
>>> format(now, '%Y-%m-%d %H:%M:%S')
'2018-08-13 17:03:56'
>>> 'It is now {:%Y-%m-%d %H:%M:%S}'.format(now)
'It is now 2018-08-13 17:03:56'

如果类没有定义 __format__ 方法, 从 object 继承的方法会返回 str(my_object)。

>>> v1 = Vector2d(3, 4)
>>> format(v1)
'(3.0, 4.0)'

实现 __format__ 方法

def __format__(self, fmt_spec=''):
        components = (format(c, fmt_spec) for c in self)
        return '({}, {})'.format(*components)

>>> v1 = Vector2d(3, 4)
>>> format(v1)
'(3.0, 4.0)'
>>> format(v1, '.2f')
'(3.00, 4.00)'
>>> format(v1, '.3e')
'(3.000e+00, 4.000e+00)'

可散列性

目前实现了 __eq__ 方法,而没有实现 __hash__ 方法,所以 Vector2d 是不可散列的。https://docs.python.org/3/reference/datamodel.html#object.__hash__

hash 值应该是不可变的,将 x、y 设为只读。

    @property
    def x(self):
        return self.__x


    @property
    def y(self):
        return self.__y

实现 __hash__ 方法

def __hash__(self):
        return hash(self.x) ^ hash(self.y)

>>> v1 = Vector2d(3, 4)
>>> hash(v1)
7
>>> v2 = Vector2d(3.1, 4.2)
>>> set([v1, v2])
{Vector2d(3.1, 4.2), Vector2d(3.0, 4.0)}

私有属性的名称会被“改写”

>>> from vector2d_v0 import Vector2d
>>> v1 = Vector2d(3, 4)
>>> v1
Vector2d(3.0, 4.0)
>>> v1.__dict__
{'_Vector2d__x': 3.0, '_Vector2d__y': 4.0}
>>> dir(v1)
['_Vector2d__x', '_Vector2d__y', '__abs__', '__bool__', '__bytes__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'frombytes', 'typecode', 'x', 'y']
>>> v1._Vector2d__x, v1._Vector2d__y
(3.0, 4.0)

约定使用 一个下划线前缀编写“受保护”的属性(如 self._x) 。不过在模块中, 顶层名称使用一个前导下划线的话, 的确会有影响: 对 from mymod import * 来说, mymod 中前缀为下划线的名称不会被导入。 然而, 依旧可以使用 from mymod import _privatefunc 将其导入。

使用 __slots__ 类属性节省空间

__slots__ = ('__x', '__y')

在类中定义 __slots__ 属性之后, 实例不能再有 __slots__ 中所列名称之外的其他属性。通过 __slots__ 类属性, 能节省大量内存, 方法是让解释器在元组中存储实例属性, 而不用字典。

如果把 '__dict__' 这个名称添加到 __slots__ 中,那么并不会节省内存。

如果需要支持弱引用,那么要把 '__weakref__' 添加到 __slots__ 中。

覆盖类属性

>>> v1
Vector2d(3.0, 4.0)
>>> dumpd = bytes(v1)
>>> dumpd
b'd\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@'
>>> len(dumpd)
17
>>> v1.typecode
'd'
>>> v1.typecode = 'f' # 将实例 v1 的 typecode 改为 f 
>>> dumpf = bytes(v1)
>>> dumpf
b'f\x00\x00@@\x00\x00\x80@'
>>> len(dumpf)
9
>>> Vector2d.typecode
'd'
>>> Vector2d.typecode = 'f' # 类属性 typecode 改为 f,默认实例化的对象将都是 f。

子类覆盖

>>> class ShortVector2d(Vector2d):
	typecode = 'f'

	
>>> sv = ShortVector2d(1/11, 1/27)
>>> sv
ShortVector2d(0.09090909090909091, 0.037037037037037035)
>>> len(bytes(sv))
9