第九章 - 符合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__属性。

 

posted @ 2017-07-26 22:21  Vincen_shen  阅读(152)  评论(0)    收藏  举报