Python 部分特殊方法的使用

Python 部分特殊方法的使用,第1张

Python 部分特殊方法的使用

摘自流畅的python 第九章 符合Python风格的对象

什么是特殊方法

它们在面向对象的Python的处处皆是。它们是一些可以让你对类添加“魔法”的特殊方法。它们经常是两个下划线包围来命名的(比如 __init__ , __lt__ )


接下来让我们在代码中了解特殊方法的妙用

vector2d_v3_slots.py

我们封装一个类 用于描述矢量 ,该类有两个私有属性 x,y 分表表示坐标

class Vector2d:

    def __init__(self, x, y):
        # 在__init__方法中把x和y转换成浮点数,尽早捕获错误,以防调用Vector2d函数时传入不当参数
        self.__x = float(x)  # 使用两个前导下划线(尾部没有下划线,或者有一个下划线),把属性标记为私有
        self.__y = float(y)
        
    @property  # 该装饰器把读值方法标记为特性
    def x(self):  # 读值方法与公开属性同名,都是x
        '''
        该装饰器装饰的变量 需要单独重构赋值方式,如果未重构将会出现一下报错 。 在此示例中 x 变量只能访问
        a = Vector2d(1,1)
        print(a.x)
        a.x = 2
        1.0
        Traceback (most recent call last):
          File "/Users/coco/PycharmProjects/FluentPython/09-pythonic-obj/vector2d_v3.py", line 158, in 
            a.x = 2
        AttributeError: can't set attribute
        :return:
        '''
        return self.__x  # 直接返回self.__x

    @property
    def y(self):
        return self.__y
__iter__
    def __iter__(self):
        '''
        定义 :使该对象可迭代
        示例 :
        a = Vector2d(1,1)
        x,y = a
        print(x,y)
        1.0 1.0
        print(*a)
        1.0 1.0
        :return:
        '''
        # 定义__iter__方法,把Vector2d实例变成可迭代对象,这样才能拆包(例如,x,y = my_vector)。这个方法的实现方式很简单,直接调用生成器表达式一个接一个产出分量
        return (i for i in (self.x, self.y))
__repr__
    def __repr__(self):
        '''
        定义 :重构交互模式下打印 (例如:Python Console下的打印)
        示例 :
        在Python Console 中定义如下变量
        >>> a = Vector2d(1,1)
        >>> a
        Vector2d(1.0,1.0)
        与 __str__ 回显并不相同
        当 __str__ 方法未重构时
        print() 会调用__repr__
        :return:
        '''
        class_name = type(self).__name__
        # __repr__方法使用{!r}获取各个分量的表示形式,然后插值,构成一个字符串;因为Vector2d实例是可迭代的对象,所以*self会把x和y分量提供给format函数
        return '{}({!r},{!r})'.format(class_name, *self)
__str__
    def __str__(self):
        '''
        定义 :重构用户打印
        示例:
        分别在实现与实现的情况下 运行这条语句 print(Vector2d(1,1))
        不实现该方法时 打印如下
        <__main__.Vector2d object at 0x7ffc726e8fa0>
        实现该方法时 打印如下
        (1.0, 1.0)
        :return:
        '''
        return str(tuple(self))  # 从可迭代的Vector2d实例中可以轻松地得到一个元祖,显示为一个有序对

__bytes__
    def __bytes__(self):
        '''
        定义 : 重构bytes() 方法 返回字节序列的逻辑
        打印 :
        print(bytes(Vector2d(1,1)))
        b'dx00x00x00x00x00x00xf0?x00x00x00x00x00x00xf0?'
        :return:
        '''
        # 为了生成字节序列,我们typecode转换成字节序列,然后
        return bytes([ord(self.typecode)]) + 
               bytes(array(self.typecode, self))  # 迭代Vector2d实例,得到一个数组,再把数组转换成字节序列

__eq__
    def __eq__(self, other):
        '''
        定义 :重构 == 运算逻辑
        示例 :
        print(Vector2d(1,1) == Vector2d(1,1))
        True
        print(Vector2d(1,1) == Vector2d(1,2))
        False
        :param other:
        :return:
        '''
        # 为了快速比较所有分量,在 *** 作数中构建元祖。对Vector2d实例来说,可以这样做,不过仍有问题。参见下面的警告
        # 该方法将自身转换成元祖与比较对象比较,在进行如下比较时 也会输出True,这显然是不符合预期的,可以在方法中添加对象类型的判断,这样更严谨
        # print(Vector2d(1, 1) == (1, 1))
        return tuple(self) == tuple(other)
__abs__
 def __abs__(self):
        '''
        定义 : 重构abs() 判断逻辑
        示例 :
        print(abs(Vector2d(1,1)))
        1.4142135623730951
        :return:
        '''
        return math.hypot(self.x, self.y)  # 模是x和y分量构成的直角三角形的斜边长
__bool__
 def __bool__(self):
        '''
        定义 : 重构bool() 判断逻辑
        示例 :
        print(bool(Vector2d(1,1)))
        True
        :return:
        '''
        return bool(abs(self))  # __bool__方法使用abs(self)计算模,然后把结果转换成布尔值,因此,0.0是False,非零值是True

__fotmat__
    def __format__(self, format_spec):
        '''
        定义 : 重构format() 运行逻辑
        示例 :
        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),'.5fp'))
        <1.41421, 0.78540>
        :param format_spec:
        :return:
        '''
        if format_spec.endswith('p'):  # 如果格式代码以'p'结尾,使用极坐标
            format_spec = format_spec[:-1]  # 从fmt_spec中删除'p'后缀
            coords = (abs(self), self.angle())  # 构建一个元祖,表示极坐标:(magnitude,angle)
            outer_fmt = '<{}, {}>'  # 把外层格式设为一对尖括号
        else:
            coords = self  # 如果不以'p'结尾,使用self的x和y分量构建直角坐标
            outer_fmt = '({}, {})'  # 把外层格式设为一对圆括号
        components = (format(c, format_spec) for c in coords)  # 使用各个分量生成可迭代的对象,构成格式化字符串
        return outer_fmt.format(*components)  # 把格式化字符串代入外层格式

__hash__
    def __hash__(self):
        '''
        定义 : 试该对象可散列
        示例 :
        v1 = Vector2d(3,4)
        v2 = Vector2d(3.1,4.2)
        print(hash(v1))
        print(hash(v2))
        print(set([v1,v2]))
        7
        384307168202284039
        {Vector2d(3.1,4.2), Vector2d(3.0,4.0)}

        :return:
        '''
        return hash(self.x) ^ hash(self.y)

小结

以上使用的特殊方法和约定的机构,定义了行为良好且符合Python风格的类

用于获取字符串和字节序列表示形式的方法:__repr__,__str__,__format__和__bytes__把对象转换成数字的方法:__abs__,__bool__,__hash__用于测试字节序列转换和支持散列(连同__hash__方法)的__eq__运算符 完整代码

from array import array
import math


class Vector2d:
    typecode = 'd'  # typecode 是类属性,在Vector2d实例和字节序列之间转换时使用
    # 在类中定义__slots__属性的目的是告诉解释器:这个类多有的实例属性都在这儿了,Python 会在各个实例中使用类似元祖的结构存储实例变量,
    # 从而避免使用消耗内存的__dict__属性。如果有百万个实例同时活动,这样做能节省大量内存
    __slots__ = ('__x', '__y')

    def __init__(self, x, y):
        # 在__init__方法中把x和y转换成浮点数,尽早捕获错误,以防调用Vector2d函数时传入不当参数
        self.__x = float(x)  # 使用两个前导下划线(尾部没有下划线,或者有一个下划线),把属性标记为私有
        self.__y = float(y)

    @property  # 该装饰器把读值方法标记为特性
    def x(self):  # 读值方法与公开属性同名,都是x
        '''
        该装饰器装饰的变量 需要单独重构赋值方式,如果未重构将会出现一下报错 。 在此示例中 x 变量只能访问
        a = Vector2d(1,1)
        print(a.x)
        a.x = 2
        1.0
        Traceback (most recent call last):
          File "/Users/coco/PycharmProjects/FluentPython/09-pythonic-obj/vector2d_v3.py", line 158, in 
            a.x = 2
        AttributeError: can't set attribute
        :return:
        '''
        return self.__x  # 直接返回self.__x

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

    def __iter__(self):
        '''
        定义 :使该对象可迭代
        示例 :
        a = Vector2d(1,1)
        x,y = a
        print(x,y)
        1.0 1.0
        print(*a)
        1.0 1.0
        :return:
        '''
        # 定义__iter__方法,把Vector2d实例变成可迭代对象,这样才能拆包(例如,x,y = my_vector)。这个方法的实现方式很简单,直接调用生成器表达式一个接一个产出分量
        return (i for i in (self.x, self.y))

    def __repr__(self):
        '''
        定义 :重构交互模式下打印 (例如:Python Console下的打印)
        示例 :
        在Python Console 中定义如下变量
        >>> a = Vector2d(1,1)
        >>> a
        Vector2d(1.0,1.0)
        与 __str__ 回显并不相同
        当 __str__ 方法未重构时
        print() 会调用__repr__
        :return:
        '''
        class_name = type(self).__name__
        # __repr__方法使用{!r}获取各个分量的表示形式,然后插值,构成一个字符串;因为Vector2d实例是可迭代的对象,所以*self会把x和y分量提供给format函数
        return '{}({!r},{!r})'.format(class_name, *self)

    def __str__(self):
        '''
        定义 :重构用户打印
        示例:
        分别在实现与实现的情况下 运行这条语句 print(Vector2d(1,1))
        不实现该方法时 打印如下
        <__main__.Vector2d object at 0x7ffc726e8fa0>
        实现该方法时 打印如下
        (1.0, 1.0)
        :return:
        '''
        return str(tuple(self))  # 从可迭代的Vector2d实例中可以轻松地得到一个元祖,显示为一个有序对

    def __bytes__(self):
        '''
        定义 : 重构bytes() 方法 返回字节序列的逻辑
        打印 :
        print(bytes(Vector2d(1,1)))
        b'dx00x00x00x00x00x00xf0?x00x00x00x00x00x00xf0?'
        :return:
        '''
        # 为了生成字节序列,我们typecode转换成字节序列,然后
        return bytes([ord(self.typecode)]) + 
               bytes(array(self.typecode, self))  # 迭代Vector2d实例,得到一个数组,再把数组转换成字节序列

    def __eq__(self, other):
        '''
        定义 :重构 == 运算逻辑
        示例 :
        print(Vector2d(1,1) == Vector2d(1,1))
        True
        print(Vector2d(1,1) == Vector2d(1,2))
        False
        :param other:
        :return:
        '''
        # 为了快速比较所有分量,在 *** 作数中构建元祖。对Vector2d实例来说,可以这样做,不过仍有问题。参见下面的警告
        # 该方法将自身转换成元祖与比较对象比较,在进行如下比较时 也会输出True,这显然是不符合预期的,可以在方法中添加对象类型的判断,这样更严谨
        # print(Vector2d(1, 1) == (1, 1))
        return tuple(self) == tuple(other)

    def __abs__(self):
        '''
        定义 : 重构abs() 判断逻辑
        示例 :
        print(abs(Vector2d(1,1)))
        1.4142135623730951
        :return:
        '''
        return math.hypot(self.x, self.y)  # 模是x和y分量构成的直角三角形的斜边长

    def __bool__(self):
        '''
        定义 : 重构bool() 判断逻辑
        示例 :
        print(bool(Vector2d(1,1)))
        True
        :return:
        '''
        return bool(abs(self))  # __bool__方法使用abs(self)计算模,然后把结果转换成布尔值,因此,0.0是False,非零值是True

    def angle(self):
        # math.atan2 方法以弧度返回y / x的反正切
        return math.atan2(self.y, self.x)

    def __format__(self, format_spec):
        '''
        定义 : 重构format() 运行逻辑
        示例 :
        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),'.5fp'))
        <1.41421, 0.78540>
        :param format_spec:
        :return:
        '''
        if format_spec.endswith('p'):  # 如果格式代码以'p'结尾,使用极坐标
            format_spec = format_spec[:-1]  # 从fmt_spec中删除'p'后缀
            coords = (abs(self), self.angle())  # 构建一个元祖,表示极坐标:(magnitude,angle)
            outer_fmt = '<{}, {}>'  # 把外层格式设为一对尖括号
        else:
            coords = self  # 如果不以'p'结尾,使用self的x和y分量构建直角坐标
            outer_fmt = '({}, {})'  # 把外层格式设为一对圆括号
        components = (format(c, format_spec) for c in coords)  # 使用各个分量生成可迭代的对象,构成格式化字符串
        return outer_fmt.format(*components)  # 把格式化字符串代入外层格式

    def __hash__(self):
        '''
        定义 : 试该对象可散列
        示例 :
        v1 = Vector2d(3,4)
        v2 = Vector2d(3.1,4.2)
        print(hash(v1))
        print(hash(v2))
        print(set([v1,v2]))
        7
        384307168202284039
        {Vector2d(3.1,4.2), Vector2d(3.0,4.0)}

        :return:
        '''
        return hash(self.x) ^ hash(self.y)

    @classmethod  # 类方法使用classmethod装饰器修饰
    def frombytes(cls, octets):  # 不用传入self参数;相反,要通过cls传入类本身
        typecode = chr(octets[0])  # 从第一个字节中读取typecode
        memv = memoryview(octets[1:]).cast(typecode)  # 使用传入的octets字节序列创建一个memoryview,然后使用typecode转换
        return cls(*memv)  # 拆包转换后的memoryview,得到构造方法所需的一对参数

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5721085.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-18
下一篇 2022-12-18

发表评论

登录后才能评论

评论列表(0条)

保存