Python编程思想(28):限制类的动态特性(__slots__属性)

李宁老师已经在「极客起源」 微信公众号推出《Python编程思想》电子书,囊括了Python的核心技术,以及Python的主要函数库的使用方法。读者可以在「极客起源」 公众号中输入 160442 开始学习。

《Python编程思想》总目录
《Python编程思想》专栏

Python是动态语言,动态语言的特征之一就是类、对象的属性、方法都可以动态增加和修改。前面已经简单介绍过为对象动态添加属性和方法,本节将进一步介绍 Python的动态特征。

前面介绍了如何为对象动态添加方法,但是所添加的方法只是对当前对象有效,如果希望为所有实例都添加方法,则可通过为类添加方法来实现,代码如下:

示例代码:dynamic_class_method. py

class Dog:
    def __init__(self, name):
        self.name = name
def run(self):
    print(f'{self.name} 正在跑...')
d1 = Dog('土豆')
d2 = Dog('皮皮')
# 为Dog动态添加run()方法,该方法的第一个参数会自动绑定
Dog.run = run
# d1、d2调用walk()方法
d1.run()
d2.run()

上面程序定义了一个Dog类,该Dog类只定义了一个构造方法,并未提供任何其他方法。因此,如果这时调用Dog类的run方法会抛出异常,因为Dog类并没有run方法。

在创建Dog对象后,为Dog类动态添加了run()方法,为Dog动态添加run()方法后,Dog类的两个实例d1hed2都拥有了run()方法,因此上面程序中最后两行d1和d2都可调用run()方法。

Python的这种动态性固然有其优势,但也给程序带来了一定的隐患。原来定义好的类,在任何时候都有可能被其他程序修改,这就带来了一些不确定性。如果程序要限制为某个类动态添加属性和方法,则可以通过__slots__属性来处理。

__slots__属性的值是一个元组,该元组的所有元素列出了该类的实例允许动态添加的所有属性名和方法名(对于 Python而言,方法与属性相同,只是这类属性的值为函数本身),代码如下:

示例代码:slots_demo py

class Dog:
    __slots__ = ('walk', 'age', 'name')
    def __init__(self, name):
        self.name = name
    def run(self):
        print('预定义的run()方法')
d = Dog('Mary')
from types import MethodType
# 只允许动态为实例添加walk、age、name这3个属性或方法
d.walk = MethodType(lambda self: print(f'{self.name}正在慢慢地走'), d)
d.age = 8
# d.sleep = MethodType(lambda self: print(f'{self.name}正在睡觉'), d)  抛出异常
d.walk()

上面程序Dog类中的开始位置通过__slots__= (walk,age,name)现在了Dog类只能动态添加walk、age和name三个属性。因此,这段代码中动态添加的walk和age属性都是允许的,但被注释掉的代码动态添加了sleep属性,就会抛出如下异常:

AttributeError: 'Dog' object has no attribute 'sleep'

需要说明的是, __slots__属性并不限制通过类来动态添加属性或方法,因此下面代码是合法的。

Dog.test = lambda  self:print('test')
d.test()

下面代码通过Dog类动态添加了test()方法,这样Dog对象就可以调用test()方法了。此外,__slots__属性指定的限制只对当前类的实例起作用,对该类派生出来的子类是不起作用的,代码如下:

class MyDog(Dog):
    def __init__(self, name):
        super().__init__(name)
    pass
md = MyDog('Bill')
# 完全可以为Mydog实例动态添加属性
md.sleep = MethodType(lambda self: print(f'{self.name}正在睡觉'), d)
md.sleep()

从这段代码可以看到,Dog的子类 MyDog的实例完全可以动态添加 sleep属性,这说明__slots__属性指定的限制只对当前类起作用。如果要限制子类的实例动态添加属性和方法,则需要在子类中也定义__ slots__属性,这样,子类的实例允许动态添加属性和方法就是子类的__ slots__元组加上父类的__slots__元组的和,代码如下:

class MyDog(Dog):
    __slots__ = ('sleep')
    def __init__(self, name):
        super().__init__(name)
    pass
md = MyDog('Bill')
# 完全可以为Mydog实例动态添加属性
md.sleep = MethodType(lambda self: print(f'{self.name}正在睡觉'), d)
md.sleep()

md.walk = MethodType(lambda self: print(f'我的 {self.name}正在睡觉'), d)
md.walk()

在这段代码中,子类MyDog只允许添加名为sleep的属性,所以MyDog允许添加的属性是:sleep、walk、age和name。

-----------------支持作者请转发本文,也可以加李宁老师微信:unitymarvel,或扫描下面二维码加微信--------
在这里插入图片描述

欢迎关注 极客起源 微信公众号,更多精彩视频和文章等着你哦!
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/nokiaguy/article/details/107012432