元类 | 艾利克斯部落

首页 » 编程 » Python » Python学习 » 面向对象 » 正文

元类

一、储备知识:exec函数

exec函数有三个参数:

参数1:字符串形式的命令

参数2:全局作用域(字典形式),如果不指定默认就使用globals()

参数3:局部作用域(字典形式),如果不指定默认就使用locals()

来看下面的例子:

g = {"x": 1, "y": 2}  # 全局
l = {}  # 局部

exec("""
global x,m
x=10
m=100

z=3
""", g, l)
print(g)  # 输出一大堆,里面有 'x': 10, 'y': 2 还有 'm': 100
print(l)  # 输出:{'z': 3}

程序通过global设定了两个全局变量x和m,分别给x和m赋了新的值,又设定了一个z=3是局部变量,然后通过exec执行后,查看全局作用域字段g可以看到里面有 ‘x’: 10, ‘y’: 2 还有 ‘m’: 100 ,查看局部作用域字典 l 可以看到里面有 ‘z’: 3

 

二、元类介绍

我们知道Python中一切皆对象,那究竟对象具有什么样的特征,对象又可以怎么用呢?

1.都可以被引用,比如 x=obj

2.都可以当做函数的参数传入

3.都可以当做函数的返回值

4.都可以当做容器类的元素,如:li=[func,time,obj,123]

 

来看例子:

# 类也是对象,Foo=type(….)
class Foo:
pass


obj = Foo()
print(type(obj)) # 输出:<class ‘__main__.Foo’>
print(type(Foo)) # 输出:<class ‘type’>
可以看到obj是类Foo的对象,我们说python中一切皆对象,那么Foo类就也是一个对象,可以看到Foo类其实是type的对象,Foo的本质相当于 Foo=type(….)

 

这就引出了元类的概念:产生类的类称之为元类,默认所有用class定义的类,他们的元类是type

 

定义类的两种方式:

方式一:class

# 方式一:class
class Chinese:  # 相当于 Chinese=type(....)
    country = "China"

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def talk(self):
        print("%s os talking" % self.name)


print(Chinese)  # 输出:<class '__main__.Chinese'>

 

方式二:type

定义类的三要素:类名,类的基类们,类的名称空间

 

我们不依赖class,来通过type元类创建一个类。

class_name = "Chinese"  # 类名
class_bases = (object,)  # 类的基类们

class_body = """
country = "China"

def __init__(self, name, age):
    self.name = name
    self.age = age

def talk(self):
    print("%s os talking" % self.name)
"""  # 类体
class_dict = {}  # 类的名称空间
exec(class_body, globals(), class_dict)  # 生成名称空间
print(
    class_dict)  # 输出:{'country': 'China', '__init__': <function __init__ at 0x000002012ED198C8>, 'talk': <function talk at 0x000002012ED19A60>}

Chinese1 = type(class_name, class_bases, class_dict)  # 通过type生成类
print(Chinese1)  # 输出:<class '__main__.Chinese'>

obj1 = Chinese1("alex", 18)
print(obj1)  # 输出:<__main__.Chinese object at 0x00000210E2E09860>
print(obj1.name, obj1.age)  # 输出:alex 18

 

 

三、自定义元类

1. 自定义元类控制类的行为

# 自定义元类控制类的行为
class Mymeta(type):  # 要继承到type的一些属性
    def __init__(self, class_name, class_bases, class_dict):
        print("进入Mymeta了...")  # 程序启动时 自动进入此类了,输出:进入Mymeta了...

        if not class_name.istitle():  # 检查类名必须为首字母大写
            raise TypeError("类名称首字母必须大写")  # 抛出异常,程序终止

        if "__doc__" not in class_dict or not class_dict["__doc__"].strip():  # 检查类里必须有注释,且注释不能为空
            raise TypeError("类里必须添加注释,且注释不能为空")  # 抛出异常,程序终止

        super(Mymeta, self).__init__(class_name, class_bases, class_dict)  # 重用父类的属性


class Chinese(object, metaclass=Mymeta):  # 指定元类为Mymeta
    """
    中国人的类
    """
    country = "China"

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def talk(self):
        print("%s os talking" % self.name)

# Chinese = type(class_name,class_bases,class_dict)
# 由于类默认为type所产生,而type需要3个参数,所以我们自定义元类也需要3个参数

 

2. 自定义元类控制类的实例化行为

来知识储备一下:__call__方法

class Foo:
    pass


obj = Foo()  # 得到一个对象
obj()  # 默认obj不能调用,报错:TypeError: 'Foo' object is not callable

上面obj对象,默认是不能够加括号调用的,要报错。但是python中一切皆对象,他应该是能够加括号调用的。

Foo是对象,他能够加括号调用,那么obj也应该可以,需要通过一些手段来实现obj对象可以加括号调用,怎么实现呢,在对象的类累不加上__call__方法。

class Foo:
    def __call__(self, *args, **kwargs):
        print(self)  # self=<__main__.Foo object at 0x00000112138B8208>
        print(args)  # args=()
        print(kwargs)  # kwargs={}


obj = Foo()
obj()  # 触发__call__方法执行,可调用了
obj(1, 2, 3, a=1, b=2, c=3) # 相当于 obj.__call__(1, 2, 3, a=1, b=2, c=3)

# obj(1, 2, 3, a=1, b=2, c=3) 输出如下:
<__main__.Foo object at 0x000001B1C3438208>
(1, 2, 3)
{‘a’: 1, ‘b’: 2, ‘c’: 3}

 

元类内部也应该有一个__call__方法,会在调用Foo时触发执行

Foo(1,2,x=1)   # 相当于 Foo.__call__(Foo,1,2,x=1)

 

# 自定义元类控制类的实例化行为
class Mymeta(type):  # 要继承到type的一些属性
    def __init__(self, class_name, class_bases, class_dict):
        print("进入Mymeta了...")  # 程序启动时 自动进入此类了,输出:进入Mymeta了...

        if not class_name.istitle():  # 检查类名必须为首字母大写
            raise TypeError("类名称首字母必须大写")  # 抛出异常,程序终止

        if "__doc__" not in class_dict or not class_dict["__doc__"].strip():  # 检查类里必须有注释,且注释不能为空
            raise TypeError("类里必须添加注释,且注释不能为空")  # 抛出异常,程序终止

        super(Mymeta, self).__init__(class_name, class_bases, class_dict)  # 重用父类的属性

    def __call__(self, *args, **kwargs):
        print(args)
        print(kwargs)

        # 第一件事:造成一个空对象 obj
        obj = object.__new__(self)

        # 第二件事:初始化obj
        self.__init__(obj, *args, **kwargs)

        # 第三件事:返回obj
        return obj


class Chinese(object, metaclass=Mymeta):  # 指定元类为Mymeta
    """
    中国人的类
    """
    country = "China"

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def talk(self):
        print("%s is talking" % self.name)


obj = Chinese("alex", age=18)  # 相当于 Chinese.__call__(Chinese, "alex", age=18)
print(obj.__dict__)  # 输出:{'name': 'alex', 'age': 18}

 

3. 自定义元类控制类的实例化行为的应用

知识储备:单例模式

 

来看个例子:

# 单例模式
class MySQL:
    def __init__(self):
        self.host = "127.0.0.1"
        self.port = 3306

    def conn(self):
        pass

    def execute(self):
        pass


obj1 = MySQL()
obj2 = MySQL()
obj3 = MySQL()
print(obj1)  # 输出:<__main__.MySQL object at 0x0000022006589390>
print(obj2)  # 输出:<__main__.MySQL object at 0x00000220065893C8>
print(obj3)  # 输出:<__main__.MySQL object at 0x0000022006589438>

如上例子,实例化出来的3个对象,都有用同样的 host和port,但是3个对象想却需要独占3个空间,很浪费资源,我们可以把他改进一下,就要利用到 单例模式,如下:

# 单例模式
class MySQL:
    __instance = None

    def __init__(self):
        self.host = "127.0.0.1"
        self.port = 3306

    @classmethod
    def singleton(cls):
        if not cls.__instance:
            obj = cls()
            cls.__instance = obj
        return cls.__instance

    def conn(self):
        pass

    def execute(self):
        pass


obj1 = MySQL.singleton()
obj2 = MySQL.singleton()
obj3 = MySQL.singleton()
print(obj1)  # 输出:<__main__.MySQL object at 0x00000262FDFC9780>
print(obj2)  # 输出:<__main__.MySQL object at 0x00000262FDFC9780>
print(obj3)  # 输出:<__main__.MySQL object at 0x00000262FDFC9780>

经过改进后的对象,都是使用的同一块空间,节省空间。

 

# 自定义元类控制类的实例化行为的应用
class Mymeta(type):  # 要继承到type的一些属性
    def __init__(self, class_name, class_bases, class_dict):
        if not class_name.istitle():  # 检查类名必须为首字母大写
            raise TypeError("类名称首字母必须大写")  # 抛出异常,程序终止

        if "__doc__" not in class_dict or not class_dict["__doc__"].strip():  # 检查类里必须有注释,且注释不能为空
            raise TypeError("类里必须添加注释,且注释不能为空")  # 抛出异常,程序终止

        super(Mymeta, self).__init__(class_name, class_bases, class_dict)  # 重用父类的属性
        self.__instance = None

    def __call__(self, *args, **kwargs):
        if not self.__instance:
            obj = object.__new__(self)
            self.__init__(obj)
            self.__instance = obj
        return self.__instance


class Mysql(object, metaclass=Mymeta):
    """
    Mysql
    """

    def __init__(self):
        self.host = "127.0.0.1"
        self.port = 3306

    def conn(self):
        pass

    def execute(self):
        pass


obj1 = Mysql()
obj2 = Mysql()
obj3 = Mysql()
print(obj1)  # 输出:<__main__.Mysql object at 0x0000025FC2679940>
print(obj2)  # 输出:<__main__.Mysql object at 0x0000025FC2679940>
print(obj3)  # 输出:<__main__.Mysql object at 0x0000025FC2679940>
print(obj1.__dict__)  # 输出:{'host': '127.0.0.1', 'port': 3306}

 

 

 

 

 

 

赞 (0)

发表评论

*