Подтвердить что ты не робот

Возвращение self в python

У меня есть класс, представляющий объект. И у меня есть множество методов, которые изменяют это состояние объекта без очевидного возврата или явно без какого-либо возврата. В С# я объявляю все эти методы как void и не вижу альтернатив. Но в Python я собираюсь сделать все методы return self, чтобы дать мне возможность писать потрясающие однострочные строки следующим образом:

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

Является ли это Pythonic или иным образом приемлемым в Python?

4b9b3361

Ответ 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