# -*- coding: utf-8 -*-
class Test:
    pass

print(type(Test))
print(type(int))

# 结果都为<class 'type'>，type就是内置的元类，class关键字定义的所有的类以及内置的类都是由元类type实例化产生。

"""
    由于在python3中没有经典类和新式类的区别，因此python3中object是所有类的基类，那内置元类type又是什么？
    type是object类的类型，总结来说就是type是object的类型，同时，object又是type的基类，这句话看起来就有问题，
    到底是先有type还是先有object呢？这个问题有点类似于先有鸡还是先有蛋，我们可以通过代码简单分析一下
    
    在python3中类和类型是同一种东西，因此对象.__class__和type(对象)得到的结果是一致的，object的基类为空，
    但是type的基类为object，但是object的类型又是type。
"""
print(type(object))
print(object.__class__)

print(type(type))
print(type.__class__)

print(object.__bases__)
print(type.__bases__)

# python字符串
strs = """
global name
global age
name = 'py'
age = 18
addr = 'xx'
"""
# 定义全局作用域中的名字和值
globals = {
    'a': '1',
    'b': '2'
}
# 定义局部作用域中的名字和值
locals = {
    'x': 3,
    'y': 4
}
exec(strs, globals, locals)
print(globals['name'])
print(locals)

# 了解了exec的作用之后，就可以分析class关键字如何借助type元类产生类的步骤：
# 1 定义类名
class_name = 'Test'

# 2 定义类的基类（父类）
class_bases = (object,)

# 3 执行类体代码拿到类的名称空间
class_dic = {}

# 4 定义类体代码（本质是字符串）
class_body = """
def __init__(self,name,age):
    self.name=name
    self.age=age

def test(self):
    print('%s:%s' %(self.name,self.name))
"""
# 5 将字符串转为python能识别的语法:将class_body运行时产生的名字存入class_dic中
exec(class_body, {}, class_dic)
# 查看类的名称空间
print(class_dic)
# 6 调用元类产生类
Test = type(class_name, class_bases, class_dic)
# 7 调用类产生对象
t = Test('python', '12')
t.test()

# 自定义元类
class MyMeta(type):  # 自定义元类必须继承type，否则就是普通的类

    '''
    早于__init__方法执行，必须返回空对象，由于该方法是调用类后第一个运行的方法，
    此时并没有对象产生，因此该方法的第一个参数必须是类本身(MyMeta),*args, **kwargs
    用来接收调用元类产生对象所需的参数（类名 类的基类 名称空间）
    '''
    def __new__(cls, *args, **kwargs):
        return type.__new__(cls, *args, **kwargs)  # 直接调用父类type中的__new__方法

    '''
    通过__init__控制类的产生，调用自定义元类与调用内置元类type的方式相同，
    需要传入类名、类的父类们、类体代码的名称空间,__init__方法中第一个参数来自于__new__方法产生的空对象。
    '''
    def __init__(self, class_name, class_bases, class_dict):

        '''
        在该方法内可以控制类的产生
        '''
        if not class_name.istitle():  # 实现类名首字母必须大写，否则抛出异常
            raise NameError('类名的首字母必须大写')

        if '__doc__' not in class_dict or len(class_dict['__doc__'].strip())==0:  # 实现类必须有文档注释，否则抛出异常
            raise TypeError('必须有文档注释')

    def __call__(self, *args, **kwargs):
        print(self)
        print(args)
        print(kwargs)
        return 'test'

class Test(metaclass=MyMeta):

    '''
    我是文档注释
    '''

    def __init__(self):
        self.name = 'python'

    def test(self):
        print('test')
t = Test()