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?


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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:
1
2
3
4
5
6
7
8
9
10
11
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:
1
2
3
4
5
6
7
8
9
10
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...
1
2
3
4
5
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...
1
2
3
4
5
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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