python 私有属性_符合Python风格的对象Part2

4、可散列的Vector

到现在为止,我们的Vector是不可散列的,也就是说没有对应的哈希值:

>>>v = Vector(3, 4)
>>>hash(v)
TypeError: unhashable type: 'Vector'

要想将Vector实例变成可散列的,必须使用__hash__、以及__eq__方法,而且要保证向量不可变。

想要实现hash很简单,只要在类内增加__hash__方法以及__eq__方法即可,官方文档中说到:

也就是说,如果要定义__hash__方法,同时也要定义__eq__方法。

4.1 可散列的实现(hash)

对象比较结果相同所需的唯一特征属性是其具有相同的哈希值;官方文档建议的做法是把参与比较的对象打包为一个元组并对该元组做哈希运算,例如:

def __hash__(self):
    return hash((self.name, self.nick, self.color))

在流畅的python书中,作者提出最好使用位运算符异或(^)混合各分量的散列值:

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

此处采用书中的做法,在类定义中添加上面的代码以后,Vector就变成可散列的了:

>>>v = Vector(3, 4)
>>>hash(v)
7

4.2 不可变性的实现(只读特性)

尽管在添加了__hash__方法以后,Vector实现了可散列,但是仍然可以为每一个分量赋值新值:

>>>v.x = 4
>>>v
Vector(4,4)

接下来我们要把x和y设置为只读:

class Vector():
    typecode = 'd'

    def __init__(self, x=0, y=0):
        self.__x = x
        self.__y = y

    @property
    def x(self):
        return self.__x

    @property
    def y(self):
        return self.__y
# 下面的代码和前面一致,此处省略

首先将x转换为私有变量__x,然后创建了同名函数x,这样调用self.x的时候调用的是对应的方法,方便后续的读值。@property装饰器把读值方法标记为特性,后续会进行介绍。

5、python的私有属性

我们的Vector暂时告一段落,但这仅仅是一个开始,你可能还需要很多的方法,比如向量的范数、向量的叉乘等等,所以后续的工作仍需要自己的探索。接下来,我们讨论一下私有属性:

如果你用过别的语言,你可能会发现很多语言使用private修饰符创建私有属性,python对在属性前加__两个下划线表明私有属性,尽管这种方式简单却容易出现属性覆盖的情况:

class Flower():

    def __init__(self, color='red'):
        self.__color = color  # 父类的私有属性


class Rose(Flower):

    def __init__(self, color='white'):
        super().__init__(self)
        self.__color = color  # 子类的同名私有属性

    def read_color(self):  # 对私有属性进行读取
        return self.__color


rose = Rose()  # 创建一个实例
>>>rose.__color
AttributeError: 'Rose' object has no attribute '__color'
>>>rose.read_color()
'white'

由于都存在私有属性__color,但是子类的私有属性将父类的同名私有属性进行了覆盖。为了避免这种情况,如果以__color的形式(两个前导下划线,尾部没有或最多有一个下划线)命名实例属性,Python会把属性名存入实例的__dict__属性中,而且会在前面加上一个下划线和类名。因此对于Flower类来说,__color会变成_Flower__color,而对于Rose来说,__color会变成_Rose__color,这个语言特性叫名称改写(name mangling)。

>>>rose.__dict__
{'_Flower__color': <__main__.rose>0x1d1966b50c8>, '_Rose__color': 'white'}

名称改写可以防止私有属性被覆盖,但是却可能是一个巨大的bug,因为只要知道私有属性名的机制,任何人都可以读取甚至修改私有属性:

print(rose._Rose__color) # 知道命名机制后就可以直接读取私有属性
rose._Rose__color = 'black' # 知道命名机制后就可以直接修改私有属性
rose.read_color() 

# 返回
white
'black'

不是所有Python程序员都喜欢名称改写功能,也不是所有人都喜欢self.__x这种不对称的名称。有些人不喜欢这种句法,他们约定使用一个下划线前缀编写“受保护”的属性(如self._x),尽管 Python解释器不会对使用单个下划线的属性名做特殊处理,但是这是这些程序员自己的约定,就像我们在定义常量的变量名的时候必须使用大写一样。

6、使用__slots__类属性节省空间(选读)

默认情况下,Python在各个实例中名为__dict__的字典里存储实例属性。不过我们前面也学过,为了使用底层的散列表提升访问速度,字典会消耗大量内存。如果你的实例内包含了几百万个属性,为了节省内存,可以使用__slots__类属性,让解释器在元组中存储实例属性,而不用字典。不过__slots__只能在单独类内使用,无法继承。

定义__slots__的方式是,直接使用__slots__这个名字创建一个类属性,它的值为各个实例属性名构成的可迭代对象(推荐使用不可变的元组)。例如前面我们的Rose:

class Rose(Flower):
    __slots__ = ('__color',)

    def __init__(self, color='white'):
        super().__init__(self)
        self.__color = color  # 子类的同名私有属性

    def read_color(self):  # 对私有属性进行读取
        return self.__color


rose = Rose()  # 创建一个实例
>>>rose.__dict__

AttributeError: 'Rose' object has no attribute '__dict__'

一旦创建了__slots__这个类属性以后,实例中将不会存在__dict__对象。

需要注意的是:__slots__只是一种事后优化的办法,却不能作为控制你的枷锁,如果你的属性不多的话,大可以忽略它,还是想强调一下,在使用过程中,应该注意这些问题:

  1. 每个子类都要定义__slots__属性,因为解释器会忽略继承的__slots__属性。
  2. 实例只能拥有__slots__中列出的属性,除非把__dict__加入__slots__中(这样做就失去了节省内存的功效)。
  3. 如果不把__weakreaf__加入__slots__,实例就不能作为弱引用的目标。

7、覆盖类属性(选读)

这个设计内部的实现方式,但是对于我们实际使用区分不大,大家简单读一下即可:

Python有个很独特的特性:类属性可用于为实例属性提供默认值。Vector中有个typecode类属性,使用时均采用self.typecode读取它的值。因为Vector实例本身没有typecode属性,所以self.typecode默认获取的是Vector.typecode类属性的值。但是一旦创建同名实例属性,则原始类属性将会被覆盖。

v = Vector(3, 4)
vbd = bytes(v)
print(v.typecode, len(vbd), ': ', vbd)
# 返回
d 17 :  b'd\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@'


v.typecode = 'f'
vbf = bytes(v)
print(v.typecode, len(vbf), ': ', vbf)
# 返回
f 9 :  b'f\x00\x00@@\x00\x00\x80@'

一开始的时候,v不存在实例化属性typecode,所以实际使用的是Vector.typecode;而一旦创建同名实例属性以后,就会把同名类属性遮盖。

如果想修改类属性的值,则需要在类上修改或者通过继承覆盖:

# 1 通过类进行修改:
Vector.typecode = 'f'

# 2 通过子类继承修改(推荐):
class anothorVector(Vector):
    typecode = 'f'

本章小结

本章可以看作第一章魔法方法的补充,在第一章的时候由于装饰器、内存等概念还没有设计,所以

在这一章对一些额外的魔法方法进行了说明。如果觉得晕头转向,我觉得是时候回过头来复习一下第一章的内容啦。

——本章完—— 

欢迎关注我的微信公众号5f19e91717b7a9eccfeafc8beb7c83dc.png

点个在看再走吧5a27e1216d1627eca22a7f7d5756d0ea.png

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值