我有一个代表对象的类。我有一堆方法可以修改这个对象状态,没有明显的返回或显然没有任何返回。在C#中,我会将所有这些方法声明为void
,并且看不到其他选择。但是在Python中,我将使所有方法return self
能够自己编写像这样的令人敬畏的单行:
classname().method1().method2().method3()
这是Pythonic,还是Python中可接受的?
答案 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()
。
在上面的示例中,Query
或Query.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