python提升五
Python MetaClass元类详解
MetaClass元类,本质也是一个类,但和普通类的用法不同,它可以对类内部的定义(包括类属性和类方法)进行动态的修改。可以这么说,使用元类的主要目的就是为了实现在创建类时,能够动态地改变类中定义的属性或者方法。
如果在创建类时,想用 MetaClass 元类动态地修改内部的属性或者方法,则类的创建过程将变得复杂:
先创建 MetaClass 元类,然后用元类去创建类,最后使用该类的实例化对象实现功能。
和前面创建的类不同,如果想把一个类设计成 MetaClass 元类,其必须符合以下条件:
必须显式继承自 type 类;
类中需要定义并实现 new() 方法,该方法一定要返回该类的一个实例对象,因为在使用元类创建类时,该 new() 方法会自动被执行,用来修改新建的类。
1 | #定义一个元类 |
,首先可以断定 FirstMetaClass 是一个类。其次,由于该类继承自 type 类,并且内部实现了 new() 方法,因此可以断定 FirstMetaCLass 是一个元类。
可以看到,在这个元类的 new() 方法中,手动添加了一个 name 属性和 say() 方法。这意味着,通过 FirstMetaClass 元类创建的类,会额外添加 name 属性和 say() 方法。通过如下代码,可以验证这个结论:
1 | #定义类时,指定元类class CLanguage(object,metaclass=FirstMetaClass): |
可以看到,在创建类时,通过在标注父类的同时指定元类(格式为metaclass=元类名),则当 Python 解释器在创建这该类时,FirstMetaClass 元类中的 new 方法就会被调用,从而实现动态修改类属性或者类方法的目的。
显然,FirstMetaClass 元类的 new() 方法动态地为 Clanguage 类添加了 name 属性和 say() 方法,因此,即便该类在定义时是空类,它也依然有 name 属性和 say() 方法。
使用的比较少
Python多态及用法详解
Python 是弱类型语言,其最明显的特征是在使用变量时,无需为其指定具体的数据类型。这会导致一种情况,即同一变量可能会被先后赋值不同的类对象,
类的多态特性,还要满足以下 2 个前提条件:
继承:多态一定是发生在子类和父类之间;
重写:子类重写了父类的方法。
1 | class CLanguage: |
其实,Python 在多态的基础上,衍生出了一种更灵活的编程机制。
1 | class WhoSay: |
此程序中,通过给 WhoSay 类中的 say() 函数添加一个 who 参数,其内部利用传入的 who 调用 say() 方法。
这意味着,当调用 WhoSay 类中的 say() 方法时,我们传给 who 参数的是哪个类的实例对象,它就会调用那个类中的 say() 方法。
Python枚举类定义和使用
一些具有特殊含义的类,其实例化对象的个数往往是固定的,
比如用一个类表示月份,则该类的实例对象最多有 12 个;再比如用一个类表示季节,则该类的实例化对象最多有 4 个。
针对这种特殊的类,Python中有Enum 枚举类。
也就是说,对于这些实例化对象个数固定的类,可以用枚举类来定义。
1 | from enum import Enum |
如果想将一个类定义为枚举类,只需要令其继承自 enum 模块中的 Enum 类即可。
如在上面程序中,Color 类继承自 Enum 类,则证明这是一个枚举类。
在 Color 枚举类中,red、green、blue 都是该类的成员(可以理解为是类变量)。
注意,枚举类的每个成员都由 2 部分组成,分别为 name 和 value,其中 name 属性值为该枚举值的变量名(如 red),value 代表该枚举值的序号(序号通常从 1 开始)。
和普通类的用法不同,枚举类不能用来实例化对象,但这并不妨碍我们访问枚举类中的成员。访问枚举类成员的方式有多种
如,以 Color 枚举类为例,在其基础上添加如下代码:
1 | #调用枚举成员的 3 种方式 |
枚举类成员之间不能比较大小,但可以用 == 或者 is 进行比较是否相等
print(Color.red == Color.green)
print(Color.red.name is Color.green.name)
需要注意的是,枚举类中各个成员的值,不能在类的外部做任何修改
下面语法的做法是错误的:
Color.red = 4
除此之外,该枚举类还提供了一个 members 属性,该属性是一个包含枚举类中所有成员的字典,通过遍历该属性,也可以访问枚举类中的各个成员
for name,member in Color.members.items():
print(name,”->”,member)
注意:Python 枚举类中各个成员必须保证 name 互不相同,但 value 可以相同:
1 | from enum import Enum |
Color 枚举类中 red 和 green 具有相同的值(都是 1),Python 允许这种情况的发生,它会将 green 当做是 red 的别名,因此当访问 green 成员时,最终输出的是 red。
在实际编程过程中,如果想避免发生这种情况,可以借助 @unique 装饰器,这样当枚举类中出现相同值的成员时,程序会报 ValueError 错误
1 | from enum import Enum,unique |
除了通过继承 Enum 类的方法创建枚举类,还可以使用 Enum() 函数创建枚举类
1 | from enum import Enum |
Enum() 函数可接受 2 个参数,第一个用于指定枚举类的类名,第二个参数用于指定枚举类中的多个成员。
其实枚举一般使用的也比较少,只有在一些特殊的场合才会使用,而且它还可以使用其他的方法将就替换
Python new()方法详解
new() 是一种负责创建类实例的静态方法,它无需使用 staticmethod 装饰器修饰,且该方法会优先 init() 初始化方法被调用。
一般情况下,覆写 new() 的实现将会使用合适的参数调用其超类的 super().new(),并在返回之前修改实例
1 | class demoClass: |
new() 通常会返回该类的一个实例,但有时也可能会返回其他类的实例,如果发生了这种情况,则会跳过对 init() 方法的调用。而在某些情况下(比如需要修改不可变类实例(Python 的某些内置类型)的创建行为),利用这一点会事半功倍。
1 | class nonZero(int): |
什么情况下使用 new() 呢?答案很简单,在 init() 不够用的时候。
前面例子中对 Python 不可变的内置类型(如 int、str、float 等)进行了子类化,这是因为一旦创建了这样不可变的对象实例,就无法在 init() 方法中对其进行修改。
注意,由于 new() 不限于返回同一个类的实例,所以很容易被滥用,不负责任地使用这种方法可能会对代码有害,所以要谨慎使用。
Python中大量使用 new() 方法且合理的,就是 MetaClass 元类。
Python repr()方法:显示属性
我们经常会直接输出类的实例化对象
1 | class CLanguage: |
通常情况下,直接输出某个实例化对象,本意往往是想了解该对象的基本信息,例如该对象有哪些属性,它们的值各是多少等等。但默认情况下,我们得到的信息只会是“类名+object at+内存地址”,对我们了解该实例化对象帮助不大。
事实上,当我们输出某个实例化对象时,其调用的就是该对象的 repr() 方法,输出的是该方法的返回值。
执行 print(clangs) 等同于执行 print(clangs.repr()),程序的输出结果是一样的(输出的内存地址可能不同)。
和 init(self) 的性质一样,Python 中的每个类都包含 repr() 方法,因为 object 类包含 reper() 方法,而 Python 中所有的类都直接或间接继承自 object 类。
默认情况下,repr() 会返回和调用者有关的 “类名+object at+内存地址”信息。
当然,我们还可以通过在类中重写这个方法,从而实现当输出实例化对象时,输出我们想要的信息。
1 | class CLanguage: |
repr() 方法是类的实例化对象用来做“自我介绍”的方法,默认情况下,它会返回当前对象的“类名+object at+内存地址”,而如果对该方法进行重写,可以为其制作自定义的自我描述信息。