- 原文地址:Classes Without Classes
- 原文做者:Fuyukai
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:EmilyQiRabbit
- 校对者:allenlongbaobao,sunhaokk
Python 的对象模型使人难以置信的强大;实际上,你能够重写全部(对象),或者向任何人分发奇怪的对象,并让他们像对待正常的对象的那样接受它。前端
Python 的面向对象是 smalltalk 面向对象的一个后裔。在 Python 中,一切都是对象,甚至对象集和对象类型都是如此;特别的,函数也是对象。这让我很好奇:不使用类建立一个类是否可能?android
这个想法的关键性代码以下所示。这是一个很基础的实现,但它支持 __call__
这样的边缘状况(但不支持其余魔术方法,由于他们须要加载依赖)。后文将会解说。ios
这是一些很先进的 Python 轮子,它用一种和对象的设计初衷毫不相同的方法使用了一些对象。咱们将分段解说代码。git
def _suspend_self(namespace, suspended):
复制代码
这是个让人有点惧怕的函数名。暂停?这可很差,但咱们是能够解决问题的。_suspend_self
函数是 functools.partial
的一个简单应用,它的工做原理是:经过从外部函数做用域中捕获 namespace
,并把它悬停在内部函数中。github
def suspender(*args, **kwargs):
return suspended(namespace, *args, **kwargs)
复制代码
接下来,这个内部的函数调用了和第一个参数 namespace 一块儿传递进来的函数 suspended,实际上这是将方法又包了一层,这样它就能够应用在一个普通的 Python 类上。_suspend_self
余下的部分就只是设置一些属性,这些属性在某些时候可能会被映射(reflection)用到(我可能漏掉一些内容)。后端
下一个函数是 make_class
。从它的签名中咱们能知道什么?bash
def make_class(locals: dict):
""" 在被调用者的本地建立一个类。 参数 locals:创建类的本地。 """
复制代码
若是其余方法请求或者直接取得了你的本地变量,可不是什么好事。一般状况下,这是为了在以前的栈中搜索什么东西,或者就是在黑你的本机。咱们当前的实例属于前面一种,搜索本地函数并加入到类中。函数
# 试着找到一个 `__call__` 来执行 call 函数
# 它将做为一个函数,这样命名空间和被调用者能够引用彼此
def call_maker():
if '__call__' in locals and callable(locals['__call__']):
return _suspend_self(namespace, locals['__call__'])
def _not_callable(*args, **kwargs):
raise TypeError('This is not callable')
return _not_callable
复制代码
这个函数至关简单,它是一个将函数做为返回值的函数! 它实际上作了以下这些事:区块链
__call__
_suspend_self
函数“挂载” namespace 来用 __call__
生成一个方法。__call__
同样,返回一个会发起错误的桩函数(stub function)。namespace 是关键的部分,然而我尚未解说。类中的每个(或者绝大部分)方法都会将 self
做为第一个参数,这个 self
就是函数运行的时候类的实例。ui
一个类的实例实际上就是一个你能够用 .
符号而不是数字索引访问其内容的字典。因此须要一个能够传入咱们指望的函数的对象来模仿这个字典。因而咱们就说,这个实例是一个 namespace
,咱们在 namespace
上设置变量等等。后文提到 namespace
的地方,就把它看成咱们的实例。经过调用类的对象自身,你能够获取这个类的实例:obb = SomeClass()
。
标准的建立点式访问的字典的方法是 attrdict:
attrdict = type("attrdict", (dict,), {"__getattr__": dict.__getitem__, "__setattr__": dict.__setitem__})
复制代码
可是既然它建立了一个类,这就有点欺骗性了。其余的方法包括 typing.SimpleNamespace
,或者建立一个无哨兵(sentinel)的类。可是这两种方法都仍是欺骗性的建立了类,咱们都不能用。
namespace 的解决方案是另外一个函数。函数的行为能够像可调用的点式访问字典,因此咱们就简单的建立一个 namespace
函数,假设它就是 self。
# 这个就充当了 self 对象
# 全部的属性都创建在此之上
def namespace():
return called()
复制代码
须要注意调用 called()
的用法 - 这是为了正常模拟实例上 __call__
的行为。
__init__
Python 中的全部类都有 __init__
(不包括默认提供空 init 的类),因此咱们须要去模仿这一点并确保用户定义的 init 被调用。
# 建立一个 init 的替代方法
def new_class(*args, **kwargs):
init = locals.get("__init__")
if init is not None:
init(namespace, *args, **kwargs)
return namespace
复制代码
这段代码就是简单的从本地获取用户定义的 __init__
,若是找到了,就调用它。而后,它返回 namespace(就是假的实例),有效地模拟了循环:(metaclass.)__call__
-> __new__
-> __init__
。
接下来要作的就是在类的基础上建立方法,这能够用超级简单的循环扫描来完成:
# 更新 namespace
for name, item in locals.items():
if callable(item):
fn = _suspend_self(namespace, item)
setattr(namespace, name, fn)
复制代码
和上文提到的类似,全部可调用的函数都被 _suspend_self
包裹来将函数变成类的方法,在 namespace 完成设置。
最后要作的就是简单的 return new_class
。获取到类的实例的最后一轮循环是:
make_class
来设置 namespace(添加 @make
修饰符,这一步就能自动完成)make_class
函数设置实例,使其为后续的初始化作好准备make_class
函数返回另外一个函数,调用这个函数就能获取到实例并完成它的初始化。如今咱们就获得它了,一个彻底没用类的类。打赌你会实际应用它。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。