在python中返回self

时间:2016-03-27 18:27:13

标签: python method-chaining

我有一个代表对象的类。我有一堆方法可以修改这个对象状态,没有明显的返回或显然没有任何返回。在C#中,我会将所有这些方法声明为void,并且看不到其他选择。但是在Python中,我将使所有方法return self能够自己编写像这样的令人敬畏的单行:

classname().method1().method2().method3()

这是Pythonic,还是Python中可接受的?

4 个答案:

答案 0 :(得分:25)

以下是来自Guido van Rossum(Python编程语言的作者)的关于此主题的邮件:https://mail.python.org/pipermail/python-dev/2003-October/038855.html

  

我想再次解释为什么我如此坚定,那种()不应该   返回' self'。

     

这来自编码风格(在各种其他语言中流行,我   相信特别是Lisp狂热其中的一系列副作用   在单个对象上可以链接如下:

     

x.compress()。斩(Y)的.sort(z)的

     

相同      

x.compress()x.chop(y)x.sort(z)

     

我发现链接形式对可读性构成威胁;它需要的   读者必须非常熟悉每种方法。该   第二种形式清楚地表明这些调用中的每一个都是相同的   对象,所以即使你不太了解班级及其方法   好吧,你可以理解第二次和第三次调用都适用于   x(并且所有调用都是针对它们的副作用而制作的),而不是   别的什么。

     

我想为返回新值的操作保留链接,   像字符串处理操作:

     

y = x.rstrip(" \ n")。split(":")。lower()

     

有一些标准库模块可以鼓励链接   副作用电话(pstat浮现在脑海中)。不应该有任何新的东西   那些;当它很弱时,pstat穿过我的过滤器。

答案 1 :(得分:14)

对于通过方法构建状态的API,这是一个很好的主意。 SQLAlchemy使用它可以产生很好的效果,例如:

>>> from sqlalchemy.orm import aliased
>>> adalias1 = aliased(Address)
>>> adalias2 = aliased(Address)
>>> for username, email1, email2 in \
...     session.query(User.name, adalias1.email_address, adalias2.email_address).\
...     join(adalias1, User.addresses).\
...     join(adalias2, User.addresses).\
...     filter(adalias1.email_address=='jack@google.com').\
...     filter(adalias2.email_address=='j25@yahoo.com'):
...     print(username, email1, email2)

请注意,在许多情况下它不会返回self;它将返回当前对象的 clone ,其中某个方面已更改。这样,您就可以基于共享库创建发散链; base = instance.method1().method2(),然后是foo = base.method3()bar = base.method4()

在上面的示例中,QueryQuery.join()调用返回的Query.filter()对象不是同一个实例,而是应用了过滤器或连接的新实例。< / p>

它使用Generative base class来构建;因此,使用的模式不是return self,而是:

def method(self):
    clone = self._generate()
    clone.foo = 'bar'
    return clone

使用a decorator进一步简化SQLAlchemy:

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 FooBar(GenerativeBase):
    @_generative
    def method(self):
        self.foo = 'bar'

所有@_generative - 装饰方法必须做的是对副本进行更改,装饰器负责生成副本,将方法绑定到副本而不是原始副本,并将其返回给调用者对你而言。

答案 2 :(得分:12)

这是一个(愚蠢的)示例,它演示了一个好的技术

的场景
class A:
    def __init__(self, x):
        self.x = x
    def add(self, y):
        self.x += y
        return self
    def multiply(self, y)
        self.x *= y
        return self
    def get(self):
        return self.x
a = A(0)
print a.add(5).mulitply(2).get()

在这种情况下,您可以创建一个对象,其中执行操作的顺序严格按函数调用的顺序确定,这可能使代码更具可读性(但也更长)

答案 3 :(得分:4)

如果您愿意,可以在这里使用装饰器。通过代码查看界面的人会很突出,而且您不必从每个函数中明确return self(如果您有多个退出点,这可能很烦人)。

import functools


def fluent(func):
    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        # Assume it's a method.
        self = args[0]
        func(*args, **kwargs)
        return self
    return wrapped


class Foo(object):
    @fluent
    def bar(self):
        print("bar")

    @fluent
    def baz(self, value):
        print("baz: {}".format(value))

foo = Foo()
foo.bar().baz(10)

打印:

bar
baz: 10