《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也支持啊!

你不逼自己一把,你永远都不知道自己有多优秀!只有经历了一些事,你才会懂得好好珍惜眼前的时光!
原文地址:https://www.cnblogs.com/zhazhaacmer/p/14407800.html