# 第九章 符合python风格的对象

"""
鸭子类型(duck typing):
    一个动物只要走起路来像鸭子,叫起来像鸭子,那么我就说它是鸭子
    一个对象只要按照预定行为实现了对象所需的方法即可
"""

"""
内容提要:
    1.支持用于生成对象其他表示形式的内置函数repr/bytes
    2.使用一个类方法实现备选构造方法
    3.扩展内置的format()函数和str.format()方法使用的格式微语言
    4.实现只读属性
    5.把对象变为可散列的
    6.利用__slots__节省内存
    7.staticmethod和classmethod
"""
# 9.1 对象的表示形式
"""
对象的字符串表示形式
    repr() :以便于开发者理解的方式返回对象的字符串表示形式
    str() :以便于用于理解的方式返回对象的字符串表示形式
    背后的功臣__repr__和__str__
为了给对象提供其他的表示形式还会用到__bytes__和__format__
    __bytes__是调用内置函数bytes()时实际执行的代码,显示对象的字节序列表示形式
    __format__会被内置的format()和str.format()调用,使用特殊的格式代码显示对象的字符串表示形式  
"""

# 9.2 再谈向量类
# vector2d_v0.py

# 9.3备选构造方法
# vector2d_v1.py

# 9.4 classmethod和staticmethod
"""
classmethod最常见的用途是定义备选的构造方法
staticmethod也会改变方法的调用方式,但第一个参数不是特殊的值
    其实静态方法就是普通的函数,只是碰巧在类的定义体中,而不是在模块层定义
"""

# 例9-4比较classmethod和staticmethod的行为
class Demo:
    @classmethod
    def klassmeth(*args):
        return args  # 返回全部位置参数

    @staticmethod
    def statmeth(*args):
        return args  # 返回全部位置参数

"""不管怎么调用klassmeth,它的第一个参数始终是Demo类"""
print(Demo.klassmeth())
# >>>(<class '__main__.Demo'>,)
print(Demo.klassmeth('spam'))
# >>>(<class '__main__.Demo'>, 'spam')

"""而Demo.statmeth和普通函数的行为相似"""
print(Demo.statmeth())
# >>>()
print(Demo.statmeth('spam'))
# >>>('spam',)

# 9.5 格式化的显示
"""
format()函数和str.format()把各个类型的格式化方式委托给
__format__(format_spec)方法
format_spec:格式说明符
"""

brl = 1/2.43 # BRL到USD的货币兑换比价'
print(brl)  # 0.4115226337448559
print(format(brl, '0.4f'))  # 0.4115
print('1 BRL = {rate:0.2f} USD'.format(rate=brl))  # 1 BRL = 0.41 USD

"""
'{0.mass:5.3e}'
    '0.mass'是字段名
    '5.3e'是格式说明符
格式说明符使用的表示法叫格式规范微语言
    'b':二进制int类型
    'x':十六进制的int类型
    'f':小数形式的float类型
    '%':百分比形式
    整数的代码:'bcdoxXn'
    浮点数的代码:'eEfFgGn'
    字符串的代码:'s'
    repr()形式返回的 : 'r'
"""
print(format(42, 'b'))  # 101010
print(format(2 / 3, '.1%'))  # 66.7%

"""
格式规范微语言是可扩展的,各个类可以自行解释format_spec参数
例如datetime模块中的类
    他们的__format__方法使用的格式代码与strftime()一样
"""

# 下面是format和str.format()的示例
from datetime import datetime
now = datetime.now()
print(format(now, '%H:%M:%S'))  # 10:26:47
print("It's now {:%I:%M:%p}".format(now))  # It's now 10:28:AM

"""
如果类没有定义__format__,从object继承的方法会返回str(my_object)
然而如果传入格式说明符,会报错
定义Vector2d类的__format__方法,使其支持极坐标显示,参看
vector2d_v1.py中的__format__方法,其中自定义了一个格式代码p
"""

# 9.6可散列的Vector2d
"""
要想创建可散列的类型,不一定要实现特性,只要正确实现__hash__和__eq__方法
如果定义的标量数值,可能还要实现__int__和__float__
还有用于支持comlex()的__complex__
代码在:vector2d_v3.py中
"""

# 9.7 python的私有属性和受保护的属性
"""
为了避免子类在不知情的情况下意外覆盖"私有"属性,python提供了一种机制名称改写
即在私有属性前加两个前导下划线
但这不是一种安全措施,而只是一种保护措施,它并不能消除主观作恶
例如,可以通过_类名.__私有属性的方法对私有属性进行改写
但python程序员会严格遵守这种约定,不在类外部访问这种属性
"""
from vector2d_v3 import Vector2d
v1 = Vector2d(3,4)
print(v1.__dict__)
print(v1._Vector2d__x)
v1._Vector2d__x = 5
print(v1)  # (5, 4.0) 改写成功

# 9.8 使用__slots__类属性节省空间
"""
默认情况下,实例.__dict__的字典里存储实例属性
而字典的实现利用的散列表,如果要处理数百万个属性不多的实例,字典将会大占据大量内存
通过__slots__类属性可能节省大量内存
继承至超类的的__slots__属性没有效果
python只会使用各个类中定义的__slots__属性

定义__slots__的方式:
    1.创建一个类属性__slots__
    2.把它的值设为一个字符串构成的可迭代对象
    3.其中各个元素表示各个实例属性
    
__slots__的问题:
    1.每个子类都要定义__slots__属性,因为解释器会忽略掉继承的__slots__属性
    2.实例只能拥有__slots__列出的属性,除非把__dict__添加进__slots__中
        但这样就失去了节省内存的功能
    3.如果不把__weakref__加入__slots__,实例就不能作为弱引用的目标
"""

# 9.9 覆盖类属性
"""
类属性可用于为实例属性提供默认值
vector2d类有一个类属性typecode
__bytes__方法两次用到了它
因为Vetor2d实例没有typecode属性,所有self.typecode默认获取的是Vetor2d.typecode类属性的值
如果为不存在的实例属性赋值,同名类属性不受影响
    然而自此以后,实例读取的self.typecode就是实例属性typecode了
    也就是说把类属性Vetor2d.typecode覆盖了
    借助这个特性可以为不同的实例定制不同的typecode值
    typecode = 'd' 8字节双精度浮点数表示向量的各个分量
    typecode = 'f' 4字节单精度浮点数表示向量的各个分量
"""
v1 = Vector2d(1.1,2.2)
dumpd = bytes(v1)
print(dumpd)
print(len(dumpd))  # 17
v1.typecode = 'f'

dumpf = bytes(v1)
print(dumpf)
print(len(dumpf))  # 9

print(Vector2d.typecode)  # 'd' 只有实例的typecode属性变了,而类属性没有变

"""
要修改Vector2d的类属性,可以显式地Vector2d = 'f'
但是更符合python风格的方式是:
创建一个子类,只用于定制类的数据属性
"""

# 例9-14 ShortVector2d是Vector2d的子类,只用于覆盖typecode的值
from vector2d_v3 import Vector2d
class ShortVector2d(Vector2d):
    typecode = 'f'

sv = ShortVector2d(1/11,1/27)
print(repr(sv))
print(len(bytes(sv)))  # 9


# 本章小结
"""
使用特殊方法和约定的结构,定义行为良好的符合python风格的类
    所有用于获取字符串和字节序列表示形式的方法
        __repr__
        __str__
        __format__
        __bytes__
    把对象转换成数字的几个方法:
        __abs__
        __bool__
        __hash__
    用于测试字节序列转换和支持散列的__eq__方法
    备选的构造方法:frombytes()
    @calssmethod和@staticmethod
    私有属性和@property
    __slots__节省内存
    实例属性覆盖类属性
"""


vector2d_v0.py


# vevtor2d_v0.py 目前定义的都是特殊方法
from array import array
import math

class Vector2d:
    typecode = 'd'  # 类属性,在Vector2d实例和字节序列转化时使用
    def __init__(self,x , y):
        self.x = float(x) # 把x,y转换为浮点数,防止实例化的时候传入不当的参数
        self.y = float(y)

    def __iter__(self):  # 变成可迭代对象,这样才能拆包
        return (i for i in (self.x,self.y))
        # 另一种实现
        # yield self.x
        # yield 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)])+ # 把typecode转换成字节序列
                bytes(array(self.typecode,self)))  # 迭代Vector2d实例,得到一个数组,再转换成二进制序列

    def __eq__(self, other):  # 这样做会存在问题 如Vector2d(3,4) == [3,4]
        return tuple(self) == tuple(other)

    def __abs__(self): # x,y为直角的三角形的斜边长
        return math.hypot(self.x,self.y)

    def __bool__(self): # 把abs的结果转换为bool型
        return bool(abs(self))

    def __add__(self,other):
        x = self.x + other.x
        y = self.y + other.y
        return Vector2d(x,y)

    def __mul__(self, scalar):
        return Vector2d(self.x * scalar,self.y * scalar)

if __name__ == '__main__':
    v1 = Vector2d(3,4)
    print(v1.x,v1.y) # 实例的分量可以直接通过属性访问,
    x,y = v1 # 可以拆包成变量元组
    print(x, y)
    v1 # repr函数调用vextor2d实例,得到的结果类似于构建实例的源码
    v1_clone = eval(repr(v1)) # eval说明repr得到的是对构造方法的准确表述
    print(v1 == v1_clone)  # 支持==比较
    print(v1)  # print会调用__str__方法
    octets = bytes(v1)  # 调用__bytes__生成二进制表示形式
    print(octets)
    print(abs(v1))  # 调用__abs__返回Vector2d实例的模
    bool(v1)  # 调用__bool__,如果实例的模为0,返回False
    bool(Vector2d(0,0))


vector2d_v1.py


# vevtor2d_v1.py 备选构造方法,添加了frombytes() __format__()
from array import array
import math

class Vector2d:
    typecode = 'd'  # 类属性,在Vector2d实例和字节序列转化时使用
    def __init__(self,x , y):
        self.x = float(x) # 把x,y转换为浮点数,防止实例化的时候传入不当的参数
        self.y = float(y)

    def __iter__(self):  # 变成可迭代对象,这样才能拆包
        return (i for i in (self.x,self.y))
        # 另一种实现
        # yield self.x
        # yield 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)])+ # 把typecode转换成字节序列
                bytes(array(self.typecode,self)))  # 迭代Vector2d实例,得到一个数组,再转换成二进制序列

    def __eq__(self, other):  # 这样做会存在问题 如Vector2d(3,4) == [3,4]
        return tuple(self) == tuple(other)

    def __abs__(self): # x,y为直角的三角形的斜边长
        return math.hypot(self.x,self.y)

    def __bool__(self): # 把abs的结果转换为bool型
        return bool(abs(self))

    def __add__(self,other):
        x = self.x + other.x
        y = self.y + other.y
        return Vector2d(x,y)

    def __mul__(self, scalar):
        return Vector2d(self.x * scalar,self.y * scalar)

    #从字节序列转换成Vector2d实例
    @classmethod  # 类方法装饰器
    def frombytes(cls,octets):
        typecode = chr(octets[0])  # 从第一个字节读取typecode
        # 使用传入的octets字节序列创建一个memoryview,然后使用typecode转换
        memv = memoryview(octets[1:]).cast(typecode)
        return cls(*memv)

    # 使用格式说明符来格式化每一个分量
    # def __format__(self, format_spec=''):
    #     components = (format(c,format_spec) for c in self)
    #     return '({},{})'.format(*components)

    #在微语言中添加一个自定义的格式代码'p',在极坐标中显示向量,即<r,θ> r是模,θ是弧度

    # 定义angel方法计算角度
    def angel(self):
        return math.atan2(self.y,self.x)

    # 第二版__format__方法计算极坐标
    def __format__(self, fmt_spec=''):
        if fmt_spec.endswith('p'):  # 如果格式代码以p结尾,使用极坐标
            fmt_spec = fmt_spec[:-1]  # 从fmt_spec中删除p
            coords = (abs(self),self.angel())  # 构建元组表示极坐标
            outer_fmt = '<{},{}>'  # 把外层格式设为尖括号
        else:  # 不以p结尾的情况
            coords = self
            outer_fmt = '({},{})'
        components = (format(c,fmt_spec) for c in coords)  # 使用各个分量生成可迭代对象,构成格式化字符串
        return outer_fmt.format(*components)  # 带入外层格式

if __name__ == '__main__':
    v1 = Vector2d(3,4)
    print(v1.x,v1.y) # 实例的分量可以直接通过属性访问,
    x,y = v1 # 可以拆包成变量元组
    print(x, y)
    v1 # repr函数调用vextor2d实例,得到的结果类似于构建实例的源码
    v1_clone = eval(repr(v1)) # eval说明repr得到的是对构造方法的准确表述
    print(v1 == v1_clone)  # 支持==比较
    print(v1)  # print会调用__str__方法
    octets = bytes(v1)  # 调用__bytes__生成二进制表示形式
    print(octets)
    print(abs(v1))  # 调用__abs__返回Vector2d实例的模
    bool(v1)  # 调用__bool__,如果实例的模为0,返回False
    bool(Vector2d(0,0))
    v3 = Vector2d.frombytes(b'd\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@')
    print(v3)
    # 如果类没有定义__format__, 从object继承的方法会返回str(my_object)
    # 然而如果传入格式说明符, 会报错
    print(format(v3))
    print(format(v3,'.2f'))  # TypeError: unsupported format string passed to Vector2d.__format__
    # 定义了__format__以后的结果 (3.00,4.00)
    print(format(Vector2d(1,1), 'p'))
    print(format(Vector2d(1,1), '.2ep'))
    print(format(Vector2d(1,1), '0.5fp'))

    hash(Vector2d(3,4))


vector2d_v3.py


# vevtor2d_v3.py 增加不可变的属性 以实现__hash__()
from array import array
import math

class Vector2d:
    # python会在各个实例中使用类似元组的结构存储实例变量
    # __slots__ = ('__x','__y')  # 这个类的所有实例属性都在这儿了

    typecode = 'd'  # 类属性,在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 __hash__(self):
        return hash(self.x) ^ hash(self.y)

    def __iter__(self):  # 变成可迭代对象,这样才能拆包
        return (i for i in (self.x,self.y))
        # 另一种实现
        # yield self.x
        # yield 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)])+ # 把typecode转换成字节序列
                bytes(array(self.typecode,self)))  # 迭代Vector2d实例,得到一个数组,再转换成二进制序列

    def __eq__(self, other):  # 这样做会存在问题 如Vector2d(3,4) == [3,4]
        return tuple(self) == tuple(other)

    def __abs__(self): # x,y为直角的三角形的斜边长
        return math.hypot(self.x,self.y)

    def __bool__(self): # 把abs的结果转换为bool型
        return bool(abs(self))

    def __add__(self,other):
        x = self.x + other.x
        y = self.y + other.y
        return Vector2d(x,y)

    def __mul__(self, scalar):
        return Vector2d(self.x * scalar,self.y * scalar)

    #从字节序列转换成Vector2d实例
    @classmethod  # 类方法装饰器
    def frombytes(cls,octets):
        typecode = chr(octets[0])  # 从第一个字节读取typecode
        # 使用传入的octets字节序列创建一个memoryview,然后使用typecode转换
        memv = memoryview(octets[1:]).cast(typecode)
        return cls(*memv)

    # 使用格式说明符来格式化每一个分量
    # def __format__(self, format_spec=''):
    #     components = (format(c,format_spec) for c in self)
    #     return '({},{})'.format(*components)

    #在微语言中添加一个自定义的格式代码'p',在极坐标中显示向量,即<r,θ> r是模,θ是弧度

    # 定义angel方法计算角度
    def angel(self):
        return math.atan2(self.y,self.x)

    # 第二版__format__方法计算极坐标
    def __format__(self, fmt_spec=''):
        if fmt_spec.endswith('p'):  # 如果格式代码以p结尾,使用极坐标
            fmt_spec = fmt_spec[:-1]  # 从fmt_spec中删除p
            coords = (abs(self),self.angel())  # 构建元组表示极坐标
            outer_fmt = '<{},{}>'  # 把外层格式设为尖括号
        else:  # 不以p结尾的情况
            coords = self
            outer_fmt = '({},{})'
        components = (format(c,fmt_spec) for c in coords)  # 使用各个分量生成可迭代对象,构成格式化字符串
        return outer_fmt.format(*components)  # 带入外层格式

    def __complex__(self):
        '''用于支持complex(my_vector2d)'''
        real = self.x
        img = self.y
        return complex(real,img)

if __name__ == '__main__':
    v1 = Vector2d(3,4)
    print(v1.x,v1.y) # 实例的分量可以直接通过属性访问,
    x,y = v1 # 可以拆包成变量元组
    print(x, y)
    v1 # repr函数调用vextor2d实例,得到的结果类似于构建实例的源码
    v1_clone = eval(repr(v1)) # eval说明repr得到的是对构造方法的准确表述
    print(v1 == v1_clone)  # 支持==比较
    print(v1)  # print会调用__str__方法
    octets = bytes(v1)  # 调用__bytes__生成二进制表示形式
    print(octets)
    print(abs(v1))  # 调用__abs__返回Vector2d实例的模
    bool(v1)  # 调用__bool__,如果实例的模为0,返回False
    bool(Vector2d(0,0))
    v3 = Vector2d.frombytes(b'd\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@')
    print(v3)
    # 如果类没有定义__format__, 从object继承的方法会返回str(my_object)
    # 然而如果传入格式说明符, 会报错
    print(format(v3))
    print(format(v3,'.2f'))  # TypeError: unsupported format string passed to Vector2d.__format__
    # 定义了__format__以后的结果 (3.00,4.00)
    print(format(Vector2d(1,1), 'p'))
    print(format(Vector2d(1,1), '.2ep'))
    print(format(Vector2d(1,1), '0.5fp'))

    print(hash(Vector2d(3, 4)))
    print(hash(Vector2d(3, 4.1)))
    print(complex(Vector2d(3, -5)))


35岁学Python,也不知道为了啥?