返回资讯中心
外部精选
软件工程
#开发者实时流

500行以内:简单对象模型实现类属性读写与isinstance检查

来源标题:500 Lines or Less: A Simple Object Model

一篇技术文章展示了如何用不到500行代码实现简单对象模型,使类的属性读写和isinstance检查通过测试。类本身作为对象,可读写属性;isinstance通过沿继承链向上查找来判定实例归属。

Hacker News 最新signa112026/11/9 · 今天30 分钟阅读

以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。

现在第一个测试通过了。第二个测试检查属性的读写是否也能在类上正常工作。这个测试很容易写,而且立刻就能通过。

def test_read_write_field_class():

类也是对象

Python 代码

class A(object): pass A.a = 1 assert A.a == 1 A.a = 6 assert A.a == 6

对象模型代码

A = Class(name="A", base_class=OBJECT, fields={"a": 1}, metaclass=TYPE) assert A.read_attr("a") == 1 A.write_attr("a", 5) assert A.read_attr("a") == 5

isinstance 检查

到目前为止,我们还没有利用对象拥有类这一事实。下一个测试实现了 isinstance 机制:

def test_isinstance():

Python 代码

class A(object): pass class B(A): pass b = B() assert isinstance(b, B) assert isinstance(b, A) assert isinstance(b, object) assert not isinstance(b, type)

对象模型代码

A = Class(name="A", base_class=OBJECT, fields={}, metaclass=TYPE) B = Class(name="B", base_class=A, fields={}, metaclass=TYPE) b = Instance(B) assert b.isinstance(B) assert b.isinstance(A) assert b.isinstance(OBJECT) assert not b.isinstance(TYPE)

要检查一个对象 obj 是否是某个类 cls 的实例,只需检查 cls 是否是 obj 所属类的超类,或者就是该类本身。要检查一个类是否是另一个类的超类,需要沿着该类的超类链向上查找。当且仅当在該链中找到另一个类时,它才是超类。一个类的超类链(包括该类本身)被称为该类的“方法解析顺序”。它可以很容易地递归计算:

class Class(Base): ... def method_resolution_order(self): """ 计算该类的方法解析顺序 """ if self.base_class is None: return [self] else: return [self] + self.base_class.method_resolution_order() def issubclass(self, cls): """ self 是 cls 的子类吗? """ return cls in self.method_resolution_order()

有了这段代码,测试就通过了。

这个对象模型第一版还缺少的最后一个功能是能够在对象上调用方法。在本章中,我们将实现一个简单的单继承模型。

def test_callmethod_simple():

Python 代码

class A(object): def f(self): return self.x + 1 obj = A() obj.x = 1 assert obj.f() == 2 class B(A): pass obj = B() obj.x = 1 assert obj.f() == 2 # 在子类上也能工作

对象模型代码

def f_A(self): return self.read_attr("x") + 1 A = Class(name="A", base_class=OBJECT, fields={"f": f_A}, metaclass=TYPE) obj = Instance(A) obj.write_attr("x", 1) assert obj.callmethod("f") == 2 B = Class(name="B", base_class=A, fields={}, metaclass=TYPE) obj = Instance(B) obj.write_attr("x", 2) assert obj.callmethod("f") == 3

要找到发送给对象的方法的正确实现,我们沿着对象所属类的方法解析顺序进行查找。在方法解析顺序中某个类的字典里找到的第一个方法会被调用:

class Class(Base): ... def _read_from_class(self, methname): for cls in self.method_resolution_order(): if methname in cls._fields: return cls._fields[methname] return MISSING

结合 Base 实现中的 callmethod 代码,这个测试就通过了。

为了确保带参数的方法也能正常工作,并且方法重写被正确实现,我们可以使用下面这个稍微复杂一些的测试,它已经通过了:

def test_callmethod_subclassing_and_arguments():

Python 代码

class A(object): def g(self, arg): return self.x + arg obj = A() obj.x = 1 assert obj.g(4) == 5 class B(A): def g(self, arg): return self.x + arg * 2 obj = B() obj.x = 4 assert obj.g(4) == 12

对象模型代码

def g_A(self, arg): return self.read_attr("x") + arg A = Class(name="A", base_class=OBJECT, fields={"g": g_A}, metaclass=TYPE) obj = Instance(A) obj.write_attr("x", 1) assert obj.callmethod("g", 4) == 5 def g_B(self, arg): return self.read_attr("x") + arg * 2 B = Class(name="B", base_class=A, fields={"g": g_B}, metaclass=TYPE) obj = Instance(B) obj.write_attr("x", 4) assert obj.callmethod("g", 4) == 12

基于属性的模型

现在我们的对象模型的最简单版本已经可以工作了,我们可以考虑改变它的方式。本节将介绍基于方法的模型和基于属性的模型之间的区别。这是 Smalltalk、Ruby 和 JavaScript 与 Python 和 Lua 之间的核心区别之一。

基于方法的模型将方法调用作为程序执行的基本操作:

result = obj.f(arg1, arg2)

基于属性的模型将方法调用拆分为两步:查找属性和调用结果:

method = obj.f result = method(arg1, arg2)

这个区别可以在下面的测试中展示:

def test_bound_method():

Python 代码

class A(object): def f(self, a): return self.x + a + 1 obj = A() obj.x = 2 m = obj.f assert m(4) == 7 class B(A): pass obj = B() obj.x = 1 m = obj.f assert m(10) == 12 # 在子类上也能工作

对象模型代码

def f_A(self, a): return self.read_attr("x") + a + 1 A = Class(name="A", base_class=OBJECT, fields={"f": f_A}, metaclass=TYPE) obj = Instance(A) obj.write_attr("x", 2) m = obj.read_attr("f") assert m(4) == 7 B = Class(name="B", base_class=A, fields={}, metaclass=TYPE) obj = Instance(B) obj.write_attr("x", 1) m = obj.read_attr("f") assert m(10) == 12

虽然设置与方法调用的相应测试相同,但方法的调用方式不同。首先,在对象上查找具有方法名称的属性。该查找操作的结果是一个绑定方法,一个封装了对象以及在类中找到的函数的对象。接下来,该绑定方法通过调用操作被调用。

(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →