类
类定义
1 | class Person: |
属性
限制实例属性
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__
变量,来限制该class实例能添加的属性:
1 | class Student(object): |
运行就会报错
1 | Traceback (most recent call last): |
类属性
1 | class Person: |
结果
1 | person |
getattr/getattribute
1 | class User: |
私有属性
1 | class Person: |
动态属性
1 | class Person: |
结果
1 | 小明 |
可以看出
动态属性就和vue的计算属性一样,可以像访问属性一样访问方法。
数据描述符/非数据描述符
1 | import numbers |
这样如果赋值的时候不是数字,就会报错
这样就可以限制参数的类型了。
获取属性的顺序
获取属性的两种方式
1 | u = User() |
获取属性的优先级顺序
调用
类
的__getattribute__
类或者基类
中定义的数据描述符
实例
中的属性user.__dict__["age"]
类
中的非数据描述符
类或基类
的属性User.__dict__["age"]
调用
类
中的__getattr__
方法抛出
AttributeError
查看实例的属性
1 | class User: |
结果
1 | {'_User__name': '小明', 'age': 16} |
类与对象
1 | class User: |
注意
__new__
是用来把类生成对象,实在创建对象之前调用,如果没有返回,则__init__
不会调用。
__init__
是用来给对象初始化属性的。
枚举类
当我们需要定义常量时,一个办法是用大写变量通过整数来定义,例如月份:
1 | JAN = 1 |
好处是简单,缺点是类型是int
,并且仍然是变量。
更好的方法是为这样的枚举类型定义一个class类型,然后,每个常量都是class的一个唯一实例。Python提供了Enum
类来实现这个功能:
1 | from enum import Enum |
这样我们就获得了Month
类型的枚举类,可以直接使用Month.Jan
来引用一个常量,或者枚举它的所有成员:
1 | for name, member in Month.__members__.items(): |
value
属性则是自动赋给成员的int
常量,默认从1
开始计数。
如果需要更精确地控制枚举类型,可以从Enum
派生出自定义类:
1 | from enum import Enum, unique |
@unique
装饰器可以帮助我们检查保证没有重复值。
方法
实例方法
1 | class Person: |
静态方法
1 | class Person: |
注意
静态方法可以用类调用,也可以用示例调用。
类方法
1 | class Person: |
注意
类方法的主要好处在于生成类的实例的时候不用写类名,这样一旦类名发生变化,类方法中不用修改代码。
判断为空
1 | if (p1 is None): |
判断是否是类的实例
1 | class Person: |
删除属性或对象
1 | a = object() |
结果
1 | <object object at 0x017265B0> |
我们会发现a被删除后,b依旧没有被销毁,这是因为对应每次被引用都会计数器+1,只有计数器为0,才会销毁。
继承
1 | class Student(Person): |
Python的类是可以多继承的
1 | class C(A,B): |
注意
开发时尽量不要用多继承
super
基本使用
1 | class A: |
结果
1 | A |
调用的顺序
1 | class A: |
结果
1 | (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) |
可以看出
调用顺序是按照MRO的算法来调用的
使用
类.__mro__
可以查看调用顺序属性和方法都是这个规则
抽象类
1 | import abc |
注意
如果B不实现A的抽象方法就会报错。
动态创建类
1 | def create_class01(ctype): |
结果
1 | 小明 |
单例
很多初学者喜欢用全局变量,因为这比函数的参数传来传去更容易让人理解。确实在很多场景下用全局变量很方便。不过如果代码规模增大,并且有多个文件的时候,全局变量就会变得比较混乱。你可能不知道在哪个文件中定义了相同类型甚至重名的全局变量,也不知道这个变量在程序的某个地方被做了怎样的操作。
因此对于这种情况,有种更好的实现方式:
单例(Singleton)
单例是一种设计模式,应用该模式的类只会生成一个实例。
单例模式保证了在程序的不同位置都可以且仅可以取到同一个对象实例:如果实例不存在,会创建一个实例;如果已存在就会返回这个实例。因为单例是一个类,所以你也可以为其提供相应的操作方法,以便于对这个实例进行管理。
以下是实现方法:
- 使用函数装饰器实现单例
- 使用类装饰器实现单例
- 使用 new 关键字实现单例
- 使用 metaclass 实现单例
使用函数装饰器实现单例
以下是实现代码:
1 | def singleton(cls): |
输出结果:
1 | True |
在 Python 中,id 关键字可用来查看对象在内存中的存放位置,这里 cls1 和 cls2 的 id 值相同,说明他们指向了同一个对象。
代码中比较巧妙的一点是:
1 | _instance = {} |
使用不可变的类地址作为键,其实例作为值,每次创造实例时,首先查看该类是否存在实例,存在的话直接返回该实例即可,否则新建一个实例并存放在字典中。
使用类装饰器实现单例
代码:
1 | class Singleton(object): |
同时,由于是面对对象的,这里还可以这么用
1 | class Cls3(): |
使用 类装饰器实现单例的原理和 函数装饰器 实现的原理相似,理解了上文,再理解这里应该不难。
使用 new 关键字实现单例模式
使用 new 方法在创造实例时进行干预,达到实现单例模式的目的。
1 | class Single(object): |
在理解到 new 的应用后,理解单例就不难了,这里使用了
1 | _instance = None |
来存放实例,如果 _instance
为 None
,则新建实例,否则直接返回 _instance
存放的实例。
使用 metaclass 实现单例模式
同样,我们在类的创建时进行干预,从而达到实现单例的目的。
在实现单例之前,需要了解使用 type 创造类的方法,代码如下:
1 | def func(self): |
以上,我们使用 type 创造了一个类出来。这里的知识是 mataclass 实现单例的基础。
1 | class Singleton(type): |
这里,我们将 metaclass 指向 Singleton 类,让 Singleton 中的 type 来创造新的 Cls4 实例。