Python Forum
Thread Rating:
  • 1 Vote(s) - 5 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How do you do method chaining?
#1
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]
Reply
#2
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)]
Reply
#3
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)
Reply
#4
(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
Test everything in a Python shell (iPython, Azure Notebook, etc.)
  • Someone gave you an advice you liked? Test it - maybe the advice was actually bad.
  • Someone gave you an advice you think is bad? Test it before arguing - maybe it was good.
  • You posted a claim that something you did not test works? Be prepared to eat your hat.
Reply
#5
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]
Reply
#6
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.
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020