Python: jQuery-подобная цепочка функций?

В гугле ничего не нашел на эту тему, думаю, стоит спросить здесь:

Можно ли связать функции с Python, как это делает jQuery?

['my', 'list'].foo1(arg1, arg2).foo2(arg1, arg2).foo3(arg1, arg2) #etc...

Я теряю много места и удобочитаемости, когда пишу этот код:

foo3(foo2(foo1(['my', 'list'], arg1, arg2), arg1, arg2), arg1, arg2) #etc...

Кажется, существует какая-то иллюзорная библиотека для создания таких функций, но я не понимаю, почему это должно быть так сложно...

Спасибо!


person Blender    schedule 03.12.2010    source источник
comment
Термин свободный интерфейс. Не знаю, почему GvR их не любит.   -  person Ignacio Vazquez-Abrams    schedule 03.12.2010


Ответы (5)


Вот расширение предложения Саймона ListMutator:

class ListMutator(object):

    def __init__(self, seq):
        self.data = seq

    def foo1(self, arg1, arg2):
        self.data = [x + arg1 for x in self.data]
        # This allows chaining:
        return self

    def foo2(self, arg1, arg2):
        self.data = [x*arg1 for x in self.data]
        return self

if __name__ == "__main__":
    lm = ListMutator([1,2,3,4])
    lm.foo1(2, 0).foo2(10, 0)
    print lm.data

    # Or, if you really must:
    print ListMutator([1,2,3,4]).foo1(2, 0).foo2(10, 0).data

Вы могли бы пойти еще лучше и заставить ListMutator вести себя как список, используя коллекции абстрактных базовых классов. На самом деле, вы можете подклассировать сам list, хотя это может ограничить вас от выполнения определенных действий, которые вам могут понадобиться... и я не знаю, каково общее мнение о создании подклассов для встроенных типов, таких как list.

person detly    schedule 03.12.2010
comment
Проблема с возвратом только self заключается в том, что это просто ссылка в Python, если вы назначаете ее другой переменной. В вашем примере. Если мы добавим lm_two = lm и снова сделаем lm.foo1(2, 0).foo2(10, 0), мы изменим оба (lm и lm_two. Лучше return self.__class__(self.data). - person ucyo; 12.01.2016

Пока функция возвращает значение, вы можете связать ее. В jQuery метод селектора обычно возвращает сам селектор, что позволяет вам выполнять цепочку. Если вы хотите реализовать цепочку в python, вы можете сделать что-то вроде этого:

class RoboPuppy:

  def bark(self):
    print "Yip!"
    return self

  def growl(self):
    print "Grr!"
    return self

pup = RoboPuppy()
pup.bark().growl().bark()  # Yip! Grr! Yip!

Однако ваша проблема, похоже, заключается в том, что аргументы вашей функции слишком ограничены. Цепочка не является решением этой проблемы. Если вы хотите сжать аргументы вашей функции, просто назначьте аргументы переменным, прежде чем передавать их в функцию, например:

spam = foo(arg1, arg2)
eggs = bar(spam, arg1, arg2)
ham = foobar(eggs, args)
person camel_space    schedule 03.12.2010
comment
:D Большое спасибо! Я даже не знал, что мой класс может сделать это! Это хорошо работает для функций этого типа, но я не могу передать возврат одной функции в другую... - person Blender; 03.12.2010
comment
Похоже, это соответствует приведенным здесь примерам: en.wikipedia.org/wiki/Fluent_interface - person Ishpeck; 03.12.2010

Если говорить о методах объекта, то это тривиально, просто return self от каждого метода. С другой стороны, если вы хотите связать несвязанные функции, мне не имеет смысла связывать их так, как вы хотите. Конечно, это выглядит красиво, однако семантически бессвязно, потому что "." означает доступ к атрибутам объекта, а не «цепочку».

person Simon    schedule 03.12.2010
comment
Хм, так вы говорите, что я не могу определять такие функции? Я думаю, что мне придется пойти на jQuery и определить себе функцию типа L('my', 'strange', 'list'). Спасибо! - person Blender; 03.12.2010
comment
Просто как вопрос не по теме, какие специальные символы я могу использовать в имени объекта в Python? Я попробовал $, но интерпретатору это не нравится... - person Blender; 03.12.2010
comment
Ну, технически вы, вероятно, могли бы, поскольку функции также являются объектами в Python. Функция может иметь настраиваемый атрибут, который также является функцией и может вызываться как func1.func2(args). Но это был бы бардак. - person Simon; 03.12.2010
comment
Из вашего примера похоже, что вы изменяете список за несколько шагов. Почему бы вам не пойти питоническим путем и не создать класс ListMutator, в котором есть все функции, которые вам нужны в качестве методов? - person Simon; 03.12.2010
comment
У меня уже есть класс, подобный этому, но я должен инициализировать объект o и вложить функции следующим образом: o.foo(o.foo2(['the', 'list'])), но я знаю, что делаю что-то не так, так как это хуже, чем просто обычные функции... - person Blender; 03.12.2010

На будущее: взгляните на Moka, минималистский библиотека функционального программирования. Из их примеров:

(List()                    # Create a new instance of moka.List
   .extend(range(1,20))    # Insert the numbers from 1 to 20
   .keep(lambda x: x > 5)  # Keep only the numbers bigger than 5
   .rem(operator.gt, 7)    # Remove the numbers bigger than 7 using partial application
   .rem(eq=6)              # Remove the number 6 using the 'operator shortcut'
   .map(str)               # Call str on each numbers (Creating a list of string)
   .invoke('zfill', 3)     # Call zfill(x, 3) on each string (Filling some 0 on the left)
   .insert(0, 'I am')      # Insert the string 'I am' at the head of the list
   .join(' '))             # Joining every string of the list and separate them with a space.

>>> 'I am 007'
person Manuel Ebert    schedule 08.05.2012

Взгляните на это. Это простой класс-оболочка для цепочки. И он реализовал некоторые функции библиотеки underscore.js. Вы оборачиваете свой список, кортеж или словарь символом подчеркивания и играете с ним, а затем получаете из него значение, добавляя еще одно подчеркивание.

print (_([1,2,3])
       .map(lambda x: x+1)
       .reverse()
       .dict_keys(["a", "b", "c"])
       .invert()
       .items()
       .append(("this is what you got", "chaining"))
       .dict()._)

выход:

{2: 'c', 3: 'b', 4: 'a', 'this is what you got': 'chaining'}
person wenjun.yan    schedule 18.07.2013