Python是一门高级语言,支持面向对象设计,如何设计一个符合Python风格的面向对象的类,是一个比较复杂的问题,本文提供一个参考,表达一种思路,探究一层原理。

目标

期望实现的类具有以下基本行为:

  • __repr__ 为repr()提供支持,返回便于开发者理解的对象字符串表示形式。
  • __str__ 为str()提供支持,返回便于用户理解的对象字符串表示形式。
  • __bytes__ 为bytes()提供支持,返回对象的二进制表示形式。
  • __format__ 为format()和str.format()提供支持,使用特殊的格式代码显示对象的字符串表示形式。

Vector2d是一个向量类,期望它能支持以下操作:

>>> v1 = Vector2d(3, 4)
>>> print(v1.x, v1.y) # 通过属性直接访问
3.0 4.0
>>> x, y = v1 # 支持拆包
>>> x, y
(3.0, 4.0)
>>> v1 # 支持repr
Vector2d(3.0, 4.0)
>>> v1_clone = eval(repr(v1)) # 验证repr描述准确
>>> v1 == v1_clone # 支持==运算符
True
>>> print(v1) # 支持str
(3.0, 4.0)
>>> octets = bytes(v1) # 支持bytes
>>> octets
b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@'
>>> abs(v1) # 实现__abs__
5.0
>>> bool(v1), bool(Vector2d(0, 0)) # 实现__bool__
(True, False)

基本实现

代码与解析如下:

from array import array
import math class Vector2d:
# Vector2d实例和二进制之间转换时使用
typecode = 'd' def __init__(self, x, y):
# 转换为浮点数
self.x = float(x)
self.y = float(y) def __iter__(self):
# 生成器表达式,把Vector2d实例变成可迭代对象,这样才能拆包
return (i for i in (self.x, self.y)) def __repr__(self):
class_name = type(self).__name__
# {!r}是个万能的格式符
# *self是拆包,*表示所有元素
return '{}({!r}, {!r})'.format(class_name, *self) def __str__(self):
# Vector2d实例是可迭代对象,可以得到一个元组,并str
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) def __bool__(self):
# 0.0是False,非零值是True
return bool(abs(self)) @classmethod
def frombytes(cls, octets): # classmethod不传self传cls
typecode = chr(octets[0])
memv = memoryview(octets[1:]).cast(typecode)
return cls(*memv) # 拆包后得到构造方法所需的一对参数

代码最后用到了@classmethod装饰器,它容易跟@staticmethod混淆。

@classmethod的用法是:定义操作类,而不是操作实例的方法。常用来定义备选构造方法。

@staticmethod其实就是个普通函数,只不过刚好放在了类的定义体里。实际定义在类中或模块中都可以。

格式化显示

代码与解析如下:

def angle(self):
return math.atan2(self.y, self.x) def __format__(self, fmt_spec=''):
if fmt_spec.endswith('p'): # 以'p'结尾,使用极坐标
fmt_spec = fmt_spec[:-1]
coords = (abs(self), self.angle()) # 计算极坐标(magnitude, angle)
outer_fmt = '<{}, {}>' # 尖括号
else:
coords = self # 不以'p'结尾,构建直角坐标(x, y)
outer_fmt = '({}, {})' # 圆括号
components = (format(c, fmt_spec) for c in coords) # 使用内置format函数格式化字符串
return outer_fmt.format(*components) # 拆包后代入外层格式

它实现了以下效果:

直角坐标:

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

极坐标:

>>> format(Vector2d(1, 1), 'p')  # doctest:+ELLIPSIS
'<1.414213..., 0.785398...>'
>>> format(Vector2d(1, 1), '.3ep')
'<1.414e+00, 7.854e-01>'
>>> format(Vector2d(1, 1), '0.5fp')
'<1.41421, 0.78540>'

可散列的

实现__hash__特殊方法能让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

这样x和y就只读不可写了。

属性名字的双下划线前缀叫做名称改写(name mangling),相当于_Vector2d__x_Vector2d__y,能避免被子类覆盖。

然后使用位运算符异或混合x和y的散列值:

def __hash__(self):
return hash(self.x) ^ hash(self.y)

节省内存

Python默认会把实例属性存储在__dict__字典里,字典的底层是散列表,数据量大了以后会消耗大量内存(以空间换时间)。通过__slots__类属性,能把实例属性存储到元组里,大大节省内存空间。

示例:

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

有几点需要注意:

  • 必须把所有属性都定义到__slots__元组中。
  • 子类也必须定义__slots__
  • 实例如果要支持弱引用,需要把__weakref也加入__slots__

覆盖类属性

实例覆盖

Python有个很独特的特性:类属性可用于为实例属性提供默认值。实例代码中的typecode就能直接被self.typecode拿到。但是,如果为不存在的实例属性赋值,会新建实例属性,类属性不会受到影响,self.typecode拿到的是实例属性的typecode。

示例:

>>> v1 = Vector2d(1, 2)
>>> v1.typecode = 'f'
>>> v1.typecode
'f'
>>> Vector2d.typecode
'd'

子类覆盖

类属性是公开的,所以可以直接通过Vector2d.typecode = 'f'进行修改。但是更符合Python风格的做法是定义子类:

class ShortVector2d(Vector2d):
typecode = 'f'

Django基于类的视图大量使用了这个技术。

小结

本文先介绍了如何实现特殊方法来设计一个Python风格的类,然后分别实现了格式化显示与可散列对象,使用__slots__能为类节省内存,最后讨论了类属性覆盖技术,子类覆盖是Django基于类的视图大量用到的技术。

参考资料:

《流畅的Python》第9章 符合Python风格的对象

https://www.jianshu.com/p/7fc0a177fd1f

Python如何设计面向对象的类(上)的更多相关文章

  1. Python如何设计面向对象的类(下)

    本文将在上篇文章二维向量Vector2d类的基础上,定义表示多维向量的Vector类. 第1版:兼容Vector2d类 代码如下: from array import array import rep ...

  2. python进阶01 面向对象、类、实例、属性封装、实例方法

    python进阶01 面向对象.类.实例.属性封装.实例方法 一.面向对象 1.什么是对象 #一切皆对象,可以简单地将“对象”理解为“某个东西” #“对象”之所以称之为对象,是因为它具有属于它自己的“ ...

  3. 22.python中的面向对象和类的基本语法

    当我发现要写python的面向对象的时候,我是踌躇满面,坐立不安呀.我一直在想:这个坑应该怎么爬?因为python中关于面向对象的内容很多,如果要讲透,最好是用面向对象的思想重新学一遍前面的内容.这个 ...

  4. python初步学习-面向对象之 类(二)

    方法重写 如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法: #!/usr/bin/env python #coding:utf8 class Parent: def myMeth ...

  5. python学习之【第十七篇】:Python中的面向对象(类和对象)

    1.什么是类和类的对象? 类是一种数据结构,我们可以用它来定义对象,后者把数据值和行为特性融合在一起,类是现实世界的抽象的实体以编程形式出现.实例是这些对象的具体化.类是用来描述一类事物,类的对象指的 ...

  6. python学习day20 面向对象(二)类成员&成员修饰符

    1.成员 类成员 类变量 绑定方法 类方法 静态方法 属性 实例成员(对象) 实例变量 1.1实例变量 类实例化后的对象内部的变量 1.2类变量 类中的变量,写在类的下一级和方法同一级. 访问方法: ...

  7. Python:笔记(3)——面向对象编程

    Python:笔记(3)——面向对象编程 类和面向对象编程 1.类的创建 说明:和Java不同的是,我们不需要显示的说明类的字段属性,并且可以在后面动态的添加. 2.构造函数 构造函数的功能毋庸置疑, ...

  8. [python 译] 基于面向对象的分析和设计

    [python 译] 基于面向对象的分析和设计 // */ // ]]>   [python 译] 基于面向对象的分析和设计 Table of Contents 1 原文地址 2 引言 2.1 ...

  9. Python之面向对象与类

    本节内容 面向对象的概念 类的封装 类的继承 类的多态 静态方法.类方法 和 属性方法 类的特殊成员方法 子类属性查找顺序 一.面向对象的概念 1. "面向对象(OOP)"是什么? ...

随机推荐

  1. 2021年主流CRM系统盘点

    面对市面上五花八门的CRM系统,相信您在选择的时候肯定是一头雾水.哪个CRM系统功能最强大?哪个CRM系统性价比最高?哪个CRM系统最适合企业使用?本篇文章小编将选出几家有代表性的CRM系统,并进行对 ...

  2. 三分钟了解B2B CRM系统的特点

    最近很多朋友想了解什么是B2B CRM系统,说到这里小Z先来给大家说说什么是B2B--B2B原本写作B to B,是Business-to-Business的缩写.正常来说就是企业与企业之间的生意往来 ...

  3. [Python] 爬虫系统与数据处理实战 Part.1 静态网页

    爬虫技术基础 HTTP/HTTPS(7层):应用层,浏览器 SSL:加密层,传输层.应用层之间 TCP/IP(4层):传输层 数据在传输过程中是加密的,浏览器显示的是解密后的数据,对爬虫没有影响 中间 ...

  4. make clean 清除之前编译的可执行文件及配置文件。 make distclean 清除所有生成的文件。

    https://blog.csdn.net/bb807777/article/details/108302105 make clean 清除之前编译的可执行文件及配置文件.make distclean ...

  5. Linux服务之nginx服务篇四(配置https协议访问)

    一.配置nginx支持https协议访问 编译安装nginx的时候需要添加相应的模块--with-http_ssl_module和--with-http_gzip_static_module(可通过/ ...

  6. xshell 终端 中文乱码

    我们在终端输入命令,中文显示乱码了. 解决方案:将xshell 传输采用的默认encode改为utf-8 解决:

  7. 利用MathType快速提取论文中的公式

    首先随便打开一个论文,比如下图,我们想提取公式(2.2.7) 第一步:按截图快捷键:Win+Shift+S ,把公式截取下来 第二步:打开大佬开发的网站:https://mathf.itewqq.cn ...

  8. Qt 设置中文

    1. 前言 在编写Qt应用程序时,有时会希望能直接设置中文字符串到界面,总结下其设置方法. 2. 设置中文 1)运行环境Qt5.5 VS2013 2)首先,查看需要设置中文的文件是否为UTF-8格式, ...

  9. SSM框架的配置整合(包含配置文件代码)

    由于SSM框架学习都要去网上或者以前的项目拷贝相同的代码,所以我在此把自己用到的配置文件全放在这里,帮助自己,帮助别人 首先开始前导入依赖和处理静态资源导出问题 <dependencies> ...

  10. nginx的请求处理

      nginx的请求处理¶ nginx使用一个多进程模型来对外提供服务,其中一个master进程,多个worker进程.master进程负责管理nginx本身和其他worker进程. 所有实际上的业务 ...