Возвращение self в python
У меня есть класс, представляющий объект. И у меня есть множество методов, которые изменяют это состояние объекта без очевидного возврата или явно без какого-либо возврата. В С# я объявляю все эти методы как void
и не вижу альтернатив. Но в Python я собираюсь сделать все методы return self
, чтобы дать мне возможность писать потрясающие однострочные строки следующим образом:
classname().method1().method2().method3()
Является ли это Pythonic или иным образом приемлемым в Python?
Ответы
Ответ 1
Вот письмо от Guido van Rossum (автора языка программирования Python) по этой теме: https://mail.python.org/pipermail/python-dev/2003-October/038855.html
Я хотел бы еще раз объяснить, почему я так категоричен, что sort() не должен return 'self'.
Это происходит из стиля кодирования (популярного на разных языках, я верьте, особенно Lisp упивается в нем), где ряд побочных эффектов на одном объекте можно связать цепочку следующим образом:
x.compress(). Измельчить (у).sort(г)
который будет таким же, как
x.compress() x.chop(y) x.sort(z)
Я считаю, что цепочка создает угрозу читаемости; это требует, чтобы читатель должен быть хорошо знаком с каждым из методов. вторая форма дает понять, что каждое из этих вызовов действует на одно и то же объект, и поэтому даже если вы не знаете класс и его методы очень хорошо, вы можете понять, что второй и третий вызов применяются к x (и что все вызовы сделаны для их побочных эффектов), а не что-то еще.
Я хочу зарезервировать цепочку для операций, возвращающих новые значения, как операции строковой обработки:
y = x.rstrip( "\n" ). split ( ":" ). lower()
Существует несколько стандартных библиотечных модулей, которые побочные эффекты (pstat приходит на ум). Не должно быть никаких новых из них; pstat проскользнул через мой фильтр, когда он был слабым.
Ответ 2
Это отличная идея для 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=='[email protected]').\
... filter(adalias2.email_address=='[email protected]'):
... print(username, email1, email2)
Обратите внимание, что во многих случаях он не возвращает self
; он вернет клон текущего объекта с измененным определенным аспектом. Таким образом, вы можете создавать расходящиеся цепочки на основе общей базы; base = instance.method1().method2()
, затем foo = base.method3()
и bar = base.method4()
.
В приведенном выше примере объект Query
, возвращаемый вызовом Query.join()
или Query.filter()
, представляет собой не тот же экземпляр, а новый экземпляр с фильтром или соединением, примененный к нему.
Он использует Generative
базовый класс для построения; поэтому вместо return self
используется шаблон:
def method(self):
clone = self._generate()
clone.foo = 'bar'
return clone
который 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
-декорированный метод должен сделать, это внести изменения в копию, декоратор позаботится о создании копии, привязывая этот метод к копии, а не оригиналу, и возвращает ее вызывающему пользователю.
Ответ 3
Вот пример (глупый), который демонстрирует сценарий, когда он является хорошим методом
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()
В этом случае вы можете создать объект, в котором порядок выполнения операций строго определяется порядком вызова функции, который может сделать код более читаемым (но и длинным)
Ответ 4
Если вы так желаете, вы можете использовать здесь декоратор. Это будет означать, что кто-то просматривает ваш код, чтобы увидеть интерфейс, и вам не нужно явно return self
от каждой функции (что может раздражать, если у вас несколько точек выхода).
import functools
def fluent(func):
@functools.wraps(func)
def wrapped(*args, **kwargs):
# Assume it 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