zoukankan      html  css  js  c++  java
  • 《Fluent Python》 CH.09_面向对象_符合Python风格的对象

    其他

    • 转换命令: jupyter nbconvert --to markdown E:PycharmProjectsTianChiProject0_山枫叶纷飞competitions13_fluent_pythonCH.09_面向对象_符合Python风格的对象.ipynb

    主要内容

    本章讨论

    • 支持用于生成对象的其他的表示形式的内置函数(如 repr()、bytes(),等等)
    • 使用一个类方法实现备选构造方法 (重载构造器)
    • 扩展内置的 format() 函数和 str.format() 方法使用的格式微语言
    • 实现只读属性
    • 把对象变为可散列的,以便在集合中及作为 dict 的键使用
    • 利用 slots 节省内存

    我们将开发一个简单的二维欧几里得向量类型,在这个过程中涵盖上述
    全部话题。

    在实现这个类型的中间阶段,我们会讨论两个概念: 如何以及何时使用 @classmethod 和 @staticmethod 装饰器 Python 的私有属性和受保护属性的用法、约定和局限 我们从对象表示形式函数开始。

    内容补充

    • Python 占位符格式,r : 获取传入对象的__repr__方法的返回值,并将其格式化到指定位置。
    • 间接证明所有类的父类都是object。(java里是这样子,Python应该也是的)

    9.1 对象表示形式

    Python 提供了两种方式。

    • repr() 以便于开发者理解的方式返回对象的字符串表示形式。
    • str()以便于用户理解的方式返回对象的字符串表示形式。

    正如你所知,我们要实现 reprstr 特殊方法,为 repr() 和 str() 提供支持。

    为了给对象提供其他的表示形式,还会用到另外两个特殊方 法:bytesformat

    bytes 方法与 str 方法类 似:bytes() 函数调用它获取对象的字节序列表示形式。

    format 方法会被内置的 format() 函数和 str.format() 方法调 用,使用特殊的格式代码显示对象的字符串表示形式。

    9.2 再谈示例的向量类

    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): # 模是 x 和 y 分量构成的直角三角形的斜边长
            return math.hypot(self.x, self.y)
        def __bool__(self): #  __bool__ 方法使用 abs(self) 计算模,然后把结果转换成布尔 值,因此,0.0 是 False,非零值是 True。
            return bool(abs(self))
    
    
    v1 = Vector2d(3, 4)
    
    v1
    
    Vector2d(3.0, 4.0)
    
    v1.__repr__()
    
    
    'Vector2d(3.0, 4.0)'
    
    v2 = Vector2d(3, 4)
    
    v1==v2
    
    True
    

    间接证明所有类的父类都是object

    isinstance(v1, object)
    
    
    True
    
    isinstance(str, object)
    
    
    True
    

    9.3 备选构造方法 (从字节进行反序列化)

    使用传入的 octets 字节序列创建一个 memoryview,然后使用 typecode 转换。
    然后拆包转换后的 memoryview,得到构造方法所需的一对参数。

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

    9.4 classmethod与staticmethod

    • classmethod,通过类名进行调用,常用于进行类的构造方法的重载时使用。(类行为)

    • staticmethod,静态方法,普通的函数。(没地方放的普通方法,不用强制传入self的方法)

    class Demo:
        @classmethod
        def my_classmethod(*args):
            return args
        @staticmethod
        def my_staticmethod(*args):
            return args
    Demo.my_classmethod()
    
    (__main__.Demo,)
    
    Demo.my_staticmethod()
    
    ()
    

    9.5 格式化显示

    内置的 format() 函数和 str.format() 方法把各个类型的格式化方式 委托给相应的 .format(format_spec) 方法。format_spec 是格 式说明符,它是: format(my_obj, format_spec) 的第二个参数,或者 str.format() 方法的格式字符串,{} 里代换字段中冒号后面的部 分.

    后续略过.

    9.6 实现可散列的类 Vector2d

    重写类的__eq__方法和__hash__ 方法。

    v1 = Vector2d(3, 4)
    hash(v1)
    
    
    ---------------------------------------------------------------------------
    
    TypeError                                 Traceback (most recent call last)
    
    <ipython-input-12-d77c92cec0f2> in <module>
          1 v1 = Vector2d(3, 4)
    ----> 2 hash(v1)
          3 
          4 
    
    
    TypeError: unhashable type: 'Vector2d'
    
    v1.x = 7
    v1
    
    Vector2d(7, 4.0)
    

    重写一版可以hash的二维向量类

    class Vector2d:
        typecode = 'd'
        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 __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): # 模是 x 和 y 分量构成的直角三角形的斜边长
            return math.hypot(self.x, self.y)
        def __bool__(self): #  __bool__ 方法使用 abs(self) 计算模,然后把结果转换成布尔 值,因此,0.0 是 False,非零值是 True。
            return bool(abs(self))
        def __hash__(self): # 虽然会hash碰撞,但是eq并不相等
            return hash(self.x) ^ hash(self.y)
    
    
    v3 = Vector2d(3, 4)
    v4 = Vector2d(4, 3)
    v3==v4
    
    False
    
    hash(v3) == hash(v4)
    
    
    True
    
    st = set()
    st.add(v3)
    st.add(v4)
    st
    
    {Vector2d(3.0, 4.0), Vector2d(4.0, 3.0)}
    

    9.7 Python的私有属性和“受保护的”属性

    Python 不能像 Java 那样使用 private 修饰符创建私有属性,但是 Python 有个简单的机制,能避免子类意外覆盖“私有”属性。
    这个语言特性叫名称改写,name mangling。
    作用:
    为了避免子类覆盖掉父类的属性的情况,如果以 __mood 的形式(两个前导下划线,尾部没有或最多有一个下划线)命名实例属性,Python 会把属性名存入实例的 dict 属性中,而且会在前面加上一个下划线和类名。因此,对 Dog 类来说,__mood 会变成 _Dog__mood;对 Beagle 类来说,会变成 _Beagle__mood。

    #### 示例 9-10 私有属性的名称会被“改写”,在前面加上下划线和类名
    
    class Vector2d:
        typecode = 'd'
        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 __repr__(self):
            class_name = type(self).__name__
            return '{}({!r}, {!r})'.format(class_name, *self)
        def __str__(self): return str(tuple(self))
    
    
    v1 = Vector2d(3, 4)
    v1.__dict_
    
    {'_Vector2d__x': 3.0, '_Vector2d__y': 4.0}
    
    print('直接改写会引发异常!')
    v1.x = 344
    
    ---------------------------------------------------------------------------
    
    AttributeError                            Traceback (most recent call last)
    
    <ipython-input-30-e00c4012d440> in <module>
    ----> 1 v1.x = 344
          2 
    
    
    AttributeError: can't set attribute
    
    print('使用名称改写的方式还是可以进行私有化属性的改写')
    v1._Vector2d__x = 344
    v1._Vector2d__x
    
    344
    

    9.8 使用 __slots__ 类属性节省空间

    默认情况下,Python 在各个实例中名为 dict 的属性(字典)里存储实例属性。
    为了使用底层的散列表提升访问速度,字典会消 耗大量内存。如果要处理数百万个属性不多的实例,通过 slots 类属性,能节省大量内存,方法是让解释器在元组中存储实例属性,而 不用字典。

    示例 9-11 vector2d_v3_slots.py:只在 Vector2d 类中添加了 slots 属性

    class Vector2d:
        __slots__ = ('__x', '__y')
        typecode = 'd'
    

    在类中定义 slots 属性的目的是告诉解释器:“这个类中的所有实 例属性都在这儿了!”这样,Python 会在各个实例中使用类似元组的结 构存储实例变量,从而避免使用消耗内存的 dict 属性。如果有数 百万个实例同时活动,这样做能节省大量内存。

    slots 属性有些需要注意的地方

    • 不能滥用,不能使用 它限制用户能赋值的属性。
    • 处理列表数据时 slots 属性最有用, 例如模式固定的数据库记录,以及特大型数据集。
    • 每个子类都要定义 slots 属性,因为解释器会忽略继承的 slots 属性。
    • 实例只能拥有 slots 中列出的属性,除非把 'dict' 加 入 slots 中(这样做就失去了节省内存的功效)。
    • 如果不把 'weakref' 加入 slots,实例就不能作为弱引 用的目标。

    9.9 覆盖类属性

    Python 有个很独特的特性:类属性可用于为实例属性提供默认 值。

    这个Java也支持啊!

    你不逼自己一把,你永远都不知道自己有多优秀!只有经历了一些事,你才会懂得好好珍惜眼前的时光!
  • 相关阅读:
    vue_源码 原理 剖析
    vue_vuex
    vue_VueRouter 路由_路由器管理n个路由_并向路由组件传递数据_新标签路由_编程式路由导航
    vue_mint-ui
    vue_ajax 请求
    vue_组件间通信:自定义事件、消息发布与订阅、槽
    vue_小项目_吃饭睡觉打豆豆
    vue-cli 脚手架 Command Line Interface
    程序员面试金典-面试题 04.03. 特定深度节点链表
    程序员面试金典-面试题 04.02. 最小高度树
  • 原文地址:https://www.cnblogs.com/zhazhaacmer/p/14407800.html
Copyright © 2011-2022 走看看