-
Notifications
You must be signed in to change notification settings - Fork 0
metaclass
之前想清楚了写到了笔记中,最近看到python3.6又出了个__init_subclass__
,之前的东西又全忘了.这次在总结一下.
new: 结合javascript的原型链体会一下动态语言一切皆对象的思想.
#!/usr/bin/env python
class Type(object):
print("运行到", "Type")
def __init__(self, type_):
print("set type", type_)
self.type_class = type_
def vaild(self, value):
return isinstance(value, self.type_class)
class TypeCheckMeta(type):
print("运行到", "TypeCheckMeta")
def __new__(cls, name, bases, dict):
print("元类 __new__")
inst = super(TypeCheckMeta, cls).__new__(cls, name, bases, dict)
inst._fileds = {}
for k, v in dict.items():
if isinstance(v, Type):
inst._fileds.setdefault(k, v)
return inst
def __init__(cls, *args, **kwargs):
print("元类 __init__")
super(TypeCheckMeta, cls).__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
print("元类 __call__")
return super(TypeCheckMeta, self).__call__(*args, **kwargs)
class Test(metaclass=TypeCheckMeta):
print("运行到", "Test")
name = Type(str)
age = Type(int)
def __new__(cls, *args, **kwargs):
print("类 __new__")
print(args, kwargs)
return super(Test, cls).__new__(cls)
def __setattr__(self, key, value):
print("类 __setattr__")
if key in self._fileds:
if not self._fileds[key].vaild(value):
raise TypeError("invaild...")
super(Test, self).__setattr__(key, value)
def __init__(self, a):
print("类 __init__")
def __call__(self, *args, **kwargs):
print("类 __call__")
t = Test(1)
print(t)
场景就是需要你对变量做强制性检查.
注释掉最后两行代码,会发现如下输出
运行到 Type
运行到 TypeCheckMeta
运行到 Test
set type <class 'str'>
set type <class 'int'>
元类 __new__
元类 __init__
首先,Python在加载的时候扫过整个文件.遇到类定义的时候如下执行: 0. 解析类中的元素, 创建类变量与方法, 并加载到类空间中. 类的基类信息, 元类等信息.
- 找到该类的元类,然后调用元类的
__new__
方法,参数是(类名where,类基类bases,类空间dict)
- 元类的
__new__
最终一定会调用内置类型type.__new__
-
type.__new__
会调用元类的__init__
创建出一个类对象放在内存中.至此类对象已经加载完成了.
现在我们来看看执行的
t = Test(1)
print(t)
-
Test
是什么?从语法层面上,他是一个类.但是在执行过程中,经过上面的加载步骤,它是一个生成的实例,所以Test()
会调用元类的__call__
方法. - 元类一定又得陷入
type.__call__
方法. -
type.__call__
方法调用类__new__
方法. - 类的
__new__
方法一定又陷入object.__new__
-
object.__new__
调用类的__init__
方法,最终一个实例被创建出来了.
触发时机: 子类加载时触发.
具体的: 加载子类时发现父类定义了__init_subclass__
方法,那么在元类__new__
之后__init__
之前调用这个__init_subclass__
.(实现应该不是这样子的,应该是基于回调.比如在type这个元元元类基础上检测调用__init_subclass__).
这样就不需要写元类就可以修饰子类了.
其传参数方式并没有什么魔法,
class SubClass(Father, param="haha"):
pass
在传递给__new__
的时候给现在要多一个位置参数
def __new__(cls, name, bases, dict, **kwargs):
pass
这样子__init_subclass__
也可以获取到了.
function People(name) {
this.name = name
}
p = new People("Irn")
不了解js原型链的同学会很疑惑这种写法.很明显通过关键词function
表明了People是个函数
,那么,new 函数
算什么语法?
实际上js里的函数可不仅仅是一个c语言层面的函数,他是一个完整的实例,是Function创建出来的实例.
f = new Function('name', 'return alert("hello, " + name + "!");');
这样我们就有一个constructor
的对象,他们可以使用new
关键词.
在创建一个对象的时候,解释器做了如下工作:
- It creates a new object.
- It sets the constructor property of the object to Vehicle.
- It sets up the object to delegate to Vehicle.prototype.
- It calls Vehicle() in the context of the new object.
具体的看这里
其中在设置prototype的时候完成了类变量,类函数的继承.
最后一步调用函数的.prototype.call(第一步创建出来的空对象)
,这个时候函数的this参数就会指向这个新的对象,这样就会拥有实例变量(每个人都不一样的)
所以最合理的模拟类实现是这个样子的
// Class definition / constructor
var Vehicle = function Vehicle(color) {
// Initialization
this.color = color;
}
// Instance methods
Vehicle.prototype = {
go: function go() {
return "Vroom!";
}
}
prototype里的东西所有的变量都共享一份,在实例里找不到就会向上查找,而function这个构造器里的this因为指向的是一个{}每次执行都会重新赋值一遍,而且会屏蔽在prototype上设置的属性.
this和prototype是js里非常聪明的做法也是整个的基石.