Атрибут вызова после цепочки методов

Я создаю класс Python, который использует цепочку атрибутов. Я пытаюсь выяснить, есть ли способ определить, когда вызывается последний атрибут цепочки, и выполнить некоторый код обработки в этой точке. После вызова последнего связанного атрибута я хотел бы обработать собранные данные. Я понимаю, что атрибут обработки может быть вызван явно в конце цепочки, но я хотел бы избежать этого, если это возможно.

Например:

o = ObjectInstance()
# Note that the attribute calling order is subjective
o('data').method_a('other data').method_c('other_data') #o.process() is called here automatically

--Обновление--

Я нашел обходной путь, специфичный для моей ситуации, хотя он не отвечает на основной вопрос.

В моем конкретном случае я намерен обрабатывать несколько цепочек отдельно с помощью одного экземпляра. Переопределив атрибут __call__ моего класса, я могу проверить, была ли обработана предыдущая цепочка, и отреагировать соответствующим образом. Я уже планировал иметь отдельный метод рендеринга — который также может обрабатывать предыдущую цепочку — после того, как все цепочки будут обработаны, поэтому он работает для моего конкретного сценария.

Класс будет выглядеть примерно так:

class Chainable:

    current_chain_data = None
    processed_chains = list()


    def __call__(self, data):
        if self.current_chain_data:
            self.process()

        #Some logic
        self.current_chain_data = data
        return self


    def method_a(self, data):
        #Some logic
        self.current_chain_data = data
        return self

    def method_b(self, data):
        #...

    def process(self, data):
        #do stuff
        self.processed_chains.append(self.current_chain_data)
        self.current_chain_data = None

    def render(self):
        if self.current_chain_data:
            self.process()

        for c in self.processed_chains:
            output += c
        return output

И использоваться как:

c = Chainable()

# do some chaining
c('data').method_a('other_data').method_c('other_data')

# previous chain is processed here, new chain started
c('additional_data').method_b('other_data') #...

# previous chain is processed here, output rendered
c.render()

person Casey Kinsey    schedule 17.03.2012    source источник
comment
Я хотел бы избежать этого, если это возможно. Пожалуйста, не надо. Явное лучше неявного.   -  person Karl Knechtel    schedule 17.03.2012
comment
Я понимаю, откуда вы исходите, и для простого примера, подобного тому, который я разместил выше, я полностью согласен. Тем не менее, для меня ключевая цель состоит в том, чтобы поддерживать (как можно точнее) знакомый синтаксис языка сценариев, отличного от Python.   -  person Casey Kinsey    schedule 17.03.2012
comment
Я предполагаю, что ваше решение может запутать ваших пользователей, потому что оно неоднородно. Что произойдет, если кто-то вызовет другой метод для c перед вызовом c? Ожидали ли ваши пользователи такое поведение?   -  person Marcin    schedule 20.03.2012
comment
Кроме того, теперь вам требуется, чтобы render вызывался как последний метод, иначе ваша последняя цепочка никогда не будет обработана.   -  person Marcin    schedule 20.03.2012


Ответы (2)


Невозможно распознать «последний» вызов, потому что «цепочка» — это не языковая конструкция, а скорее следствие единого синтаксиса в сочетании с вашей собственной (хорошей) практикой возврата одного и того же объекта из нефункционального метода. .

Ваши варианты:

  1. иметь окончательный вызов процесса, как вы предлагаете (во многих отношениях самое чистое решение);
  2. выполнять обработку на каждом этапе (что может быть или не быть хитом производительности, в зависимости от обработки и того, что на самом деле делают ваши методы); или же
  3. пусть каждый метод определяет аргумент ключевого слова, который позволяет вам пометить его как последний вызов, чтобы инициировать обработку.

Я бы предложил вариант 1, если только вариант 2 естественным образом не приводит к снижению производительности, хотя бы по той причине, что он вносит наименьшую сложность, а явное обычно лучше, чем неявное.

Ваше решение по переопределению __call__ имеет несколько недостатков: оно может запутать ваших пользователей, потому что оно неоднородно; если кто-то вызовет другой метод для c перед вызовом c, такое поведение вполне может их удивить (продолжение цепочки); и, наконец, вам все еще нужен последний вызов render, чтобы закрыть последнюю цепочку, что сделает такой код более хрупким, чем необходимо.

person Marcin    schedule 18.03.2012
comment
Я думаю, что все ваши пункты вполне обоснованы. Я решил пойти по пути окончательного вызова обработки (в данном случае метода рендеринга), который будет работать достаточно хорошо (и семантически, я мог бы добавить) с фреймворком, для которого создан этот проект. Я уверен, что это совсем не смутит пользователей, поскольку класс почти точно отражает знакомый синтаксис языка сценариев, отличного от Python, с которым класс напрямую интегрируется. Спасибо! - person Casey Kinsey; 20.03.2012
comment
@CaseyKinsey Круто! Если вы пытаетесь заставить пользователей использовать python, вы также можете предоставить обычный интерфейс, чтобы отучить их от того, к чему они привыкли (конечно, это может вообще не применяться в вашей ситуации). - person Marcin; 20.03.2012

person    schedule
comment
Это сработало бы, если бы был определенный порядок вызова, но, как я отметил в своем вопросе, это субъективно. method_c вполне может вызываться перед method_a. - person Casey Kinsey; 17.03.2012
comment
@CaseyKinsey, в этом случае вам, вероятно, придется явно вызывать .process(), я отредактировал, чтобы отразить это. - person John; 17.03.2012
comment
Да, я отметил в своем вопросе, что мне, возможно, придется согласиться на это. Я нашел обходной путь для моей очень конкретной ситуации, о которой я писал выше, но мне все еще любопытно, есть ли Pythonic-способ справиться с этим более универсальным способом, о котором я не знаю. - person Casey Kinsey; 17.03.2012