Python MetaClass元类详解

MetaClass元类,本质也是一个类,但和普通类的用法不同,它可以对类内部的定义(包括类属性和类方法)进行动态的修改。可以这么说,使用元类的主要目的就是为了实现在创建类时,能够动态地改变类中定义的属性或者方法

如果在创建类时,想用 MetaClass 元类动态地修改内部的属性或者方法,则类的创建过程将变得复杂:

先创建 MetaClass 元类,然后用元类去创建类,最后使用该类的实例化对象实现功能。
和前面创建的类不同,如果想把一个类设计成 MetaClass 元类,其必须符合以下条件:

必须显式继承自 type 类;
类中需要定义并实现 new() 方法,该方法一定要返回该类的一个实例对象,因为在使用元类创建类时,该 new() 方法会自动被执行,用来修改新建的类。

1
2
3
4
5
6
7
8
#定义一个元类
class FirstMetaClass(type):
def __new__(cls, name, bases, attrs):
# 动态为该类添加一个name属性
attrs['name'] = "xiiaoyang"
attrs['say'] = lambda self: print("调用 say() 实例方法")
return super().__new__(cls,name,bases,attrs)

,首先可以断定 FirstMetaClass 是一个类。其次,由于该类继承自 type 类,并且内部实现了 new() 方法,因此可以断定 FirstMetaCLass 是一个元类。

可以看到,在这个元类的 new() 方法中,手动添加了一个 name 属性和 say() 方法。这意味着,通过 FirstMetaClass 元类创建的类,会额外添加 name 属性和 say() 方法。通过如下代码,可以验证这个结论:

1
2
3
4
5
#定义类时,指定元类class CLanguage(object,metaclass=FirstMetaClass):
pass
clangs = CLanguage()
print(clangs.name)
clangs.say()

可以看到,在创建类时,通过在标注父类的同时指定元类(格式为metaclass=元类名),则当 Python 解释器在创建这该类时,FirstMetaClass 元类中的 new 方法就会被调用,从而实现动态修改类属性或者类方法的目的。

显然,FirstMetaClass 元类的 new() 方法动态地为 Clanguage 类添加了 name 属性和 say() 方法,因此,即便该类在定义时是空类,它也依然有 name 属性和 say() 方法。

使用的比较少

Python多态及用法详解

Python 是弱类型语言,其最明显的特征是在使用变量时,无需为其指定具体的数据类型。这会导致一种情况,即同一变量可能会被先后赋值不同的类对象,

类的多态特性,还要满足以下 2 个前提条件:

继承:多态一定是发生在子类和父类之间;
重写:子类重写了父类的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class CLanguage:
def say(self):
print("调用的是 Clanguage 类的say方法")
class CPython(CLanguage):
def say(self):
print("调用的是 CPython 类的say方法")
class CLinux(CLanguage):
def say(self):
print("调用的是 CLinux 类的say方法")
a = CLanguage()
a.say()
a = CPython()
a.say()
a = CLinux()
a.say()

其实,Python 在多态的基础上,衍生出了一种更灵活的编程机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class WhoSay:
def say(self,who):
who.say()
class CLanguage:
def say(self):
print("调用的是 Clanguage 类的say方法")
class CPython(CLanguage):
def say(self):
print("调用的是 CPython 类的say方法")
class CLinux(CLanguage):
def say(self):
print("调用的是 CLinux 类的say方法")
a = WhoSay()
#调用 CLanguage 类的 say() 方法
a.say(CLanguage())
#调用 CPython 类的 say() 方法
a.say(CPython())
#调用 CLinux 类的 say() 方法
a.say(CLinux())

此程序中,通过给 WhoSay 类中的 say() 函数添加一个 who 参数,其内部利用传入的 who 调用 say() 方法。
这意味着,当调用 WhoSay 类中的 say() 方法时,我们传给 who 参数的是哪个类的实例对象,它就会调用那个类中的 say() 方法。

Python枚举类定义和使用

一些具有特殊含义的类,其实例化对象的个数往往是固定的,
比如用一个类表示月份,则该类的实例对象最多有 12 个;再比如用一个类表示季节,则该类的实例化对象最多有 4 个。
针对这种特殊的类,Python中有Enum 枚举类。

也就是说,对于这些实例化对象个数固定的类,可以用枚举类来定义。

1
2
3
4
5
6
from enum import Enum
class Color(Enum):
# 为序列值指定value值
red = 1
green = 2
blue = 3

如果想将一个类定义为枚举类,只需要令其继承自 enum 模块中的 Enum 类即可。
如在上面程序中,Color 类继承自 Enum 类,则证明这是一个枚举类。

在 Color 枚举类中,red、green、blue 都是该类的成员(可以理解为是类变量)。

注意,枚举类的每个成员都由 2 部分组成,分别为 name 和 value,其中 name 属性值为该枚举值的变量名(如 red),value 代表该枚举值的序号(序号通常从 1 开始)。

和普通类的用法不同,枚举类不能用来实例化对象,但这并不妨碍我们访问枚举类中的成员。访问枚举类成员的方式有多种

如,以 Color 枚举类为例,在其基础上添加如下代码:

1
2
3
4
5
6
7
8
9
10
#调用枚举成员的 3 种方式
print(Color.red)
print(Color['red'])
print(Color(1))
#调取枚举成员中的 value 和 name
print(Color.red.value)
print(Color.red.name)
#遍历枚举类中所有成员的 2 种方式
for color in Color:
print(color)

枚举类成员之间不能比较大小,但可以用 == 或者 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
2
3
4
5
6
from enum import Enum
class Color(Enum):
red = 1
green = 1
blue = 3
print(Color['green'])

Color 枚举类中 red 和 green 具有相同的值(都是 1),Python 允许这种情况的发生,它会将 green 当做是 red 的别名,因此当访问 green 成员时,最终输出的是 red。

在实际编程过程中,如果想避免发生这种情况,可以借助 @unique 装饰器,这样当枚举类中出现相同值的成员时,程序会报 ValueError 错误

1
2
3
4
5
6
7
8
from enum import Enum,unique
#添加 unique 装饰器
@unique
class Color(Enum):
red = 1
green = 1
blue = 3
print(Color['green'])

除了通过继承 Enum 类的方法创建枚举类,还可以使用 Enum() 函数创建枚举类

1
2
3
from enum import Enum
#创建一个枚举类
Color = Enum("Color",('red','green','blue'))

Enum() 函数可接受 2 个参数,第一个用于指定枚举类的类名,第二个参数用于指定枚举类中的多个成员。

其实枚举一般使用的也比较少,只有在一些特殊的场合才会使用,而且它还可以使用其他的方法将就替换

Python new()方法详解

new() 是一种负责创建类实例的静态方法,它无需使用 staticmethod 装饰器修饰,且该方法会优先 init() 初始化方法被调用。

一般情况下,覆写 new() 的实现将会使用合适的参数调用其超类的 super().new(),并在返回之前修改实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class demoClass:
instances_created = 0
def __new__(cls,*args,**kwargs):
print("__new__():",cls,args,kwargs)
instance = super().__new__(cls)
instance.number = cls.instances_created
cls.instances_created += 1
return instance
def __init__(self,attribute):
print("__init__():",self,attribute)
self.attribute = attribute
test1 = demoClass("abc")
test2 = demoClass("xyz")
print(test1.number,test1.instances_created)
print(test2.number,test2.instances_created)

new() 通常会返回该类的一个实例,但有时也可能会返回其他类的实例,如果发生了这种情况,则会跳过对 init() 方法的调用。而在某些情况下(比如需要修改不可变类实例(Python 的某些内置类型)的创建行为),利用这一点会事半功倍。

1
2
3
4
5
6
7
8
9
class nonZero(int):
def __new__(cls,value):
return super().__new__(cls,value) if value != 0 else None
def __init__(self,skipped_value):
#此例中会跳过此方法
print("__init__()")
super().__init__()
print(type(nonZero(-12)))
print(type(nonZero(0)))

什么情况下使用 new() 呢?答案很简单,在 init() 不够用的时候。

前面例子中对 Python 不可变的内置类型(如 int、str、float 等)进行了子类化,这是因为一旦创建了这样不可变的对象实例,就无法在 init() 方法中对其进行修改。

注意,由于 new() 不限于返回同一个类的实例,所以很容易被滥用,不负责任地使用这种方法可能会对代码有害,所以要谨慎使用。
Python中大量使用 new() 方法且合理的,就是 MetaClass 元类。

Python repr()方法:显示属性

我们经常会直接输出类的实例化对象

1
2
3
4
class CLanguage:
pass
clangs = CLanguage()
print(clangs)

通常情况下,直接输出某个实例化对象,本意往往是想了解该对象的基本信息,例如该对象有哪些属性,它们的值各是多少等等。但默认情况下,我们得到的信息只会是“类名+object at+内存地址”,对我们了解该实例化对象帮助不大。

事实上,当我们输出某个实例化对象时,其调用的就是该对象的 repr() 方法,输出的是该方法的返回值。

执行 print(clangs) 等同于执行 print(clangs.repr()),程序的输出结果是一样的(输出的内存地址可能不同)。

init(self) 的性质一样,Python 中的每个类都包含 repr() 方法,因为 object 类包含 reper() 方法,而 Python 中所有的类都直接或间接继承自 object 类。

默认情况下,repr() 会返回和调用者有关的 “类名+object at+内存地址”信息。
当然,我们还可以通过在类中重写这个方法,从而实现当输出实例化对象时,输出我们想要的信息。

1
2
3
4
5
6
7
8
class CLanguage:
def __init__(self):
self.name = "漂亮鬼"
self.add = "https://xiaoyangzst.vercel.app/"
def __repr__(self):
return "CLanguage[name="+ self.name +",add=" + self.add +"]"
clangs = CLanguage()
print(clangs)

repr() 方法是类的实例化对象用来做“自我介绍”的方法,默认情况下,它会返回当前对象的“类名+object at+内存地址”,而如果对该方法进行重写,可以为其制作自定义的自我描述信息。