class Trick(object):
pass
当Python在执行带 class
语句的时候,会初始化一个类对象放在内存里面。
例如这里会初始化一个 Trick
对象。
这个对象(类)自身拥有创建对象(通常说的实例,但是在Python中还是对象)的能力。
为了方便后续理解,可以先尝试一下在新式类中最古老厉害的关键字 type
。
class Trick(object):
pass
print (type('123'))
<class 'str'>
print (type(123))
<class 'int'>
print (type(Trick()))
<class '__main__.Trick'>
print (type('Trick', (), {}))
<class '__main__.Trick'>
同样可以实例化这个类对象。
print (type('trick', (), {})())
<__main__.trick object at 0x7f6f15a6fef0>
可以看到,这里就是一个 trick
的实例对象了。
同样的这个方法还可以初始化创建类的父类,同时也可以初始化类属性: 复制代码。
class FlyToSky(object):
pass
pw = type('Trick', (FlyToSky, ), {'laugh_at': 'hahahaha'})
print( pw().laugh_at)
hahahaha
print( pw.__dict__)
{'laugh_at': 'hahahaha', '__module__': '__main__', '__doc__': None}
print (pw.__bases__)
(<class '__main__.FlyToSky'>,)
print( pw().__class__)
<class '__main__.Trick'>
print( pw().__class__.__class__)
<class 'type'>
下面将依次理一下上面的内容,在此之前必须先介绍两个魔法方法:
__class__
这个方法用于查看对象属于是哪个生成的,这样理解在Python中的所有东西都是对象, 类对象也是对象。如果按照以前的思维来想的话就是类是元类的实例,而实例对象是类的实例。__bases__
这个方法用于得到一个对象的父类是谁,特别注意一下__base__
返回单个父类,__bases__
以tuple形式返回所有父类。
知道了这两个方法,依次说一下每行的含义:
- 使用
type
创建一个类赋值给pwtype
的接受的三个参数的意思分别是(类的名称, 类是否有父类()
, 类的属性字典{}
)。 - 这里初始化一个类的实例,然后尝试去获得类属性 的
laugh_at
属性值,然后得到结果hahahaha
。 - 取一个
pw
的也就是常见类的类字典数据。 - 拿到
pw
的父类,结果是指定的FlyToSky
。 pw
的实例pw()
属于哪个类初始化的,可以看到是class Trick
。- 再看
class trick
是谁初始化的? 就是元类type
了。
Trick = MetaClass()
MyObject = Trick()
上面已经介绍了,搞一个 Trick
可以直接这样:
Trick = type('Trick', (), {})
可以这样其实就是因为,Type
实际上是一个元类,用他可以去创建类。什么是元类刚才说了,
元类就是创建类的类。也可以说他就是一个类的创建工厂。
类上面的 __metaclass__
属性,相信愿意了解元类细节的盆友,都肯定见过这个东西,
而且为之好奇。不然不知道是什么支撑看到这里的😂。
使用了 __metaclass__
这个魔法方法就意味着就会用 __metaclass__
指定的元类来创建类了。
class Trick(FlyToSky):
pass
当在创建上面的类的时候,Python做了如下的操作:
Trick中有__metaclass__
这个属性吗?如果有,
那么Python会在内存中通过 __metaclass__
创建一个名字为 Trick
的类对象,
也就是 Trick
这个东西。如果Python没有找到 __metaclass_
_,
它会继续在自己的父类 FlyToSky
中寻找 __metaclass__
属性,
并且尝试以 __metaclass__
指定的方法创建一个 Trick
类对象。
如果Python在任何一个父类中都找不到 __metaclass__
,它也不会就此放弃,
而是去模块中搜寻是否有 __metaclass__
的指定。如果还是找不到,
好吧那就是使用默认的 type
来创建 Trick
。
那么问题来了,要在 __metaclass__
中放置什么呢?答案是可以创建一个类的东西,
type
,或者任何用到 type
或子类化 type
的东西都行。
自定义元类
自定义类的的目的,总结了一下就是拦截类的创建,然后修改一些特性, 然后返回该类。是不是有点熟悉?没错,就是感觉是装饰器干的事情, 只是装饰器是修饰一个函数,同样是一个东西进去,然后被额外加了一些东西, 最后被返回。
其实除了上面谈到的制定一个 __metaclass__
并不需要赋值给它的不一定要是正式类,
是一个函数也可以。要创建一个使所有模块级别都是用这个元类创建类的话,
在模块级别设定 __metaclass__
就可以了。先写一个来试试看,
还是延用 stackoverflow
上面的例子,将所有的属性都改为大写的。🤗
示例如下:
input
:
def upper_attr(class_name, class_parents, class_attr):
"""
返回一个对象,将属性都改为大写的形式
:param class_name: 类的名称
:param class_parents: 类的父类tuple
:param class_attr: 类的参数
:return: 返回类
"""
# 生成了一个generator
attrs = ((name, value) for name, value in class_attr.items() if not name.startswith('__'))
uppercase_attrs = dict((name.upper(), value) for name, value in attrs)
return type(class_name, class_parents, uppercase_attrs)
__metaclass__ = upper_attr
pw = upper_attr('Trick', (), {'bar': 0})
print (hasattr(pw, 'bar'))
False
print (hasattr(pw, 'BAR'))
True
print (pw.BAR)
0
可以从上面看到,实现了一个元类(metaclass), 然后指定了模块使用这个元类来创建类,
所以当下面使用 type
进行类创建的时候,可以发现小写的 bar
参数被替换成了大写的 BAR
参数,
并且在最后调用了这个类属性并,打印了它。
上面使用了函数做元类传递给类,下面使用一个正式类来作为元类传递给 __metaclass__
。
class UpperAttrMetaClass(type):
def __new__(mcs, class_name, class_parents, class_attr):
attrs = ((name, value) for name, value in class_attr.items() if not name.startswith('__'))
uppercase_attrs = dict((name.upper(), value) for name, value in attrs)
return super(UpperAttrMetaClass, mcs).__new__(mcs, class_name, class_parents, uppercase_attrs)
class Trick(object):
__metaclass__ = UpperAttrMetaClass
bar = 12
money = 'unlimited'
>>> print (Trick.BAR)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-8-3ff07bba582e> in <module>()
----> 1 print (Trick.BAR)
AttributeError: type object 'Trick' has no attribute 'BAR'
>>> print (Trick.MONEY)