【问题标题】:Fluent interface with Python与 Python 的流利界面
【发布时间】:2016-06-15 06:38:20
【问题描述】:

我有一个 Python 函数“send_message”,它接受三个参数:

send_message("i like windmills", to="INBOX", from="OUTBOX")

我正在考虑在其上放置一个流畅的界面。理想情况下,我想写以下任何内容:

send_message("i like windmills").to("INBOX").from("OUTBOX")

send_message("i like windmills").from("OUTBOX").to("INBOX")

# The `to()` information is mandatory but the `from()` is not (as with real letters), so this one would also be a valid call:
send_message("i like windmills").to("INBOX")

任何想法如何完成这个或类似的事情?

我理解让对象的方法返回“self”的一般方法,但在我的理解中,这会导致这样的事情:

message = Message("i like windmills")
message.to("INBOX").from("OUTBOX").send()

但是这个不如前面的例子好,然后我实际上更喜欢带有命名参数的原始版本。

感谢任何帮助。

【问题讨论】:

  • 你想实现Builder Patternsend_message 将是创建构建器的工厂方法。
  • 或者你可以实现currying;它避免了 OP 显然不需要的对象“开销”。
  • 嗨,Lutz,不完全是。构建器模式需要链中的最后一个“build()”方法来实际执行操作。我希望找到一种方法,让链中的每个方法都可以判断它是否是最后一个(通过元编程?)。如果是这种情况,它实际上会隐式执行操作。
  • @samba2:那么请编辑您的问题,这与 Builder 模式有何不同。我不明白message.to("INBOX").from("OUTBOX").send() 是如何“不如前一个示例好” 的,我认为您的意思是不应该有一个尾随/build()/send()/whatever() 调用,并且每个方法调用都应该自动计算如果它是链中的最后一个,如果是,则触发.send()。这对我来说听起来不受欢迎且有风险,因为现在你不能像general_msg = send_message("i like windmills").from("OUTBOX")specific_msg = general_msg.to("Shirley") 那样做多个任务。..
  • ...没有第一次分配触发广泛的.send() 给所有人。并且它强制你所有流畅的行成为一行,行长不受限制,不能跨行拆分复杂的代码。

标签: python dsl fluent-interface


【解决方案1】:

可以通过这种方式完成,我不确定是否有更好的方法,因为这是我的第一次尝试。祝你好运!

DEFAULT_SENDER = 'my_address'
#Because the sender object is optional I assume you have a default sender

class Send_message(object):
    def __init__(self, message):
        self.message = message
        self.sender = None
        self.receiver = None
        self.method = None

    def to(self, receiver):
        self.receiver = receiver
        self.method = self.send()
        return self

    def _from(self, sender):
        self.sender = sender
        self.method = self.send()
        return self

    def __call__(self):
        if self.method:
            return self.method()
        return None

    def send(self):
        if self.receiver:
            if not self.sender:
                self.sender = DEFAULT_SENDER

            return lambda:actual_message_code(self.message, self.sender, self.receiver)


def actual_message_code(message, sender, receiver):
    print "Sent '{}' from: {} to {}.".format(message, sender, receiver)



Send_message("Hello")._from('TheLazyScripter').to('samba2')()
Send_message("Hello").to('samba2')._from('TheLazyScripter')()
Send_message("Hello").to('samba2')()

#Only change in actual calling is the trailing ()

通过实现__call__ 方法,我们可以知道我们何时处于调用链的末尾。这当然会添加结尾的 () 调用。并要求您更改指向实际消息传递方法和默认发件人变量的指针,但我认为这将是在不知道链何时结束的情况下实现目标的最简单方法。

【讨论】:

  • 嗨懒惰的脚本家。我投票赞成你的答案,因为它是我最初问题的一个非常简单的解决方案。但是,随着敏捷需求的发展。您指出了我最初的问题中缺少的一个事实:to()method 是强制性的,而 from()method 是可选的。因为您的想法是基于“发送者”和“接收者”这两个属性的存在,所以我认为它不会起作用。
  • 我已经更新了我的代码以适应您更新的参数。
【解决方案2】:

返回self 有一个缺点。改变其中一个变量会影响另一个变量。这是一个例子

取自@TheLazyScripter 代码,但带有修改的示例。

a = Send_message("Hello")
b = a
a = a._from('theLazyscripter')
b = b._from('Kracekumar').to('samba 2')
b()
a.message = 'Hello A'
a.to('samba2')()
b.to('samba 2')()
Send_message("Hello").to('samba2')._from('TheLazyScripter')()
Send_message("Hello").to('samba2')()

a 和 b 变量指向同一个实例。修改一个人的价值会影响其他人。查看输出的第二行和第三行。

输出

Sent 'Hello' from: Kracekumar to samba 2.
Sent 'Hello A' from: Kracekumar to samba2.
Sent 'Hello A' from: Kracekumar to samba 2.
Sent 'Hello' from: TheLazyScripter to samba2.
Sent 'Hello' from: my_address to samba2.

b=a 和修改a 的内容会影响b 的值。

如何消除这种副作用?

而不是返回self,而是返回一个新的实例来消除副作用。

DEFAULT_SENDER = 'my_address'
#Because the sender object is optional I assume you have a default sender

class Send_message(object):
    def __init__(self, message):
        self.message = message
        self.sender = None
        self.receiver = None
        self.method = None

    def _clone(self):
        inst = self.__class__(message=self.message)
        inst.sender = self.sender
        inst.receiver = self.receiver
        inst.method = self.method
        return inst

    def to(self, receiver):
        self.receiver = receiver
        self.method = self.send()
        return self._clone()

    def _from(self, sender):
        self.sender = sender
        self.method = self.send()
        return self._clone()

    def __call__(self):
        if self.method:
            return self.method()
        return None

    def send(self):
        if self.receiver:
            if not self.sender:
                self.sender = DEFAULT_SENDER

        return lambda:actual_message_code(self.message, self.sender, self.receiver)


def actual_message_code(message, sender, receiver):
    print("Sent '{}' from: {} to {}.".format(message, sender, receiver))



a = Send_message("Hello")
b = a
a = a._from('theLazyscripter')
b = b._from('Kracekumar').to('samba 2')
b()
a.message = 'Hello A'
a.to('samba2')()
b.to('samba 2')()
Send_message("Hello").to('samba2')._from('TheLazyScripter')()
Send_message("Hello").to('samba2')()

_clone 方法每次都会创建实例的新副本。注意:当其中一个值是列表或字典时,需要调用深拷贝。这里是字符串,因此不需要。但是思路还是一样的,在返回之前复制每个属性。

输出

Sent 'Hello' from: Kracekumar to samba 2.
Sent 'Hello A' from: theLazyscripter to samba2.
Sent 'Hello' from: Kracekumar to samba 2.
Sent 'Hello' from: TheLazyScripter to samba2.
Sent 'Hello' from: my_address to samba2.

输出行号23 清楚地表明新代码中没有副作用。

我写了一篇关于Fluent Interface的博文

【讨论】:

  • 我同意这个策略。另一个会被 Python 的创建者认为是“非 Python 的”。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-07-19
  • 1970-01-01
  • 1970-01-01
  • 2016-02-27
  • 2019-04-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多