011. 如何在Python中动态创建类?

说在前面

  • 答案是type

  • 你印象中的type是用来查看对象的类型的

    li = []
    type(li)  # 得到list
    
  • 对自定义的类是这样的

    class Person:
        pass
    
    wuxianfeng = Person()
    type(wuxianfeng)  # __main__.Person
    
  • 此处的wuxianfeng是一个Person类的实例

  • 既然一切皆对象,那么Person的类型是啥呢?

    type(Person)  # type
    

去官网看看

class type(name, bases, dict, **kwds)

1. 传入一个参数时,返回 object 的类型。 返回值是一个 type 对象,通常与 object.__class__ 所返回的对象相同。
2. 推荐使用 isinstance() 内置函数来检测对象的类型,因为它会考虑子类的情况。

3. 传入三个参数时,返回一个新的 type 对象。 这在本质上是 class 语句的一种动态形式,
	name 字符串即类名并会成为 __name__ 属性;
	bases 元组包含基类并会成为 __bases__ 属性;如果为空则会添加所有类的终极基类 object。
    dict 字典包含类主体的属性和方法定义;它在成为 __dict__ 属性之前可能会被拷贝或包装。 

4. 下面两条语句会创建相同的 type 对象:
        class X:
            a = 1

        X = type('X', (), dict(a=1))

5. 提供给三参数形式的关键字参数会被传递给适当的元类机制 (通常为 __init_subclass__()),相当于类定义中关键字 (除了 metaclass) 的行为方式。

6. 在 3.6 版更改: type 的子类如果未重载 type.__new__,将不再能使用一个参数的形式来获取对象的类型
  1. 传入一个参数时,返回 object 的类型。 返回值是一个 type 对象,通常与 object.class 所返回的对象相同
a = 1
a.__class__
type(a)  # 跟上面的结果是一样的, 但不能1.__class__哦~
  1. isinstance
s1 = 'a'
isinstance(s1,str) # True
isinstance(s1,(str,int,list)) # 一样也是True
class A:
    pass
class B(A):
    pass

b = B()
isinstance(b,B)  # True
isinstance(b,A) # 也是True

# 但是type不一样,不会检查继承
type(b) == B # True
type(b) == A # False
  1. 上面是最简单的使用,最关键的来了,type传入三个参数时,返回一个新的 type 对象。 这在本质上是 class 语句的一种动态形式

    # 在Pycharm上查看type的定义
    class type(object):
        """
        type(object_or_name, bases, dict)  # 2种用法
        type(object) -> the object's type # 上面讲的用法1
        type(name, bases, dict) -> a new type # 创建动态类的另外一种方式
        """
    
    1. name 字符串即类名并会成为 __name__ 属性;# 字符串类型
    2. bases 元组包含基类并会成为 __bases__ 属性;如果为空则会添加所有类的终极基类 object。 
    3. dict 字典包含类主体的属性和方法定义;它在成为 __dict__ 属性之前可能会被拷贝或包装。# 这里可以填写类属性、类方式、静态方法,采用字典格式,key为属性名,value为属性值
    

动态创建类

  • 官网案例

    class X:
        a = 1
    # 等价于
    X = type('X', (), dict(a=1))
    
  • 我们做点拓展

  • 示例1:一些典型的错误

    type('A',,)  # SyntaxError: invalid syntax
    
    type('A',(),)  # TypeError: type() takes 1 or 3 arguments
    
    type('A',(),1) # TypeError: type.__new__() argument 3 must be dict, not int
    
    
    
  • 示例2:类的创建

    # 依旧是错误的示范
    type('A',(),{}) # 相当于创建了一个类的名字叫A
    a = A()  # NameError: name 'A' is not defined  但你不能这么用
    
    # 正确的
    B= type('B',(),{})
    b = B()
    type(b)  # __main__.B
    
    # 诡异的(不推荐)
    D = type('C',(),{})
    x = C()  # 哪个是对的?
    y = D()  # 哪个是对的?
    
    
    
    
    D = type('C',(),{})
    x = C()  # 提示报错了,NameError: name 'C' is not defined
    
    y = D() # 对的
    type(y)  # 结果是啥?  __main__.C 你想到了吗?当然也还是可以理解的
    # type('C',(),{}) 创建一个名叫C的类
    # 但D这个变量接收了上面的返回值,C是没有这个变量的(不在内存中)
    
  • 示例3:带继承的类的创建

    class A:
        pass
    
    B = type('B',('A'),{})  # 这个错比较明显 
    # TypeError: type.__new__() argument 2 must be tuple, not str
    
    
    
    class A:
        pass
    
    B = type('B',('A',),{})
    
    TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
    # 其实错在你不能用'A',这是个str,实际上此处应该是一个类名
    
    • 应该这样
    class A:
        pass
    
    B = type('B',(A,),{})
    b = B() # 实例化没问题
    print(B.__mro__) # mro也是ok的
    # (<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
    
    

  • 示例4:带类属性的创建

    C = type('C',(),{'name':'wuxianfeng'})
    print(C.name)  # 输出 wuxianfeng
    
    • 注意是类属性,并不是实例属性

  • 示例5:带实例方法的创建

    def say(self):
        self.name = 'wuxianfeng'
        print(f'calling method say, my name is {self.name}')
    Person = type('Person',(),{'say':say})
    wxf = Person()
    wxf.say()   # calling method say, my name is wuxianfeng
    
    
  • 多一个例子(因为这是最常见的做法)

    def run(self):
        return 'calling run'
    
    Foo = type('Foo',
               (object,),
               {'value':1,
                'func':lambda self:'calling func',
               'run':run})
    foo = Foo()
    print(foo.value) # 输出 1
    result = foo.func() # 接收返回值‘calling func’
    print(result)   # 输出 calling func
    print(foo.run()) # 输出 calling run
    

说在最后

  • class创建类的本质就是用type创建,所以可以说python中所有类都是type创建的,包括整数、字符串、函数以及用户自定义的类

  • 当type()只有一个参数时,其作用就是返回变量或对象的类型

  • 当type()有三个参数时,其作用就是创建类对象

  • 通过type添加的属性是类属性,不是实例属性

  • type就是Python的内建元类

03-08 09:11