第九章 - 符合Python风格的对象
符合Python风格的对象
得益于Python数据模型,自定义类型的行为可以像内置类型那样自然。实现如此自然的行为,靠的不是继承,而是鸭子类型:我们只需按照预定行为实现对象所需的方法即可。
本章包含以下话题:
1、使用一个类方法(classmethod)实现备选构造方法
2、实现只读属性
3、把对象变为可散列的,以便在集合中及作为dict的键使用
4、利用__slots__节省内存
9.1 对象表示形式
repr()
以便于开发者理解的方式返回对象的字符串表示形式。
str()
以便于用户理解的方式返回对象的字符串表示形式。
正如你所知,我们要实现__repr__和__str__特殊方法,为repr(), str()提供支持。
9.2 再谈向量类
示例9-2-1
from array import array import math class Vector2d(object): 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__ print("class_name", class_name) print("type(self)", type(self)) 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)) v1 = Vector2d(3, 4) print(v1.x, v1.y) x, y = v1 print(x, y) print(v1) v1_clone = eval(repr(v1)) print(v1 == v1_clone) octets = bytes(v1) print(octets) >>> 3.0 4.0 3.0 4.0 (3.0, 4.0) class_name Vector2d type(self) <class '__main__.Vector2d'> True b'd\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@'
9.3 备选构造方法(classmethod)
classmethod主要用途是作为构造函数;
Python只有一个构造函数__new__,如果想要多种构造函数就很不方便。只能在new里面写一堆if isinstance 。
有classmethod之后就可以用classmethod来写不同的构造函数,Cpython里面大部分classmethod最后都是 return cls(XXX), return XXX.__new__ ()之类的。
class DateTest(object): def __init__(self, year=None, month=None, day=None): self.year = year self.month = month self.day = day def out_date(self): print("Year:", self.year) print("Month:", self.month) print("Day:", self.day) @classmethod def string_date(cls, date): year, month, day = map(int, date.split("-")) return cls(year, month, day) t1 = DateTest("2017", "06", "21") t1.out_date() t2 = DateTest.string_date("2017-06-21") t2.out_date() """ t1和t2的输出都是一样的,t2的string_date会将"2017-06-21"拆分后传递给cls(类本身的构造方法init) """
9.6 可散列的类
为了让一个类的实例需要变成可散列的,必须使用__hash__方法或者让变量不可变:
class 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 __iter__(self): return (i for i in (self.x, self.y)) def __hash__(self): return hash(self.x) ^ hash(self.y) # ^异或运算符 v1 = Vector2d(3, 4) print(hash(v1)) a = {v1: "123"} # 实现了可散列的对象后,就可以将其作为字典的Key
9.8 使用__slots__类属性节省空间
如果要处理数百万个属性不多的实例,通过__slots__类属性,能节省大量内存,方法是让解释器在元组中存储实例属性,而不是字典。
*继承自超类的__slots__属性没有效果,Python只会使用各个类中定义的__slots__属性。
定义__slots__的方式是,创建一个类属性,使用__slots__这个名字,并把它的值设为一个字符串构成的可迭代对象。
示例9-8-1
class Vector2d: __slots__ = ("__x", "__y") 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 __iter__(self): return (i for i in (self.x, self.y)) def __hash__(self): return hash(self.x) ^ hash(self.y)
在类中定义__slots__属性的目的是告诉解释器:“这个类中的所有实例属性都在这儿了!”, 这样Python会在各实例中使用类似元组的结构存储实例变量,从而避免使用消耗内存的__dict__属性。