元类介绍

元类

什么是元类呢,有句话是Python中一切皆对象。

class StanfordTeacher(object):
    school = 'Stanford'
    def __init__(self,name,age):
        self.name = name
        self.age = age
        
    def say(self):
        print(f'{self.name says welcome to the Stanford to learn Python}')

所有的对象都是实例化或者说是调用类而得到的(调用类的过程就是类的实例化),比如t1是调用类StanfordTeacher得到的

t1 = StanfordTeacher('Jerry',18)
print(type(t1))	# 查看对象t1的类是<class '__main__.StanfordTeacher'>

如果说一切皆为对象,那么类StanfordTeacher本质上也是一个对象,既然所有的对象都是调用类得到的,那么StanfordTeacher必然也是调用了一个类得到的,这个类称为元类

根据推导,我们可以说 在产生StanfordTeacher的过程中一定发生了:StanfordTeacher = 元类(...)

print(type(StanfordTeacher))
# 结果为<class 'type'> 证明是调用了type这个元类而产生的StanfordTeacher,也就是说默认的元类为type

exec的用法

exec:是一个Python的内置函数,可以将字符串的代码添加到名称空间中

exec需要三个参数:

"""	参数一:包含一系列Python代码的字符串
	参数二:全局作用域(字典形式),如果不指定,默认为global()
	参数三:局部作用域(字典形式),如果不指定,默认为locals()
可以把exec命令的执行当成是一个函数的执行,会将执行期间产生的名字存放于局部名称空间中
"""
g = {'x': 1, 'y': 2}
l = {}
exec("""
global x, z
x = 100
z = 200
m = 300
""", g, l)
print(g)	#{'x':100, 'y':2, 'z':200,......}
print(l)	#{'m': 300}

练习

x = 10  # 定义全局变量
expr = """
z = 30
y = 5
global x    				# 声明全局变量x
x = 18
m =10

sum = x + y + z
print(sum)  # 求和
"""
func1 = {'x': 1, 'y': 2}    # 自定义全局名称空间
func2 = {'x': 3, 'y': 4}    # 自定义局部名称空间


def func():
    y = 20
    x = 11
    # exec(expr)    		# 此处进行了字符串expr中的计算---> 结果为53

    exec(expr, func1, func2)    # 此处又进行了字符串expr中的计算
    print(func1,'111')  	# {'x':18,'y':2}111
    print(func2,'222')  	# {'x':3,'y':5,'z':30,'m':10,'sum':53}222
	# 注意:全局名称空间中的值x不影响局部,且sum求和的值在上面已经进行了计算
func()

元类

说了这么多,类的类就是元类,所有通过关键字class定义的类,都是相当于通过调用type()得到的对象(因为一切皆对象)

  • 元类有什么作用呢

    • 定义类,让我们更好的理解类的产生和类的底层原理
    • 元类可以控制类的创建过程
  • 怎么创建元类

    • 通过class定义类
    class Chiness:
        country = 'China'
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def speak(self):
            print(f'{self.name} speak Chinese')
    
    p_obj = Chiness('tank', 18)
    print(p_obj)
    
    <class '__main__.Chinese'>
    <__main__.Chinese object at 0x000001E97164CA48>
    
    • 通过元类type来定义类的方法
    class_name = 'Chinese'  # 类名
    class_dic = {}  # 局部名称空间(类的名称空间)
    code = """
    country = 'China'
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def speak(self):
        print(f'{self.name} speak Chinese')
    """
    exec(code, {}, class_dic)   # 通过exc将字符串加入名称空间
    print(class_dic)
    Chinese = type(class_name, (object,), class_dic)
    print(Chinese)
    obj = Chinese('tank', 18)   # 这里可以将obj换为Chinese和class方法一样
    print(obj)
    
    {'country': 'China', '__init__': <function __init__ at 0x00000213808D1558>, 'speak': <function speak at 0x00000213808D14C8>}
    <class '__main__.Chinese'>	# 可以看到与上面class创建的类一样
    <__main__.Chinese object at 0x00000213824EC9C8>
    
    • 怎么使用
    # 自定义一个元类
    class MyMetaClass(type):
        # 定义元类自己的属性,并重用父类type中的属性
        def __init__(self, class_name, class_bases, class_dict):
            print(self)
            if not class_name.istitle():    # 判断类名首字母是否大写
                raise NameError('类名首字母必须大写哦宝贝')
            if not class_dict.get('__doc__'): # 判断类的名称空间中文档属性是否为空(就是类中是否写了注释)
                raise TypeError('必须写注释哦小宝贝')
            # 重用父类的属性
            super().__init__(class_name, class_bases, class_dict)
    
        """
        元类中的__call__就是创建类的过程
        __call__在通过对象()时调用,此处的对象为元类MyMetaClass
        (User,(object,), User.__dict__)产生的对象User,在User()实例化时调用的
        """
        def __call__(self, *args, **kwargs):
            print(self)
            # 默认继承object类中的__new__方法,并将User传入得到一个对象,若不用此方法会出现递归
            obj = object.__new__(self)
            print(obj)
            self.__init__(obj, *args, **kwargs) # 类调用类内部方法时,相当于是一个普通函数
            return obj
    
    class User(object, metaclass=MyMetaClass):
        """元类实验"""
        def __init__(self):
            print('tank_dsb')
    
    obj = User()
    print(obj)
    print(User)
    
    <class '__main__.User'>
    <class '__main__.User'>
    <__main__.User object at 0x00000279B014CC48>
    tank_dsb
    <__main__.User object at 0x00000279B014CC48>
    <class '__main__.User'>
    
原文地址:https://www.cnblogs.com/YGZICO/p/12050809.html