Python类方法链式调用

13
为了避免在架构决策中迷失方向,我将用一个类比的例子来询问:
假设我想要一个像这样的Python类模式:
queue = TaskQueue(broker_conn)
queue.region("DFW").task(fn, "some arg") 

这里的问题是如何设计一个类,使得某些方法可以以这种方式“链式”调用。

task() 需要访问 queue 类实例的属性以及 region() 的输出结果决定了 task() 的操作。

我看到 SQLalchemy 这样做(见下文),但很难在他们的代码中挖掘出来并隔离这个模式。

query = db.query(Task).filter(Task.objectid==10100) 
2个回答

17

在这种调用下,SQLAlchemy会产生一个克隆,详见Generative._generate()方法,它只是返回当前对象的克隆。

每次调用生成方法(如.filter().orderby()等),都会返回一个新的克隆对象,其中特定方面被修改(如查询树被扩展等)。

SQLAlchemy使用@_generative装饰器来标记必须在此处操作和返回克隆的方法,用所生成的克隆替换self

在您自己的代码中使用此模式非常简单:

from functools import wraps

class GenerativeBase(object):
    def _generate(self):
        s = self.__class__.__new__(self.__class__)
        s.__dict__ = self.__dict__.copy()
        return s

def _generative(func):
    @wraps(func)
    def decorator(self, *args, **kw):
        new_self = self._generate()
        func(new_self, *args, **kw)
        return new_self
    return decorator


class TaskQueue(GenerativeBase):
    @_generative
    def region(self, reg_id):
        self.reg_id = reg_id

    @_generative
    def task(self, callable, *args, **kw):
        self.tasks.append((callable, args, kw))

.region().task()的每个调用现在都将产生一个克隆,修饰方法通过改变状态更新该克隆。然后返回克隆体,使原始实例对象保持不变。


实际上,我认为这个解决方案是我正在寻找的,但是在实现它时遇到了困难:http://pastebin.com/HpsgK0T5 - 我一直在尝试调试它,但一直没有成功,“TaskQueue”对象不可调用。 - NFicano
@NFicano:你改变了装饰器的签名;你用fn替换了self - Martijn Pieters
@NFicano:我现在看到我的回答中确实有一个打字错误;已经将 fn(...) 更正为 func(...) - Martijn Pieters
啊,我没有看到你的编辑,我知道 fn 不对,但是想不出它应该指什么,再次感谢! - NFicano
1
@HalcyonAbrahamRamirez:不,它是实际的类对象。与 type(instance) 返回的相同对象。 - Martijn Pieters
显示剩余6条评论

6

只需像这样从region方法返回当前对象即可:

def region(self, my_string):
    ...
    ...
    return self

由于region返回具有task函数的当前对象,因此现在可以进行链接。

注意:

如@chepner在评论部分提到的那样,请确保regionself对象进行更改。


此外,region 需要以某种方式修改 self,因为假设 queue.region("DFW").task(fn, "some arg") 应该与 queue.task(fn, "some arg") 不同。或者,region() 不应返回 self,而是另一个 TaskQueue 对象(或者具有 task 方法的任何对象)。 - chepner
你需要在这里返回一个 克隆;一个复制了self但已应用更改的副本。 - Martijn Pieters
1
@MartijnPieters 为什么?您能详细解释一下吗? - thefourtheye
@thefourtheye:这就是SQLAlchemy的作用:返回一个新的实例对象,然后允许进一步过滤。原始查询对象不受影响。 - Martijn Pieters
@thefourtheye 我们如何准确修改 self? - Halcyon Abraham Ramirez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接