[python 进阶] 9. 符合Python风格的对象

文章目录

    • 9.1 对象表示形式
    • 9.2 再谈向量类
    • 9.3 备选构造方法
    • 9.4 classmethod与staticmethod
    • 9.5 格式化显示
    • 9.6 可散列的Vector2d
    • 什么是可散列的数据类型
    • 9.6 可散列的Vector
    • 9.7 Python的私有属性和“受保护的”属性
    • 9.8 使用 __slots__ 类属性节省空间

本章包含以下话题:

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

我们将开发一个简单的二维欧几里得向量类型,在这个过程中涵盖上述全部话题。
在实现这个类型的中间阶段,我们会讨论两个概念:

  • 如何以及何时使用 @classmethod 和 @staticmethod 装饰器
  • Python 的私有属性和受保护属性的用法、约定和局限
    我们从对象表示形式函数开始。

9.1 对象表示形式

每门面向对象的语言至少都有一种获取对象的字符串表示形式的标准方式。Python 提供了
两种方式。

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

为了给对象提供其他的表示形式,还会用到另外两个特殊方法:_bytes_ 和_format_。_bytes_ 方法与 _str_ 方法类似:bytes() 函数调用它获取对象的字节序列表示形式。而 _format_ 方法会被内置的 format() 函数和 str.format() 方法调用,使用特殊的格式代码显示对象的字符串表示形式。
记住,在 Python 3 中,

  • _repr_、_str_ 和 _format_ 都必须返回 Unicode 字符串(str 类型)。
  • 只有_bytes_ 方法应该返回字节序列(bytes 类型)

9.2 再谈向量类

from array import array
import mathclass Vector2d:typecode='d'# typecode是类属性def __init__(self, x, y):self.x = xself.y = ydef __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):return math.hypot(self.x, self.y) #模是 x 和 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_clone == v1)
print(v1)
octets = bytes(v1)
print(octets)
print(abs(v1))

9.3 备选构造方法

我们可以把 Vector2d 实例转换成字节序列了;同理,也应该能从字节序列转换成Vector2d 实例。在标准库中探索一番之后,我们发现 array.array 有个类方法.frombytes(2.9.1 节介绍过)正好符合需求。下面在 vector2d_v1.py(见示例 9-3)中为Vector2d 定义一个同名类方法。

@classmethod ➊
def frombytes(cls, octets): ➋typecode = chr(octets[0]) ➌memv = memoryview(octets[1:]).cast(typecode) ➍return cls(*memv) ➎

❶ 类方法使用 classmethod 装饰器修饰。
❷ 不用传入 self 参数;相反,要通过 cls 传入类本身。
❸ 从第一个字节中读取 typecode。
❹ 使用传入的 octets 字节序列创建一个 memoryview,然后使用 typecode 转换。
2.9.2 节简单介绍过 memoryview,说明了它的 .cast 方法。
❺ 拆包转换后的 memoryview,得到构造方法所需的一对参数。

9.4 classmethod与staticmethod

先来看 classmethod。示例 9-3 展示了它的用法:定义操作类,而不是操作实例的方法。classmethod 改变了调用方法的方式,因此类方法的第一个参数是类本身,而不是实例。classmethod 最常见的用途是定义备选构造方法,例如示例 9-3 中的
frombytes。注意,frombytes 的最后一行使用 cls 参数构建了一个新实例,即cls(*memv)。按照约定,类方法的第一个参数名为 cls(但是 Python 不介意具体怎么命名)。
staticmethod 装饰器也会改变方法的调用方式,但是第一个参数不是特殊的值。其实,静态方法就是普通的函数,只是碰巧在类的定义体中,而不是在模块层定义。

>>> class Demo:
...     @classmethod
...     def klassmeth(*args):
...             return args
...     @staticmethod
...     def statmeth(*args):
...             return args
... 
>>> Demo.klassmeth()
(<class '__main__.Demo'>,)
>>> Demo.statmeth()
()
>>> Demo.klassmeth('spam')
(<class '__main__.Demo'>, 'spam')

9.5 格式化显示

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

>>> br1 = 1/2.43
>>> br1
0.4115226337448559
>>> format(br1, '0.4f')
'0.4115'
>>> '1 BRL={rate:0.2f} USD'.format(rate=br1)
'1 BRL=0.41 USD'

格式规范微语言为一些内置类型提供了专用的表示代码。比如,b 和 x 分别表示二进制和十六进制的 int 类型,f 表示小数形式的 float 类型,而 % 表示百分数形式:

>>> format(42,'b')
'101010'
>>> format(2/3, '.1%')
'66.7%'

下面是内置的 format() 函数和 str.format() 方法的几个示例

>>> from datetime import datetime
>>> now= datetime.now()
>>> format(now, '%H:%M:%S')
'18:35:23'
>>> "Its now {:%I:%M %p}".format(now)
'Its now 06:35 PM'

如果类没有定义 format 方法,从 object 继承的方法会返回 str(my_object)。我
们为 Vector2d 类定义了 str 方法,因此可以这样做:

>>> v1 = Vector2d(3, 4)
>>> format(v1)
'(3.0, 4.0)'

然而,如果传入格式说明符,object.format 方法会抛出 TypeError:

>>> format(v1, '.3f')
Traceback (most recent call last):
...
TypeError: non-empty format string passed to object.__format__

我们将实现自己的微语言来解决这个问题。首先,假设用户提供的格式说明符是用于格式
化向量中各个浮点数分量的。我们想达到的效果是:

>>> v1 = Vector2d(3, 4)
>>> format(v1)
'(3.0, 4.0)'
>>> format(v1, '.2f')
'(3.00, 4.00)'
>>> format(v1, '.3e')
'(3.000e+00, 4.000e+00)'

实现这种输出的 format 方法如示例 9-5 所示。
示例 9-5 Vector2d._format_ 方法,第 1 版

# 在Vector2d类中定义
def __format__(self, fmt_spec=''):components = (format(c, fmt_spec) for c in self)  return '({}, {})'.format(*components) 

对极坐标来说,我们已经定义了计算模的 abs 方法,因此还要定义一个简单的
angle 方法,使用 math.atan2() 函数计算角度。angle 方法的代码如下:

# 在Vector2d类中定义
def angle(self):return math.atan2(self.y, self.x)

这样便可以增强 format 方法,计算极坐标,如示例 9-6 所示。
示例 9-6 Vector2d.format 方法,第 2 版,现在能计算极坐标了

def __format__(self, fmt_spec=''):if fmt_spec.endswith('p'): fmt_spec = fmt_spec[:-1] coords = (abs(self), self.angle()) outer_fmt = '<{}, {}>' else:coords = self outer_fmt = '({}, {})' components = (format(c, fmt_spec) for c in coords) return outer_fmt.format(*components) 

9.6 可散列的Vector2d

按照定义,目前 Vector2d 实例是不可散列的,因此不能放入集合(set)中:

>>> v1 = Vector2d(3, 4)
>>> hash(v1)
Traceback (most recent call last):
...
TypeError: unhashable type: 'Vector2d'
>>> set([v1])
Traceback (most recent call last):
...
TypeError: unhashable type: 'Vector2d'

为了把 Vector2d 实例变成可散列的,必须使用 hash 方法(还需要 eq 方法,前面已经实现了)。

什么是可散列的数据类型

可散列的(hashable)
在散列值永不改变,而且如果 a == b,那么 hash(a) == hash(b) 也是 True 的情况下,如果对象既有 _hash_ 方法,也有 _eq_ 方法,那么这样的对象称为可散列的对象。在内置的类型中,大多数不可变的类型都是可散列的;但是,仅当元组的每一个元素都是可散列的时,元组才是可散列的。

  • 如果一个对象是可散列的,那么在这个对象的生命周期中,它的散列值是不变的,而且这个对象需要实现 _hash_() 方法。另外可散列对象还要有_eq_() 方法,这样才能跟其他键做比较。如果两个可散列对象是相等的,那么它们的散列值一定是一样的……
  • 原子不可变数据类型(str、bytes 和数值类型)都是可散列类型,frozenset 也是可散列的,因为根据其定义,frozenset 里只能容纳可散列类型。元组的话,只有当一个元组包含的所有元素都是可散列类型的情况下,它才是可散列的。

有这么一句话“Python 里所有的不可变类型都是可散列的”。这个说法其实是不准确的,比如虽然元组本身是不可变序列,它里面的元素可能是其他可变类型的引用。

一般来讲用户自定义的类型的对象都是可散列的,散列值就是它们的 id() 函数的返回值,所以所有这些对象在比较的时候都是不相等的。如果一个对象实现了 _eq_ 方法,并且在方法中用到了这个对象的内部状态的话,那么只有当所有这些内部状态都是不可变的情况下,这个对象才是可散列的。

9.6 可散列的Vector

from array import array
import mathclass Vector2d:typecode='d'# typecode是类属性def __init__(self, x, y):self.__x = float(x) #使用两个前导下划线(尾部没有下划线,或者有一个下划线),把属性标记为私有self.__y = float(y)@property # @property 装饰器把读值方法标记为特性def x(self):return self.__x@propertydef y(self):return self.__ydef __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):return math.hypot(self.x, self.y) #模是 x 和 y 分量构成的直角三角形的斜边长def __bool__(self):return bool(abs(self))v1 = Vector2d(3, 4)

注意,我们让这些向量不可变是有原因的,因为这样才能实现 hash 方法。这个方法应该返回一个整数,理想情况下还要考虑对象属性的散列值(eq 方法也要使用),因为相等的对象应该具有相同的散列值。
要想创建可散列的类型,不一定要实现特性,也不一定要保护实例属性。只需正确地实现 hasheq 方法即可。但是,实例的散列值绝不应该变化,因此我们借机提到了只读特性。
如果定义的类型有标量数值,可能还要实现 intfloat 方法(分别被 int()和 float() 构造函数调用),以便在某些情况下用于强制转换类型。此外,还有用于支持内置的 complex() 构造函数的 complex 方法。Vector2d 或许应该提供
complex 方法。

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

Python 不能像 Java 那样使用 private 修饰符创建私有属性,但是 Python 有个简单的机制,能避免子类意外覆盖“私有”属性。
举个例子。有人编写了一个名为 Dog 的类,这个类的内部用到了 mood 实例属性,但是没有将其开放。现在,你创建了 Dog 类的子类:Beagle。如果你在毫不知情的情况下又创建了名为 mood 的实例属性,那么在继承的方法中就会把 Dog 类的 mood 属性覆盖掉。这是个难以调试的问题。
为了避免这种情况,如果以__mood 的形式(两个前导下划线,尾部没有或最多有一个下划线)命名实例属性,Python 会把属性名存入实例的__dict__ 属性中,而且会在前面加上一个下划线和类名。因此,对 Dog 类来说,__mood 会变成 _Dog__mood;对 Beagle类来说,会变成 _Beagle__mood。这个语言特性叫名称改写(name mangling)

示例 9-10 私有属性的名称会被“改写”,在前面加上下划线和类名
>>> v1 = Vector2d(3, 4)
>>> v1.__dict__
{'_Vector2d__y': 4.0, '_Vector2d__x': 3.0}
>>> v1._Vector2d__x
3.0

名称改写是一种安全措施,不能保证万无一失:它的目的是避免意外访问,不能防止故意做错事。

9.8 使用 slots 类属性节省空间

默认情况下,Python 在各个实例中名为 dict 的字典里存储实例属性。如 3.9.3 节所述,为了使用底层的散列表提升访问速度,字典会消耗大量内存。如果要处理数百万个属性不多的实例,通过 slots 类属性,能节省大量内存,方法是让解释器在元组中存储实例属性,而不用字典。
定义 slots 的方式是,创建一个类属性,使用 slots 这个名字,并把它的值设为一个字符串构成的可迭代对象,其中各个元素表示各个实例属性。我喜欢使用元组,因为这样定义的 slots 中所含的信息不会变化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/273788.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

android软件获取系统签名

有时候有的功能必须要有系统签名才能使用&#xff0c;例如调用系统自带的Surface.screenShot方法时&#xff0c;就必须在androidManifest.xml里声明android:sharedUserId"android.uid.system" 但是这个时候在编译生成的apk很有可能无法安装的情况 并且报这个错误&…

Python3中的可变与不可变类型

在描述变量是否是可变类型时&#xff0c;可变与否实际上说的是对变量进行“修改”时变量的内存地址是否会发生变化&#xff0c;而非值是否可变。在Python中&#xff0c;对不可变的变量进行“修改”实际上是重新赋值&#xff0c;对可变的变量进行修改才是真正的修改&#xff0c;…

python中带*(单星号)的变量和**(双星号)的变量

一、*args的使用方法 *args 用来将参数打包成tuple给函数体调用二、**kwargs的使用方法 **kwargs 打包关键字参数成dict给函数体调用注意点&#xff1a;参数arg、*args、**kwargs三个参数的位置必须是一定的。必须是(arg,*args,**kwargs)这个顺序&#xff0c;否则程序会报错。单…

百度知道回答的依赖注入

oC 或者 DI 或者 ...一大堆的缩写词不管是面向对象&#xff0c;还是面向过程&#xff0c;都需要分成许多的块&#xff0c;然后由这些部件协同工作完成任务 要协同工作就会产生依赖&#xff0c;一个方法调用另一个方法&#xff0c;一个对象包含另一个对象 如果对象A包含对象B的话…

Django model中的 class Meta 详解

参考 (1) https://www.cnblogs.com/tongchengbin/p/7670927.html

C\C++ 获取当前路径

C\C 获取当前路径 获取当前工作目录是使用函数&#xff1a;getcwd。cwd指的是“current working directory”&#xff0c;这样就好记忆了。 函数说明&#xff1a; 函数原型&#xff1a;char* getcwd(char* buffer, int len); 参数&#xff1a;buffer是指将当前工作…

[python进阶]11接口:从协议到抽象基类

本章讨论的话题是接口&#xff1a;从鸭子类型的代表特征动态协议&#xff0c;到使接口更明确、能验证实现是否符合规定的抽象基类&#xff08;Abstract Base Class&#xff0c;ABC&#xff09;。 首先&#xff0c;本章说明抽象基类的常见用途&#xff1a;实现接口时作为**超类(…

ie11浏览器不能显示最新修改的程序,调试出现代码逻辑错误却依旧执行

1、问题&#xff1a;ie11浏览器不能显示最新修改的程序&#xff0c;调试也不能&#xff0c;出现代码逻辑错误却依旧执行 2、百度解决方案&#xff1a;http://blog.163.com/wang_hj138126/blog/static/1408001062012631508444/ FireFox每次访问页面时检查最新版本 2012-07-31 …

C# 基础备忘录

1. decimal 类型调用ToString()方法后没把末尾的0去掉的解决办法: 例子&#xff1a;decimal? money Convert.ToDecimal(10.8950);string moneyStrmoney.Value.ToString(); 结果在同一台机子&#xff0c;两个项目里面会出现两个不同的结果。结果一&#xff1a;moneyStr"1…

[python进阶]12.继承的优缺点

本章探讨继承和子类化&#xff0c;重点是说明对 Python 而言尤为重要的两个细节&#xff1a; 子类化内置类型的缺点多重继承和方法解析顺序 12.1 子类化内置类型很 12.2 多重继承和方法解析

Android中用GridView实现九宫格的两种方法(转)

Android中用GridView实现九宫格的两种方法http://blog.csdn.net/shakespeare001/article/details/7768455 1.传统办法&#xff1a;实现一个继承BaseAdapter的 ImageAdapter package com.test; import android.app.Activity; import android.content.Context; import andro…

django框架中的模型

文章目录关联关系Many-to-one relationshipsMany-to-many relationshipsdjango学习——model中的get和filter方法的区别模型模型是您的数据唯一而且准确的信息来源。它包含您正在储存的数据的重要字段和行为。一般来说&#xff0c;每一个模型都映射一个数据库表。基础&#xff…

虚拟主机TOMCAT配置

在tomcat中添加虚拟主机&#xff1a;   编辑"tomcat\conf\server.xml"&#xff0c;在"<Engine></Engine>"元素中新加子元素"<Host></Host>"&#xff0c;如下&#xff1a;  </Host>     <Host name&quo…

django框架中表单

参考官方文档,太详细了 (https://docs.djangoproject.com/zh-hans/2.1/topics/forms/)

鸟哥学习笔记六(基础篇第十一章)

type:查看指令是否是bash内建指令 变量的设定规则 1. 变量与变量内容以一个等号『』来连结&#xff0c;如下所示&#xff1a; 『mynameVBird』 2. 等号两边不能直接接空格符&#xff0c;如下所示为错误&#xff1a; 『myname VBird』或『mynameVBird Tsai』3. 变量名称只能…

django-models类索引外键时候的related_name属性作用

其实可以就理解为,一对多关系拿对象的解决 可以把引用理解为主从关系 主引用从,即一对多 , 注意外键字段是放在多的一端的,比如一个班级class 有很多同学 students,那么就在students类里面设置class字段值是外键类型 从students拿class数据很好拿, studet.class就拿到了 但是从…

查找算法分析

参考&#xff1a; https://www.cnblogs.com/maybe2030/p/4715035.html#_label0

PPT设计里的小技巧

首先想说的是PPT设计&#xff0c;现在还是有很多人只把PPT当成一个存放文字和图片的软件&#xff0c;说的更直接点就是当是一个可以全屏放映内容的软件。但是我想说的是PPT已经走向了设计类型的软件&#xff0c;当Microsoft office Powerpoint2010正式版出来的时候这种感觉更盛…

PDFlib免费下载地址及详细介绍手册

PDFlib是一个用于创建PDF文档的开发工具,也可直接在你的服务器端产生PDF输出, 可利用PDFLib提供的简单易用的API&#xff08;应用编程接口&#xff09;在服务器或客户端产生PDF文档, PDFlib在生成PDF文档时不需要第3方软件的支持,也不需要其它工具。此产品属于产品 PDFlibPDI 的…

Hbase时间同步

如果Hbase的时间没有同步&#xff0c;启动主节点会起来&#xff0c;子节点的regionServer就不会起来。 错误日志如下&#xff1a; aused by: org.apache.hadoop.hbase.ipc.RemoteWithExtrasException(org.apache.hadoop.hbase.ClockOutOfSyncException): org.apache.hadoop.hba…