目录:
- 每篇前言:
- 0. Python中的元类:
- 1. 本文引子:
- 2. Python中的mro机制:
- 3. Python中类的魔法属性dict:
- 注意事项:
- 拓展——内建函数dir()
- 4. 正式谈一谈元类(metaclass):
- (1)引子:
- (2)类由自定义type创建:
- (3)类的基类中指定了metaclass,那么当前类也是由metaclass指定的类来创建的:
- (4)实例化一个指定了metaclass的类:
每篇前言:
🏆🏆作者介绍:【孤寒者】—CSDN全栈领域优质创作者、HDZ核心组成员、华为云享专家Python全栈领域博主、CSDN原力计划作者
- 🔥🔥本文已收录于Flask框架从入门到实战专栏:《Flask框架从入门到实战》
- 🔥🔥热门专栏推荐:《Python全栈系列教程》、《爬虫从入门到精通系列教程》、《爬虫进阶+实战系列教程》、《Scrapy框架从入门到实战》、《Flask框架从入门到实战》、《Django框架从入门到实战》、《Tornado框架从入门到实战》、《前端系列教程》。
- 📝📝本专栏面向广大程序猿,为的是大家都做到Python全栈技术从入门到精通,穿插有很多实战优化点。
- 🎉🎉订阅专栏后可私聊进一千多人Python全栈交流群(手把手教学,问题解答); 进群可领取Python全栈教程视频 + 多得数不过来的计算机书籍:基础、Web、爬虫、数据分析、可视化、机器学习、深度学习、人工智能、算法、面试题等。
- 🚀🚀加入我一起学习进步,一个人可以走的很快,一群人才能走的更远!
再来讲一下python中的元类是个啥【虽然我在《Python全栈系列教程》中已经讲解的非常透彻了,但是多来一遍多加深一遍印象】?
【本文内容在《Python全栈系列教程》中都十分细致地讲解过,本文的作用是复习,因为下一篇文章剖析wtforms源码需要这些知识点~】
0. Python中的元类:
在 Python 中,元类(metaclass)是用于创建类的类。你可以将元类视为类的 “类生成器”。通常情况下,我们定义类来创建对象,而元类用于定义类本身的行为。
每个类都是一个对象,而这个类的创建过程也是由一个元类控制的。在 Python 中,大多数类都是通过元类 type
创建的,包括内置的类和用户定义的类。
以下是一些关键概念:
-
类是对象: 在 Python 中,类本身也是对象。类对象用于创建类的实例对象。
-
元类是类的类: 元类是用于创建类的类。类定义了对象的行为,而元类定义了类的行为。
-
type
是默认元类: 在 Python 中,如果没有显式指定元类,那么默认的元类是type
。type
实际上是一个元类,也是一个类。当你定义一个类时,Python 使用type
来创建这个类的实例。 -
自定义元类: 你可以创建自定义的元类来控制类的创建过程。自定义元类可以继承自
type
,并覆盖其方法,例如__new__
和__init__
。 -
__metaclass__
属性: 你可以在类中使用__metaclass__
属性来指定使用的元类。如果没有指定,Python 将使用模块中的__metaclass__
属性,如果仍未找到,则将使用type
作为默认元类。 -
元类的作用: 元类的主要作用是允许你在创建类的时候定制类的行为。这使得元类成为一种高级的编程工具,通常在框架和库的开发中使用。
快速上手——如何使用元类:
class MyMeta(type):
def __new__(cls, name, bases, attrs):
# 在创建类的时候添加一个新的属性
attrs['custom_attribute'] = 'This is a custom attribute'
return super().__new__(cls, name, bases, attrs)
class MyClass(metaclass=MyMeta):
pass
# 创建 MyClass 的实例
obj = MyClass()
# 访问自定义属性
print(obj.custom_attribute)
MyMeta
是一个简单的元类,通过继承 type
并覆盖 __new__
方法,在创建类的时候添加了一个新的属性。这个新属性将出现在 MyClass
类的所有实例中。
1. 本文引子:
请大家认真分析一下下述代码的执行顺序(先不要看我的分析)
class MyType(type):
def __call__(self, *args, **kwargs):
pass
class Foo(object, metaclass=MyType):
def __init__(self):
pass
def __new__(cls, *args, **kwargs):
pass
obj = Foo()
在上述代码中,由于我定义了自定义元类 MyType
,并在类 Foo
中使用了这个元类,所以调用顺序将是 MyType
的 __call__
方法、Foo
的 __new__
方法,然后是 Foo
的 __init__
方法。
详细解释一下:
-
调用 MyType 的
__call__
方法:class MyType(type): def __call__(self, *args, **kwargs): pass
当实例化
Foo
类时,由于使用了元类MyType
,所以MyType
的__call__
方法被调用。 -
调用 Foo 的
__new__
方法:class Foo(object, metaclass=MyType): def __init__(self): pass def __new__(cls, *args, **kwargs): pass
在
__call__
方法调用之后,Foo
类的__new__
方法被调用。这个方法在对象实例化时被调用,通常用于创建并返回实例(当然我这里重在讲执行顺序,所以直接pass了)。 -
调用 Foo 的
__init__
方法:obj = Foo()
最后,
Foo
类的__init__
方法被调用。这个方法在对象实例创建后被调用,通常用于执行初始化操作。
综合起来,执行顺序是 MyType.__call__
-> Foo.__new__
-> Foo.__init__
。
2. Python中的mro机制:
mro
(Method Resolution Order【方法解析顺序】)是 Python 中的一个机制,用于确定类继承关系中方法的调用顺序。在多继承的情况下,mro
决定了方法的查找顺序,以确保在类层次结构中的不同类中定义的方法能够正确被调用。
Python 使用 C3 线性化算法来计算 mro
。mro
的计算规则如下:
-
深度优先: 在同一层级的继承关系中,首先会深入到下一级的类,而不是横向移动到同级的另一个类。
-
左右优先: 在多继承中,首先考虑继承列表中的左侧类,然后再考虑右侧的类。
-
子类优先: 在搜索过程中,子类的
mro
会优先于父类的mro
。
简单的例子1:
class A(object):
pass
class B(A):
pass
class C(object):
pass
class D(B, C):
pass
print(D.__mro__)
以下是类 D
的 MRO 的详细解释:
- D
- B
- A
- C
- object
这意味着当我们尝试在类 D
的实例上访问属性或方法时,Python 首先会查找 D
,然后是 B
,接着是 A
,然后是 C
,最后是通用的基类 object
,如果在前面的类中找不到所需的属性或方法的话。
通过使用 __mro__
属性,可以以编程方式访问 MRO,就像使用 print(D.__mro__)
打印出来的顺序一样。这提供了一种透明的方式,让我们了解 Python 在多继承的类层次结构中查找属性和方法的顺序。
简单例子2:
class A:
def method(self):
print("A method")
class B(A):
def method(self):
print("B method")
super().method()
class C(A):
def method(self):
print("C method")
super().method()
class D(B, C):
pass
obj = D()
obj.method()
在这个例子中,类 D
继承自 B
和 C
,它们都是直接或间接继承自 A
。当调用 obj.method()
时,D
的 mro
决定了方法调用的顺序。mro
的计算顺序是 [D, B, C, A]
。因此,调用 obj.method()
时的实际调用顺序是 D.method()
-> B.method()
-> C.method()
-> A.method()
。
这样的 mro
计算确保了在多继承的情况下,方法的查找顺序是有序的,且保持了继承关系的结构。
易错点强调一下:
可能会有人上来就说这个例子的mro顺序应该是[D, B, A, C],所以我来反驳一下:
要计算D的MRO,可以用如下的merge规则:
D = [D] + merge(MRO(B), MRO(C), BC) D = [D] + merge([B, A, object], [C, A, object], [B, C])
应用合并规则:
B出现在列表的第一个位置,因此B是下一个在MRO中的类;
接下来,从其余的列表中删除B,并比较接下来的元素;
C现在出现在没有B作为首位的列表的第一个位置,所以C是下一个;
然后同样的规则,删除C,比较剩下的元素;
接下来A没有争议地第一;
最后加入object(所有Python类最基本的类型,它们始终位于MRO列表的末尾)。
所以最终的MRO列表就是:
D = [D, B, C, A, object]
3. Python中类的魔法属性dict:
- 对于类而言,Python 提供了
__dict__
魔法属性,它是一个包含类的命名空间的字典。这个字典包含了类的所有属性和方法。
class MyClass:
x = 10
def __init__(self, y):
self.y = y
print(MyClass.__dict__)
- 对于类的实例,
__dict__
包含了实例的属性。
obj = MyClass(5)
print(obj.__dict__)
注意事项:
- 在一些特殊情况下,
__dict__
中可能会包含一些额外的属性,例如__weakref__
用于支持弱引用。 - 对于内建类型(如
int
、str
等),__dict__
是不存在的。
总的来说,__dict__
是一个强大的工具,可以让我们动态地查看和修改类和实例的属性。然而,直接使用 __dict__
有时不是最佳的做法,因为它绕过了类或实例中可能存在的一些定制行为,推荐使用更高层级的接口和方法【比如内建函数getattr()和setattr(),hasattr()等】
拓展——内建函数dir()
类的dict魔法属性得到的最终结果是key: value形式的字典格式;而dir()方法则只拿到key:
-
对于类的实例,内建函数dir()作用如下:
dir()
内建函数,用于获取对象的属性和方法列表。当调用dir()
时,它返回一个包含对象所有属性和方法名称的列表。这包括对象的普通属性、方法、类属性、类方法等。如果没有提供参数,dir()
将返回当前作用域内的所有变量和函数的列表。class MyClass: x = 10 def __init__(self, y): self.y = y def method(self): pass obj = MyClass(20) print(dir(obj))
在上面的例子中,
dir(obj)
会返回一个包含MyClass
类实例obj
的所有属性和方法的列表。print(dir())
在这个例子中,
dir()
没有提供参数,因此它返回当前作用域内的所有变量和函数的列表。dir()
的输出结果包括以下类型的条目:- 字符串:表示对象的普通属性或方法。
__xxx__
形式的字符串:表示对象的特殊方法(魔法方法)。- 类属性和类方法的名称。
-
对于类来说,内建函数dir()的作用如下:
如果传入
dir()
的参数是一个类,它会返回类的所有属性、方法以及基类的信息。下面是一个示例:class MyClass: x = 10 def __init__(self, y): self.y = y def method(self): pass class DerivedClass(MyClass): z = 20 def __init__(self, y, w): super().__init__(y) self.w = w def derived_method(self): pass # 使用 dir() 获取类的信息 print(dir(MyClass))
在这个例子中,
dir(MyClass)
返回一个包含MyClass
类的所有属性和方法的列表。这包括类属性x
、构造函数__init__
、普通方法method
等。注意:
dir()
返回的列表包含了类的所有属性和方法,包括继承自基类的。- 如果子类有自己的属性或方法,也会包含在列表中。
- 特殊方法(魔法方法)以
__xxx__
形式显示在列表中。 - 类的基类信息也会包括在列表中。
这样,
dir()
提供了一种查看类的结构和功能的方式,使你能够快速了解类的成员。
4. 正式谈一谈元类(metaclass):
(1)引子:
创建类的两种方法:
# 方法一: class Foo(object): CITY = 'zz' def func(self, x): return x + 1 # 方法二【其实第一种方法底层也是通过type来实现创建类的~】: def func(self, x): return x + 1 Foo = type('Foo', (object,), {'CITY': 'zz', 'func': func})
很容易知道:下述两种其实一模一样,因为默认就是metaclass=type,所以加不加都一样~
class Foo(object):
CITY = 'zz'
def func(self, x):
return x + 1
class Foo(object, metaclass=type):
CITY = 'zz'
def func(self, x):
return x + 1
(2)类由自定义type创建:
class MyType(type):
pass
class Foo(object, metaclass=MyType):
CITY = 'zz'
def func(self, x):
return x + 1
引子部分实现类的两种方法中的第二种告诉我们类Foo就是type加括号创建的。所以上部分代码中Foo类就是MyType加括号创建的(这样就会执行MyType类的构造方法init)。
class MyType(type):
def __init__(self, *args, **kwargs):
print('创建类之前')
super(MyType, self).__init__(*args, **kwargs)
print('创建类之后')
class Foo(object, metaclass=MyType):
CITY = 'zz'
def func(self, x):
return x + 1
上述代码跟引子中第一种创建类的方法一样!同时上述代码运行已经可以打印代码中两个print,因为类已经创建了!!!
(3)类的基类中指定了metaclass,那么当前类也是由metaclass指定的类来创建的:
class MyType(type):
def __init__(self, *args, **kwargs):
print('创建类之前')
super(MyType, self).__init__(*args, **kwargs)
print('创建类之后')
class Foo(object, metaclass=MyType):
CITY = 'zz'
def func(self, x):
return x + 1
class Bar(Foo):
pass
这样会打印两遍print,因为Bar没有指定metaclass,但是这个类也是由其继承的类的metaclass指定的type创建的,所以会打印两遍print。
简单改动一下:
class MyType(type):
def __init__(self, *args, **kwargs):
print('创建类之前')
super(MyType, self).__init__(*args, **kwargs)
print('创建类之后')
Base = MyType('Base', (object, ), {})
# 上一行等价于:
# class Base(object, metaclass=MyType):
# pass
class Foo(Base):
CITY = 'zz'
def func(self, x):
return x + 1
上部分代码还是和上一次讲的一样,基类中指定了自定义的元类MyType,所以打印两次~
class MyType(type):
def __init__(self, *args, **kwargs):
print('创建类之前')
super(MyType, self).__init__(*args, **kwargs)
print('创建类之后')
def with_metaclass():
return MyType('Base', (object,), {})
class Foo(with_metaclass()):
CITY = 'zz'
def func(self, x):
return x + 1
上述代码还是一个意思~
将object当参数传递给with_metaclass函数:
class MyType(type):
def __init__(self, *args, **kwargs):
print('创建类之前')
super(MyType, self).__init__(*args, **kwargs)
print('创建类之后')
def with_metaclass(arg):
return MyType('Base', (arg,), {})
class Foo(with_metaclass(object)):
CITY = 'zz'
def func(self, x):
return x + 1
总结:
- 如果某个类指定了metaclass为MyType,那么当前类的所有派生类都是由这个MyType创建的!
(4)实例化一个指定了metaclass的类:
class MyType(type):
def __init__(self, *args, **kwargs):
super(MyType, self).__init__(*args, **kwargs)
class Foo(object, metaclass=MyType):
pass
- MyType的
__init__
obj = Foo() - MyType的
__call__
- Foo的
__new__
- Foo的
__init__