本文从底层介绍python中的类机制,首先介绍python中类和对象的关系,创建类对象的方式,以及元类的概念,然后分析创建一个类对象的python代码编译后字节码,并基于字节码分析类对象的创建/读取等机制。
0 类即对象
python中,所有都是对象。当然,我们写得类也是一个对象。
Class A(object):
pass
instance_a = A()
上面这三行代码,在python中执行会创建两个对象,一个是class对象,一个是instance对象。当然,这里还有一个对象就是object,他是一个已经存在的class对象。
类对象自身拥有创建实例对象的能力,而这就是为什么它是一个类的原因
通过内置type函数,我们可以查看对象类型。type(A) 和type(object)结果是<type 'type'>,type(a)结果是<class 'main.A'>
<type 'type'>和<class 'main.A'>的区别是什么呢?<type 'type'>是一种特殊的class对象,这种特殊的class对象可以成为其他class对象的type,我们称之为metaclass对象。metaclass对象实例化后就是Class对象,因此ClassA对象几十instance_a对象的class对象,也是metaclass的instance对象。
1 如何创建一个class对象。
在python代码里面,我们使用的语法是通过class关键字创建(类似C++中的声明)一个class对象,创建了这个class对象,我们就可以通过class对象创建instance对象。
除了通过class关键字创建class对象,其实,python本身还提供了一种不常见的创建class对象的方式,那就是type()函数。读者可能会问,type不就是上文提到的查看对象类型的函数么。type(inst)返回inst类型,而type函数还有另一种格式:type(class_name, (base_class,),{'attr_name':class_attr})
type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))
使用type函数创建一个class对象与直接使用class关键字等价:
class Foo(object):
bar = True
等价与:
Foo = type('Foo', (), {'bar':True})
那么,在Python虚拟机中是如何创建的呢。本文不介绍python虚拟机C++代码,但是我浏览了《python源码分析》以及分析发现,python虚拟机创建一个class对象的流程和使用type函数类似,都是处理基类列表,然后填充属性dict,然后基于这些信息创建一个class对象。
下文我们将分析python创建class对象时的字节码来具体说明python创建class对象的具体过程。
3 元类
首先说明,关于元类,我主要是参考了这篇文章:http://blog.jobbole.com/21351/。
我这里简单介绍总结下元类的概念和使用方式,大家可以通过阅读该文章详细了解。
元类是什么?元类就是用来创建class对象的“东西”,简单的说,元类就是类(class对象)的类。
我们可以通过MyClass = type('MyClass', (), {})
创建一个类对象,其实函数type就是一个元类。注意,元类并不一定是一个类,它可以是一个函数。
我们可以通过关键字'metaclass'给一个类指定元类。
class Foo(object):
__metaclass__ = somethine
当我们创建一个class对象(如Foo类对象)时,首先会去查看metaclass属性,然后通过meta创建一个名为Foo的类对象。如果没有找到,那么就用内置的元类,也就是type()函数,去创建这个类对象。
那么,元类可以做什么呢?元类可以在一个类创建的时候,自定义的去改变类。比如,希望自动的把一个类的所有的属性名都改为大写,就可以通过元类来实现。
比如,在我们游戏中有一个使用元类的场景:一个玩家类PlayerClass,它管理一个玩家的所有功能,包括装备、物品、任务、属性、场景、邮箱、技能、好友....为了管理这些代码,一种方式就是把这些代码每个模块都写成一个类分别放在不同的文件,然后PlayerClass的元类是把这些放在个个文件的代码,都整合到PlayerClass里面,这样就可以比较好的管理代码结构,而且每次写的时候想访问其他模块的属性,就可以直接通过self.attr来访问,因为最后他们都属于PlayerClass。(当然,可以使用其他方式来组织代码,比如component等)
总的来说,元类存在的目的是在创建类对象的时候,自动的改变它。
自定义元类
说完应用场景,现在来说说如何编写一个元类。
# 元类会自动将你通常传给‘type’的参数作为自己的参数传入
def upper_attr(future_class_name, future_class_parents, future_class_attr):
'''返回一个类对象,将属性都转为大写形式'''
# 选择所有不以'__'开头的属性
attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
# 将它们转为大写形式
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
# 通过'type'来做类对象的创建
return type(future_class_name, future_class_parents, uppercase_attr)
__metaclass__ = upper_attr # 这会作用到这个模块中的所有类
class Foo(object):
# 我们也可以只在这里定义__metaclass__,这样就只会作用于这个类中
bar = 'bip'
或者,通过类的方式来编写一个元类。
# 请记住,'type'实际上是一个类,就像'str'和'int'一样
# 所以,你可以从type继承
class UpperAttrMetaClass(type):
# __new__ 是在__init__之前被调用的特殊方法
# __new__是用来创建对象并返回之的方法
# 而__init__只是用来将传入的参数初始化给对象
# 你很少用到__new__,除非你希望能够控制对象的创建
# 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
# 如果你希望的话,你也可以在__init__中做些事情
# 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用
def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
return type(future_class_name, future_class_parents, uppercase_attr)
元类本身其实是很简单的:
- 拦截类的创建
- 修改类
- 返回修改之后的类
类机制的实现
实现参考《使用python实现python虚拟机》
代码参考:https://coding.net/u/shinepengwei/p/Byterun/git/blob/master/byterun_class.py
以以下代码为例:
class A(object):##这个是第二行
def __init__(self):
self.a_attr = 1
def func(self,x):
self.a_attr = x
instance_a = A()
这段代码编译后的字节码为:
2 0 LOAD_CONST 0 ('A')
3 LOAD_NAME 0 (object)
6 BUILD_TUPLE 1
9 LOAD_CONST 1 (<code object A at 0x10cfdf930, file "tmp", line 2>)
12 MAKE_FUNCTION 0
15 CALL_FUNCTION 0
18 BUILD_CLASS
19 STORE_NAME 1 (A)
7 22 LOAD_NAME 1 (A)
25 CALL_FUNCTION 0
28 STORE_NAME 2 (instance_a)
31 LOAD_CONST 2 (None)
34 RETURN_VALUE
这一层的字节码就做了两个事情:第一行,创建一个名字为A的clss;第六行创建一个名字为instance_a的对象,类型为A。
之前我们介绍过,我们可以通过MyClass = type('MyClass', (), {})
创建一个类对象。从字节码看也是类似,首先将名字A保存(0),然后生成一个基类的tuple(3,6),然后创建类相应的属性和函数(9,12,15),最后创建类对象(18,19)。
其中,值得我们重点介绍的是创建类相应的属性和函数。
偏移为12的字节码创建了一个函数并且执行了这个函数,这个函数对应的字节码如下:
2 0 LOAD_NAME 0 (__name__)
3 STORE_NAME 1 (__module__)
3 6 LOAD_CONST 0 (<code object __init__ at 0x10cfdf8b0, file "tmp", line 3>)
9 MAKE_FUNCTION 0
12 STORE_NAME 2 (__init__)
5 15 LOAD_CONST 1 (<code object func at 0x10cfdf7b0, file "tmp", line 5>)
18 MAKE_FUNCTION 0
21 STORE_NAME 3 (func)
24 LOAD_LOCALS
25 RETURN_VALUE
这个就是生成类对象的属性dict的函数字节码,可以看出,这个dict保存了类的name属性,以及两个类函数。
LOAD_LOCALS生成了一个dict并返回,这个dict为:
{
'__module__': '__main__',
'func': <__main__.Function object at 0x10cf22cb0>,
'__init__': <__main__.Function object at 0x10cf22c20>
}