Python Forum
How do you do method chaining? - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: Python Coding (https://python-forum.io/forum-7.html)
+--- Forum: Web Scraping & Web Development (https://python-forum.io/forum-13.html)
+--- Thread: How do you do method chaining? (/thread-3140.html)



How do you do method chaining? - nilamo - May-01-2017

How do you guys do method chaining?  Everything I've tried to break method calls across multiple lines looks pretty ugly.
Or do you guys just not do chaining at all?


class query(object):
    def __init__(self, source):
        self.filters = []
        self.source = source

    def where(self, filter):
        self.filters.append(filter)
        return self

    def get(self):
        all_filters = lambda x: all(func(x) for func in self.filters)
        return list(filter(all_filters, self.source))

things = query(range(1000)
    ).where(lambda x: x % 2 == 0
    ).where(lambda x: x % 3 == 0
    ).where(lambda x: x % 4 == 0
    ).where(lambda x: x % 5 == 0)

#things = query(range(1000)).where(
#    lambda x: x % 2 == 0).where(
#    lambda x: x % 3 == 0).where(
#    lambda x: x % 4 == 0).where(
#    lambda x: x % 5 == 0)

print(things.get())
Output:
[0, 60, 120, 180, 240, 300, 360, 420, 480, 540, 600, 660, 720, 780, 840, 900, 960]



RE: How do you do method chaining? - Mekire - May-01-2017

Not quite sure if this is what you are going for but it gives the same results:
from functools import partial

# ... class definition

def divisible_by(div, num):
    return num % div == 0

things = query(range(1000))

for i in (2,3,4,5):
    things.where(partial(divisible_by, i))
Or even:
from functools import partial

# ... class definition

def divisible_by(div, num):
    return num % div == 0

things = query(range(1000))

things.filters = [partial(divisible_by, i) for i in (2,3,4,5)]



RE: How do you do method chaining? - nilamo - May-01-2017

Yeah, that gives the same results, but it was just a toy class to demonstrate where it could syntactically be useful.  
What if each filter was unrelated from the others?  Like, what if it's a wrapper around BeautifulSoup, and each query is a sub-selector of the parent filter's result?

The original example could also be written like this, though I think using backslashes looks worse than the dangling parentheses...
things = query(range(1000)) \
   .where(lambda x: x % 2 == 0) \
   .where(lambda x: x % 3 == 0) \
   .where(lambda x: x % 4 == 0) \
   .where(lambda x: x % 5 == 0)



RE: How do you do method chaining? - volcano63 - May-01-2017

(May-01-2017, 02:01 PM)nilamo Wrote: The original example could also be written like this, though I think using backslashes looks worse than the dangling parentheses...
things = query(range(1000)) \
   .where(lambda x: x % 2 == 0) \
   .where(lambda x: x % 3 == 0) \
   .where(lambda x: x % 4 == 0) \
   .where(lambda x: x % 5 == 0)
Actually - while I hate backlashes - I would rather use this option - it's ugly but it's more obvious


RE: How do you do method chaining? - nilamo - May-01-2017

If we rewrite the original class to be immutable (...as was my initial intention, but I wrote it fast), it becomes a little more clear why I'm aiming for this.

I guess using temporary variables or just having multiple filters/selectors on a single line is the way to go.

Here's probably a better example:
class query(object):
    def __init__(self, source, filters = ()):
        self.filters = filters
        self.source = source

    def where(self, filter):
        filters = self.filters + (filter, )
        new_query = query(self.source, filters)
        return new_query

    def __and__(self, other):
        filters = self.filters + other.filters
        return query(self.source, filters)

    def get(self):
        all_filters = lambda x: all(func(x) for func in self.filters)
        return list(filter(all_filters, self.source))

things = query(range(1000))

by_9 = things.where(lambda x: x % 9 == 0)

only_odd = things.where(lambda x: x % 2 != 0)
further_filtered = only_odd.where(lambda x: x % 3 == 0).where(lambda x: x % 7 == 0)

print((further_filtered & by_9).get())
# [63, 189, 315, 441, 567, 693, 819, 945]



RE: How do you do method chaining? - Mekire - May-01-2017

Yes, as to your original lambda chaining; I would absolutely never do that and it makes my eyes bleed, if that is the answer you were looking for. That is why I proposed an alternative. xD

If a library I was using forced me to code this way I would really try to find an alternative library as well.