【python高级】元类的认识和基础用法

2023-08-03 13:37:23 浏览数 (1)

元类

“元类就是深度的魔法,99%的⽤户应该根本不必为此操⼼。 如果你想搞清楚 究竟是否需要⽤到元类,那么你就不需要它。 那些实际⽤到元类的⼈都⾮常 清楚地知道他们需要做什么,⽽且根本不需要解释为什么要⽤元类。“ ——蒂姆·彼得斯TimPeters

什么是元类

在python中,所有的类,都是基于元类创建的。

代码语言:javascript复制
class demo(object):
    pass

在python中,一切都是对象,类也是对象,所以一个类必定会有一个类型。

此处的object是所有python类层次结构的基类,也就是说所有的类都是继承它的。

那么,object又是什么类型?

代码语言:javascript复制
print(type(object))

我们打印出来看一下:

代码语言:javascript复制
<class 'type'>

显示的就是元类。

我们回到前面说的那句话,再加上注解会更容易理解了。

  • 在python中,一切都是对象(object),类(class)也是对象(object),所以一个类(class)必定会有一个类型(type)。
  • 用来创建类的类,叫做元类,函数type实际上也是一个元类。
  • python中任何形式类以及python3中的任何类都是type元类的一个实例。

需要注意的是,我们要区分元类和继承的基类:

  • type:是元类,所有的类都是通过type所创建出来的
  • object:顶层的基类,所有类的继承顶层父类都是object
  • type是创造者女娲,object是女娲创造的第一个孩子。(不是很准确,但这样好理解)

参数详解

type源码

代码语言:javascript复制
class type(object):
    """
    type(object_or_name, bases, dict)
    type(object) -> the object's type
    type(name, bases, dict) -> a new type
    """

源码中写道,使用type(name, bases, dict)就可以定义一个新的元类。

参数详解

  • name : 表示要创建的类的名称。(字符串类型)
  • bases : 继承类的基类元组(或包含基类的元类)。(元组类型)
  • dict : 类属性和方法。(字典类型)

自定义类与元类创建的比对

我们自己创建类,代码如下

代码语言:javascript复制
class MyClass(object):
    x = 42

obj = MyClass()
print(obj.x) # 输出42

这里,类名为MyClass,继承了基类object,属性为x=42

那么我们就按照元类创建类的方式构造一个和如上类一样功能的类。

代码语言:javascript复制
MyClass = type('MyClass',(object,),{"x":42})
obj = MyClass()
print(obj.x) #输出42

由于object是默认继承的基类,bases参数可以为空,代码如下:

代码语言:javascript复制
MyClass = type('MyClass',(),{"x":42})
obj = MyClass()
print(obj.x) #输出42

既然我们可以自己创建类,为什么要用元类?

别问,问就回去文章开头TimPeters说的那句话。

深入一下

经过上面的例子我们知道了,object是所有类的基类,而type是创建类的类,那么我如果把基类修改了,是不是创建出来的类就是自动继承了我所修改后的基类?

定义一个元类

  • 声明一个类,并继承自type类。
  • 在元类中定义__new__方法,该方法用于创建新的类。
  • __new__方法中可以自定义类的行为、属性和方法。
代码语言:javascript复制
class MyMeta(type):
    def __new__(meta, name, bases, attrs):
        # 自定义类的行为
        print("想不到吧,我才是基类")
        # 创建并返回新的类
        return super().__new__(meta, name, bases, attrs)

使用元类动态创建类

在创建类时,可以使用metaclass参数指定所使用的元类。

声明一个普通的类,并将metaclass参数设置为定义的元类,metaclass默认为type

代码语言:javascript复制
class MyClass(metaclass=MyMeta):
    # 类的定义
    hh = 123
    print("呜呜呜,我就是一个普通的类,可我的元类不是type了,而是自定义的")

运行以上代码:

代码语言:javascript复制
aa = MyClass()
print(aa.hh)
#输出结果如下>>>
呜呜呜,我就是一个普通的类,可我的元类不是type了,而是自定义的
想不到吧,我才是基类
123

如果我们没有指定元类为自定义的元类,输出将会没有想不到吧那一行。

代码语言:javascript复制
class MyClass2():
    # 类的定义
    h = 123
    print("呜呜呜,我就是一个普通的类,可我的元类不是type了,而是自定义的")

aa = MyClass2()
print(aa.h)
#输出结果如下>>>
呜呜呜,我就是一个普通的类,可我的元类不是type了,而是自定义的
123

用元类写一个简单的日志记录器

代码语言:javascript复制
class LogMeta(type):
    def __new__(meta, name, bases, attrs):
        # 检查属性中的日志消息
        logs = {}
        for key, value in attrs.items():
            if isinstance(value, str) and value.startswith("LOG "):
                logs[key] = value
        
        # 添加日志方法
        for key in logs.keys():
            def log_func(self, message):
                print(f"Log {key}: {message}")
            attrs[key] = log_func
        
        # 创建并返回新的类
        return super().__new__(meta, name, bases, attrs)


class MyLogger(metaclass=LogMeta):
    LOG_INFO = "LOG INFO"
    LOG_ERROR = "LOG ERROR"
    LOG_WARNING = "LOG WARNING"

在上面的示例中,我们创建了一个LogMeta元类,它通过检查类属性中的日志消息并动态创建日志方法。然后我们使用LogMeta元类创建了一个MyLogger类,并在其中定义了几个日志消息。最后,我们可以使用MyLogger类创建对象并调用日志方法。

代码语言:javascript复制
logger = MyLogger()
logger.LOG_INFO("This is an informational message.")
logger.LOG_ERROR("An error occurred.")

当我们运行上面的代码时,它将输出以下内容:

代码语言:javascript复制
Log LOG_WARNING: This is an informational message.
Log LOG_WARNING: An error occurred.

下一节我们讲讲如何将它运用在我们的测试框架里面动态创建测试用例。

0 人点赞