类和对象

class关键词声明的类其实也是对象,比较特殊的是,它是type的实例对象。因此类也可以和对象一样作为参数进行传递等一系列操作。以下两种方法创造的类是一样的:

# type(name, bases, attrs) ,name为类的名称,bases为需要继承的父类的集合,attrs为需要初始化的变量的值
Ex1=type('Example', (), {}) # type创造Ex1的类(不是实例对象)
ex1=Ex1() # 实例化
class Example: # 手动创造
    pass
ex2=Example() # 实例化

进一步验证:

Ex1=type('Example',(),{'age':12})
class Example:
    age=12

ex1=Ex1()
ex2=Example()
print(Ex1.__class__)
print(Example.__class__)
print(ex1.__class__)
print(ex2.__class__)
print(ex1.__class__.__class__)
print(ex2.__class__.__class__)
age=ex1.age
print(age.__class__)
print(age.__class__.__class__)

输出为:

<class 'type'>
<class 'type'>
<class '__main__.Example'>
<class '__main__.Example'>
<class 'type'>
<class 'type'>
<class 'int'>
<class 'type'>

进一步说明了,无论是声明的类,还是实例化的对象,还是自带的数据类型。最终都是由type演变而来。除此之外还可以在声明类时制定metaclass关键词声明元类。所谓元类就是可以隐式的继承给子类的类,同时改变子类在创造过程中的行为。示例如下:

class MyMetaClass(type):
    def __new__(cls, name, base, attrs):
        if name=='Model':
            print('Model BINGO!!')
            return type.__new__(cls, name, base,attrs)
        else:
            print('name={0}. bsae={1}, attrs={2}'.format(name, base, attrs))
            return type.__new__(cls, name, base, attrs)

class Model(metaclass=MyMetaClass):
    def __init__(self, instanceName):
        print(instanceName)

class MyModel(Model):
    def __init__(self, mymodelname):
        print(mymodelname)

class MyModel2(metaclass=MyMetaClass):
    def __init__(self, mymodelname):
        print(mymodelname)

M1=Model('First Model')
M2=MyModel('Second Model')
M3=MyModel2('Last Model')

输出为:

Model BINGO!!
name=MyModel. bsae=(<class '__main__.Model'>,), attrs={'__module__': '__main__', '__qualname__': 'MyModel', '__init__': <function MyModel.__init__ at 0x0000017EC898D940>}
name=MyModel2. bsae=(), attrs={'__module__': '__main__', '__qualname__': 'MyModel2', '__init__': <function MyModel2.__init__ at 0x0000017EC898D9D0>}
First Model
Second Model
Last Model

由此可见,类被实例化的时候,总是会自下而上的寻找metaclass,如果没有的话则按照继承顺序来继承创建,否则按照metaclass当中的创建原则返回实例。

注:__new__()方法属于特殊的@classmethod,无需次关键字声明,同理还有诸如__eq__等方法。因此传入的cls实际上为当前的类,也可以采用其他的关键字替代。
img

类的实例化经过

上一节当在修改实例创建行为的时候是在__new__()方法上实现的,但是我们平常的初始化是在__init__()方法上进行的,这二者有什么关系呢?

对象被实例化的过程实际上是需要先调用__call__()以及__new__()方法进行实例化

# 先call再new最后init
def __call__(obj_type, *args, **kwargs):
    obj = obj_type.__new__(*args, **kwargs)
    if obj is not None and issubclass(obj, obj_type):
        obj.__init__(*args, **kwargs)
    return obj
  • Mode(*args, **kwargs) 等价于 Mode.__call__(*args, **kwargs)
  • 因为 Mode 是 type 的对象,所以 Mode.__call__(*args, **kwargs) 调用的是 type.__call__(Mode, *args, **kwargs)
  • type.__call__(Mode, *args, **kwargs) 调用的是 type.__new__(Mode, *args, **kwargs),然后返回了 obj
  • obj 被 obj.__init__(*args, **kwargs) 初始化
  • 最后 obj 就被返回了

正是因为类每次实例化之前都需要调用__new__()方法,因此可以通过控制__new__()方法来实现单例模式的设计。单例模式本质上就是判断某一个类在实例化的时候确保之前没有被实例化过

class singleton(object):
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = object.__new__(cls)
        return cls._instance

    def __init__(self, modelName):
        self.modelName = modelName

输出:

True
obj2
obj2

可以看到起始输出是不符合我们预期的,按照常理来说实例的名字应该只输出obj1,而不是之后被修改过的(虽然确实达到了单例的目的)。注意到之前提到的先call再new最后init的实例化原则,由于我们控制的是__new__()方法,返回的也是先前实例化的对象,但是之后的__init__()方法每次都会被调用执行。这里说的每次都会被调用指的是__new__()当中返回的是属于自己类的实例之后,__init__()才会被调用进行初始化,返回的是其他类的实例,那么不会调用自己的__init__(),这一点也好理解,别的类需要初始化的参数对于当前类也不知道,因此也无法进行初始化。。对于单例模式而言,调用多次之后会改变之前初始化的参数,这种覆盖不能不是我们希望看到的。

因此可以借助元类在该类初始化的时候就进行控制:

class Singleton(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Cls4(metaclass=Singleton):
    def __init__(self, name):
        print(name)

cls1 = Cls4('1')
cls2 = Cls4('2')
print(id(cls1) == id(cls2))

输出:

1
True

最后可以看到通过元类控制的单例模式是符合我们预期的。

类似文章