《流畅的Python》学习笔记(9)—— Python风格的对象

本小节主要是利用Python内部的类“协议”写一个欧几里得向量类。

1.对象需要实现的功能


>>> v1 = Vector2d(3, 4)
>>> print(v1.x, v1.y)3.0 4.0
>>> x, y = v1 ➋
>>> x, y
(3.0, 4.0)
>>> v1 ➌
Vector2d(3.0, 4.0)
>>> v1_clone = eval(repr(v1))>>> v1 == v1_clone ➎
True
>>> print(v1)(3.0, 4.0)
>>> octets = bytes(v1)>>> octets
b'd\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x10@'
>>> abs(v1)5.0
>>> bool(v1), bool(Vector2d(0, 0))(True, False)

➊ Vector2d 实例的分量可以直接通过属性访问(无需调用读值方法)。
➋ Vector2d 实例可以拆包成变量元组。
➌ repr 函数调用 Vector2d 实例,得到的结果类似于构建实例的源码。
➍ 这里使用 eval 函数,表明 repr 函数调用 Vector2d 实例得到的是对构造方法的准确表述。 2
➎ Vector2d 实例支持使用 == 比较;这样便于测试。
➏ print 函数会调用 str 函数,对 Vector2d 来说,输出的是一个有序对。
➐ bytes 函数会调用 __bytes__方法,生成实例的二进制表示形式。
➑ abs 函数会调用 __abs__方法,返回 Vector2d 实例的模。
➒ bool 函数会调用 __bool__方法,如果 Vector2d 实例的模为零,返回 False,否则返回 True。

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):
		return math.hypot(self.x, self.y)def __bool__(self):
		return bool(abs(self))

从这一版方案可以看到所有需要实现的功能都已经基本实现,但很多地方还存在一些问题。比如__eq__函数使用的是将数据换作元组进行比较,这一方法简便,但是会创造出额外的空间存储两个新的元组,效率不高。

2.1 字节序列转实例与类方法

前面的代码中已经实现了将实例转字节码,但没有实现从字节码转实例方法,下面来完成这一代码。

首先分析,当需要从字节码转为实例时,说明没有实例可以使用,不能够使用实例.方法完成功能,而是需要类.类方法来实现,在Python中的类方法应该如何实现?

@classmethoddef frombytes(cls, octets): ➋
	typecode = chr(octets[0]) ➌
	memv = memoryview(octets[1:]).cast(typecode)return cls(*memv) 
classmethod与Staticmethod

在Python中有两个装饰器去实现面向对象种的类方法,这两个对比

  • 相同点:两个方法都可以实先操作类的方法,即可以实现类名.方法的调用方式
  • 不同点:classmethod是定义在类中,即它的第一个参数永远是类名,而statmethod是函数,只不过定义在类的定义体中里面,所以没有固定的第一参数。
class Demo:
    @classmethod
    def klassmeth(*args):
        return args
    
    @staticmethod
    def statmeth(*args):
        return args
>>> Demo.klassmeth() # ➌
(<class '__main__.Demo'>,)
>>> Demo.klassmeth('spam')
(<class '__main__.Demo'>, 'spam')
>>> Demo.statmeth() # ➍
()
>>> Demo.statmeth('spam')
('spam',)

从这个例子可以看到,staticmethod的参数只有输入的,classmethod会带有类名。一般用classmethod即可,没有非要使用staticmethod的需求。

2.2格式化表示

Python内置的format()函数和str.format()方法是将各个类型的格式化方式委托给相应的__format__()方法。具体为:

  • format(my_obj, format_spec) 的第二个参数,或者
  • str.format() 方法的格式字符串, {} 里代换字段中冒号后面的部分

其中format_spec时格式说明符,所谓格式说明符是由格式规范微语言用以规范字符串输出格式的语言。

格式规范微语言

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

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

格式规范微语言是可扩展的,因为各个类可以自行决定如何解释 format_spec 参数。

如果我们想要自己实现一个显示向量的方法,可以利用格式规范微语言重写__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(v1, '.3f')
Traceback (most recent call last):
...
TypeError: non-empty format string passed to object.__format__

为了兼顾坐标的极坐标和直角坐标表示方式,将__format__重写为:

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)

当末尾为p时,此时输出为极坐标形式,这几行代码写得非常妙,首先将内容和形式分开写,存于两个变量,然后在用最后两行代码统一。

2.3 Hash与Python类的私有属性

Python种要想实现散列,那么必须要实现__hash__()函数,而能够散列就要要求值不能改变。而在Python中,一切属性都是公开的,在没有约束的情况下,a.x = 5这样的幅值语句是可以实现的。所以为了散列,我们需要保护属性不能修改。

class Vector2d:
	typecode = 'd'
	def __init__(self, x, y):
		self.__x = float(x) ➊
		self.__y = float(y)
	@propertydef x(self):return self.__x ➍
	@propertydef y(self):
		return self.__y
	def __iter__(self):
		return (i for i in (self.x, self.y))def __hash__(self):
		return hash(self.x) ^ hash(self.y)

➊ 使用两个前导下划线(尾部没有下划线,或者有一个下划线),把属性标记为私有的。
➋ @property 装饰器把读值方法标记为特性。
➌ 读值方法与公开属性同名,都是 x。
➍ 直接返回 self.__x。
➎ 以同样的方式处理 y 特性。
➏ 需要读取 x 和 y 分量的方法可以保持不变,通过 self.x 和 self.y 读取公开特 性,而不必读取私有属性,因此上述代码清单省略了这个类的其他代码。

在此基础上可以实__hash__方法。

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

Python 不能像 Java 那样使用 private 修饰符创建私有属性,但是 Python 有个简单的机制,能避免子类意外覆盖“私有”属性。

例如,如有Dog类,这个类有一个私有属性叫mood,而它的子类Beagle也创建了一个属性叫mood,然而两个mood的含义并不一定相同,很可能导致依赖于mood属性的方法出现问题

为了避免这种情况,以__mood的形式,双下划线标注命名实例属性。Python会把属性名存储到__dict__属性中而且会在前面加上一个下划线和类名。因此,对 Dog 类来说, __mood 会变成 _Dog__mood;对 Beagle 类来说,会变成 _Beagle__mood。这个语言特性叫名称改写( name mangling)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值