python不可变对象

创建不可变对象

修改__setattr__创建不可变对象

setattr方法掌管已有属性的赋值,和新建属性的赋值。

下面重写了setattr方法,可以在常规操作下保证对象是不可变的。

class F1:
    def __init__(self):
        #因为这个类修改了setattr属性,所以不能调用“=”赋值,只能调用父类的setattr方法
        super().__setattr__('name', '小明')  
        super().__setattr__('age', 10)
        # super().__setattr__('sex', '男')	   #因为slots中没有sex,即使在初始化中创建也会报错
    def __setattr__(self, key, value):		
         raise TypeError("__class__.name has no attribut 'name'".format(__class__=self.__class__, name=key))
f = F1()
# f.sex='男'  # 这里无法添加新属性,因为本质上调用的是__setattr__方法
f.__dict__['sex'] = '女'  # 这里可以改变,因为这里调用的是字典的setattr,而不是当前这个对象的。

_slots_

slots使 __dict__对象失效, 不能添加新的属性。如果不重写 setattr方法,那么已有的属性是可变的。slots主要目的是限制属性数量节省内存。

以下为 : 让实例对象不可变,无法对已有属性重新赋值,无法创建新的属性和方法。

class F1:
    __slots__ = ('name','age')
    def __init__(self):
        #因为这个类修改了setattr属性,所以不能调用“=”赋值,只能调用父类的setattr方法
        super().__setattr__('name', '小明')  
        super().__setattr__('age', 10)
        # super().__setattr__('sex', '男')	   #因为slots中没有sex,即使在初始化中创建也会报错
    def __setattr__(self, key, value):		
         raise TypeError("__class__.name has no attribut 'name'".format(__class__=self.__class__, name=key))
f = F1()
# f.sex='男' 									#这里无法添加新的属性
# f.__dict__['sex'] = '女'  # 这里不可以改变,因为slots使 dict属性失效了

slots 是不被继承的,所以子类是可以添加新的属性的

class F1:
    # 这个类无法添加新的属性
    __slots__ = ('name', 'age')

    def __init__(self):
        self.name = '小明'
        self.age = 10

        
class F2(F1):
    # slots属性不被继承,子类仍然使用dict管理属性。但是父类中声明的属性依然会使用slots管理。
    pass

>> f = F2()
>> f.p = 'pp'
>> print(f.__dict__)
>> {'p': 'pp'}
>> print(f.__slots__)  # 此时__slots__的返回值为父类中声明的值
>> ('name', 'age')

如果子类也需要slots的功能,那么需要显式声明。

class F1:
    __slots__ = ('name', 'age')

    def __init__(self):
        self.name = '小明'
        self.age = 10

class F2(F1):
    # 这时F2也无法添加新的属性
    __slots__ = ('sex',)

    def __init__(self):
        self.sex = '男'
        super().__init__()


>> f = F2()
>> print(f.__slots__)
>> ('sex',)
>> print(f.__class__.__bases__[0].__slots__) # 这是获取父类的slots值
>> ('name', 'age')

使用tuple子类创建不可变对象

class F1(tuple):
    # 实例化的过程是先new开辟内存区域,后init
    # 因为元组是不可变的,需要在初始化内存区域的时候就存入数据,所以不能使用init方法
    def __new__(cls, *args, **kwargs):
        # 记得return
        return super().__new__(cls, ('小明', '小王', '小李'))
	
    # 使用字典记录每个元素的位置,然后用索引获取元素
    def __getattr__(self, item):
        return self[{'xiaoming': 0, "xiaowang": 1, "xiaoli": 2}[item]]
	
    # 不添加这个也可以,但是在使用“=”赋值时,会没有结果,容易误导
    # 所以重写这个方法,直接抛出异常
    def __setattr__(self, key, value):
        raise AttributeError

f = F1()
# f.xiaoming = 2  # 不添加__setattr__方法的话这里会没有反应
print(f.xiaoming)

_getattribute_() 隐藏私有属性

当访问一个属性时, 先从 _dict_ 中或者 __slots__ 中查找, 如果属性没有找到,则调用 __getattr__ 函数。如果值是一个修饰符,那么对修饰符进行处理。

class F1:
    def __init__(self):
        super().__setattr__('name', '小明')
        super().__setattr__('age', 10)

    def __setattr__(self, key, value):
        raise TypeError("__class__.name has no attribut 'name'".format(__class__=self.__class__, name=key))
	
    # 如果访问的是私有属性,那么触发异常
    def __getattribute__(self, item):
        if item.startswith('_'): raise AttributeError   
        return super().__getattribute__(item)
	
    # 这个函数将无法调用
    def _private(self):  
        return None

f = F1()
print(f.name)
print(f._private()) # 这个函数将无法调用