面向对象:元类

Posted neozheng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面向对象:元类相关的知识,希望对你有一定的参考价值。

元类:

 python中一切皆对象,意味着:

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

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

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

  4. 都可以当做容器类的元素(列表、字典、元祖、集合),如: l = [func,time,obj,1]

换句话说,只要能满足上述4点,它就是对象;例如,类也是对象(类也是由type实例化产生的)

 

class Foo:
    pass

class Bar:
    pass

print(type(Foo))
print(type(Bar))
# 运行结果:
# <class ‘type‘>
# <class ‘type‘>

定义: 产生类的类称之为元类,默认所有用class定义的类,他们的元类都是type

定义类的两种方式:

  方式一:class方法(class方式也是利用了type这个元类)

  方式二:直接利用type这个元类的方法 

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

"""利用class方法"""
class Chinese:
    country = "China"

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

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

"""利用type自己定义一个类"""
class_name = "Chinese"   # 类名
class_base = (object,)   # 它的基类;元祖的形式

# 类体(即 类内部的代码)
class_body = """
country = "China"

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

def talk(self):
    print("%s is talking" %self.name)
"""
class_dict = {}  # 类的名称空间

exec(class_body,globals(),class_dict)   # 执行类体的代码,并把类体的值放入到 class_dict中作为其名称空间
print(class_dict)
# 打印结果:
# {‘country‘: ‘China‘, ‘__init__‘: <function __init__ at 0x000000D574701E18>, ‘talk‘: <function talk at 0x000000D57487BD90>}

# 定义类
Chinese1 = type(class_name,class_base,class_dict)   # 这样就定义出了一个类

print(Chinese)
print(Chinese1)
# 打印结果:
# <class ‘__main__.Chinese‘>
# <class ‘__main__.Chinese‘>

obj = Chinese("neo",18)
obj1 = Chinese1("egon",22)

print(obj,obj.name,obj.age)
print(obj1,obj1.name,obj1.age)
# 打印结果:
# <__main__.Chinese object at 0x000000F1D54B67B8> neo 18
# <__main__.Chinese object at 0x000000F1D54B6710> egon 22

 

自定义元类控制类的行为:

利用class定义类(如上面的 Chinese)时,完整的写法其实是: class Chinese(object,metaclass = type)  # metaclass = type的含义是 元类是type ; “class Chinese:” 这行代码在执行(即定义Chinese类)时,其实是 Chinese = type(...) ,也就是 type这个元类在实例化。

如下代码:

"""不以type作为元类,而以自己定义的类作为元类,来控制类的行为"""

"""第二步的分析:创建 Mymeta 元类"""
class Mymeta(type):  # class 是调用type这个元类; type元类里面已经定义了很多的方法,而我只是重新定义其中一小部分代码,所以我创建的元类需要继承type,从而Mymeta能够调用type的其他方法
    """
    Chinese 的元类是这个Mymeta,在定义Chinese类的时候需要实例化Mymeta,
    如: Chinese = Mymeta(class_name,class_bases,class_dict)
    需要往Mymeta中传入定义类的三要素,所以 Mymeta中需要有 __init__ 方法
    """
    def __init__(self,class_name,class_bases,class_dict):  # 这个实例化的__init__ 肯定有这三个参数:class_name,class_bases,class_dict
        super(Mymeta,self).__init__(class_name,class_bases,class_dict)   # type 的 __init__ 方法中有很多的功能,自己创建的元类 Mymeta 首先需要继承type的__init__ , 同时再定义自己的方法

        print(class_name)
        print(class_bases)
        print(class_dict)


"""第一步的分析:不以type作为元类,而以我自己创建的类 Mymeta 作为元类;所以我需要先定义一个元类 Mymeta"""
class Chinese(object,metaclass=Mymeta):
    country = "China"

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

    def talk(self):
        print("%s is talking" %self.name)   # 类体代码会以字典的形式传入到class_dict中作为 Chinese类的名称空间

# 运行结果:
# Chinese
# (<class ‘object‘>,)
# {‘__module__‘: ‘__main__‘, ‘__qualname__‘: ‘Chinese‘, ‘country‘: ‘China‘, ‘__init__‘: <function Chinese.__init__ at 0x00000075D7EDBD90>, ‘talk‘: <function Chinese.talk at 0x00000075D7EDBE18>}

"""执行上述代码,直接就执行了 Mymeta 中 __init__ 的三个print,是因为 class Chinese(object,metaclass=Mymeta) 就是Mymeta在进行实例化,而实例化时会自动调用 __init__"""

补充知识点: raise TypeError("类型错误")  # raise 是主动抛出异常,程序就不往下走了

自定义元类控制类的行为示例2:(类名首字母大写)

class Mymeta(type):
    def __init__(self,class_name,class_bases,class_dict):
        """目标功能:如果类名的首字母没有大写,直接报错"""
        if not class_name.istitle():  # .istitle() 用于判断字符串首字母是否大写,返回bool值
            raise TypeError("类名首字母必须大写")  # 首字母没有大写,直接抛出异常,程序终止
        super(Mymeta,self).__init__(class_name,class_bases,class_dict)

class chinese(metaclass=Mymeta):  # 首字母没有大写  # 元类是 Mymeta
    pass

# 执行结果:
# 直接报错
# TypeError: 类名首字母必须大写

 自定义元类控制类的行为示例2:(类体中需要有注释且注释不能为空)

补充知识: 类的名称空间中有一个key 是 “__doc__”,它的含义是“类里面的注释”

class Mymeta(type):
    def __init__(self,class_name,class_base,class_dict):
        if "__doc__" not in class_dict or not class_dict["__doc__"].strip():  # class_dict 中没有"__doc__"这个key 或者 class_dict["__doc__"]为空 # 空字符串和None自带bool值为False
            raise TypeError("必须有注释,且注释不能为空")

        super().__init__(class_name,class_base,class_dict)

class chinese(metaclass=Mymeta):
    pass

# 运行结果:
# 报错
# TypeError: 必须有注释,且注释不能为空

 

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

补充知识点: __call__ 方法: 让对象(如 people)也能有 people()的方法。(此方法是给对象用的)

class Foo:
    def __call__(self, *args, **kwargs):  # 能让对象变成一个可调用对象  # obj()能触发 __call__
        print(self)
        print(args)
        print(kwargs)

obj = Foo()  # 同理,Foo()能这样调用, Foo的那个元类(type)里面也有 __call__ 方法
obj(1,2,3,a="A",b="AB")  # obj也能用类似于类Foo()的方法

# 运行结果:
# <__main__.Foo object at 0x000000D889D0A240>
# (1, 2, 3)
# {‘a‘: ‘A‘, ‘b‘: ‘AB‘}

"""
元类type内部也应该有一个 __call__ 方法, 会在调用Foo时触发执行
Foo(1,2,x=1) 即相当于 Foo.__call__(Foo,1,2,x=1) 同时,类的调用也是一个实例化的过程
"""

自定义元类控制类的实例化:

class Mymeta(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)
"""现在我自己定义一个__call__ 方法""" def __call__(self, *args, **kwargs): print("=====>>") class Chinese(object,metaclass=Mymeta): """ xx """ def __init__(self,name,age): self.name = name self.age = age def talk(self): print("%s is talking" %self.name) # obj = Chinese("neo",22) # Chinese能这样调用说明Mymeta中默认有 __call__ 方法 obj = Chinese("neo",22) # Chinese("neo",22) 在实例化的过程中会自动调用我重新定义的__call__ , 即 Chinese.__call__(Chinese,"neo",22) print(obj) # 运行结果: # =====>> # None """ 正常情况下,类实例化时会有3个过程发生: 1. 先造出一个空对象 2. 根据__init__把空对象初始化 3. 得到一个返回值 """ # 所以我上面在Mymeta中自定义的__call__ 是不规范的

 下面对 Mymeta 中的 __call__ 方法进行修改,并说明类在实例化时自动调用其__init__方法的机制:

class Mymeta(type):
    def __init__(self,class_name,class_bases,class_dict):     # 这个__init__ 控制的是 类(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):  # 它控制的是类(Chinese)的调用
        print("=====>>")
        # 第一步: 先造一个空对象obj
        obj = object.__new__(self)  # object.__new__(cls)的作用是新建一个 cls类 的对象
        # 第二步: 把空对象obj初始化
        self.__init__(obj,*args,**kwargs)  # Chinese调用自己的 __init__  # 把新建的obj这个对象传入到 Chinese类的__init__ 中 # 这行的 *args和**kwargs的含义是: __call__中的*args和**kwargs是怎么接收的,就怎么原封不动的传给这行代码中的*args和*kwargs
        # 第三步: 返回obj
        return obj
    """
    以上也是类(Chinese)在实例化时__init__被触发自动动用的原因,因为 __call__ 被自动调用,而 __call__ 中又手动调用了 类(Chinese)中的 __init__
    """

class Chinese(object,metaclass=Mymeta):
    """
    Chinese中的 __init__ 在被 Mymeta 中的 __call__ 调用时,*args中的"neo"当做位置参数传给 name,**kwargs中的 age=22 当作关键字参数传给 age
    """
    def __init__(self,name,age):
        self.name = name
        self.age = age

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

obj = Chinese("neo",age=22)
print(obj.__dict__)
# 运行结果:
# =====>>
# {‘name‘: ‘neo‘, ‘age‘: 22}

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

单例模式: 如果生成的多个对象里面的属性完全一样,就没必要实例化多次,让这多个对象使用同一块内存空间就行

单例模式实现方式一 :

"""单例模式:让实例化的的对象共用同一个内存地址"""

class mysql:
    __instance = None  # 设置一个隐藏属性,用于储存实例化的对象
    def __init__(self):
        self.host = "127.0.0.1"
        self.port = 3306

    @classmethod
    def singleton(cls):
        if not cls.__instance:  # __instance 为 None时,说明还没有实例化过
            obj = cls()  # 实例化得到一个对象
            cls.__instance = obj # 把实例化的对象赋值给 __instance
        return cls.__instance

    def con(self):
        pass

    def execute(self):
        pass

obj1 = Mysql.singleton()
obj2 = Mysql.singleton()
obj3 = Mysql.singleton()

print(obj1)
print(obj2)
print(obj3)

# 运行结果:
# <__main__.Mysql object at 0x000000E52BF366D8>
# <__main__.Mysql object at 0x000000E52BF366D8>
# <__main__.Mysql object at 0x000000E52BF366D8>

 

单例模式实现方式二: 元类的方式

"""通过自定义元类控制类的实例化过程:单例模式的 __call__ 方法"""

class Mymeta(type):
    __instance = None  # 定义一个隐藏属性用于储存实例化的对象
    def __init__(self,class_name,class_bases,class_dict):
        if not class_name.istitle():
            raise TypeError("首字母必须大写,且只能首字母大写")
        super().__init__(class_name,class_bases,class_dict)

    def __call__(self, *args, **kwargs):
        if not self.__instance: # 如果没有经过实例化
            obj = object.__new__(self)  # 创建一个 self 的新对象
            self.__init__(obj)  # 对新创建的 self的对象执行 Mysql中的 __init__ 方法(实例化)
            self.__instance = obj  # 把实例化后的对象赋值给 __instance
        return self.__instance


class Mysql(metaclass=Mymeta):
    def __init__(self):
        self.host = "127.0.0.1"
        self.port = 3306

    def con(self):
        pass
    def execute(self):
        pass

obj1 = Mysql()
obj2 = Mysql()
obj3 = Mysql()

print(obj1)
print(obj2)
print(obj3)
# 运行结果:
# <__main__.Mysql object at 0x0000001965AC67B8>
# <__main__.Mysql object at 0x0000001965AC67B8>
# <__main__.Mysql object at 0x0000001965AC67B8>

 

以上是关于面向对象:元类的主要内容,如果未能解决你的问题,请参考以下文章

Python----面向对象---元类介绍

面向对象之元类

python面向对象--元类

python 吐血总结面向对象元类

python 吐血总结面向对象元类

面向对象--元类