Search code examples
pythonchaining

Call attribute after chaining methods


I'm building a Python class that utilizes attribute chaining. I'm trying to figure out if there is a way to identify when the final attribute of a chain is called and execute some processing code at that point. After the final chained attribute is called, I'd like to process the collected data. I realize that a processing attribute could be called explicitly at the end of the chain, but I'd like to avoid that if possible.

For example:

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

--Update--

I've found a workaround that is specific to my situation, though it does not answer the underlying question.

For my particular case, I intend to process multiple chains separately with a single instance. By overriding the __call__ attribute of my class, I can check if the previous chain has been processed, and react accordingly. I was already planning to have a separate rendering method---which can also process the previous chain--after all chains were processed, so it works for my particular scenario.

The class would look something like:

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

And be used like:

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()

Solution

  • There is no way to recognise a "last" call, because a "chain" is not a language construct, but rather a consequnce of a uniform syntax combined with your own (good) practice of returning the same object from a non-functional method.

    Your options are:

    1. have a final process call, as you suggest (in many ways the cleanest solution);
    2. perform your processing at every stage (which may or may not be a performance hit, depending on the processing, and what your methods actually do); or
    3. have each method define a keyword argument that allows you to label it as the final call, in order to trigger processing.

    I would suggest option 1, unless option 2 naturally produces no performance penalty, if for no other reason than it introduces the least complexity, and explicit is usually better than implicit.

    Your solution of overriding __call__ has several drawbacks: it is likely to confuse your users, because it is non-uniform; if someone calls another method on c before calling c, the behaviour might well surprise them (chain continued); and finally, you still need a final render call to close out the last chain, which will make such code more fragile than necessary.