3

Is there any non-explicit for way to call a member n times upon an object?

I was thinking about some map/reduce/lambda approach, but I couldn't figure out a way to do this -- if it's possible.

Just to add context, I'm using BeautifulSoup, and I'm extracting some elements from an html table; I extract some elements, and then, the last one.

Since I have:

# First value
print value.text

# Second value
value = value.nextSibling
print value.text

# Ninth value
for i in xrange(1, 7):
    value = value.nextSibling
print value.text

I was wondering if there's any lambda approach -- or something else -- that would allow me to do this:

# Ninth value
((value = value.nextSibling) for i in xrange(1, 7))
print value.text

P.S.: No, there's no problem whatsoever with the for approach, except I really enjoy one-liner solutions, and this would fit really nice in my code.

Rubens
  • 14,478
  • 11
  • 63
  • 92
  • What does `nextSibling` return in the case there is no next sibling? – Silas Ray Jan 31 '13 at 22:03
  • @sr2222 you can consider `nextSibling` is always to return a valid value; the issue I'm trying to point rather about the code writing -- working is a mere consequence (: – Rubens Jan 31 '13 at 22:06
  • So this isn't a "handle n-length list" problem, it's a "one-liner for specific slice" question. – Silas Ray Jan 31 '13 at 22:07
  • @sr2222 I hope the title edit suits better the problem itself. Thanks for pointing it out. – Rubens Jan 31 '13 at 22:10

6 Answers6

7

I have a strong preference for the loop, but you could use reduce:

>>> class Foo(object):
...     def __init__(self):
...        self.count = 0
...     def callme(self):
...        self.count += 1
...        return self
... 
>>> a = Foo()
>>> reduce(lambda x,y:x.callme(),range(7),a)
<__main__.Foo object at 0xec390>
>>> a.count
7
mgilson
  • 300,191
  • 65
  • 633
  • 696
  • whoa, looks very neat; let me try it! – Rubens Jan 31 '13 at 22:04
  • 1
    @StoryTeller -- I'm happy to see alternatives to satisfy OP's criteria. As I state up front, I wouldn't use this. I'd use a loop. – mgilson Jan 31 '13 at 22:05
  • 1
    @StoryTeller: The OP explicitly said he wants a one-liner, even though he knows it isn't idiomatic. So it's a perfectly reasonable answer. – abarnert Jan 31 '13 at 22:05
  • 1
    Wouldn't using map be easier? As in `map(lambda _: a.callme(), range(7))` – l4mpi Jan 31 '13 at 22:05
  • @l4mpi: No, that will return a list of 7 copies of `a.callme()`, not `a.callme().callme()…callme()`. – abarnert Jan 31 '13 at 22:06
  • @l4mpi -- Nope. That calls the `callme` method on the same object 7 times -- It was just easist to have my method return `self` so it could chain an infinite number of `.callme().callme().callme()...` – mgilson Jan 31 '13 at 22:06
  • 1
    Ah, of course... I overlooked the fact that the returned object was used for successive calls as it doesn't make a difference in this example. – l4mpi Jan 31 '13 at 22:08
  • Yes well, my point is that it isn't really a *one-liner*. You have to write quite a few lines of preperation. If it's for a single use, I stand by what I said. – StoryTeller - Unslander Monica Jan 31 '13 at 22:10
  • 2
    @StoryTeller: What lines of preparation? He's just created a simple example to test his one-liner with. – abarnert Jan 31 '13 at 22:11
  • 1
    @StoryTeller the actual solution is just the `reduce` line, everything else is just an example. – l4mpi Jan 31 '13 at 22:12
  • Never read code examples before youv'e had coffee. My apologies @mgilson – StoryTeller - Unslander Monica Jan 31 '13 at 22:13
  • Well, among all the very amazing answers, I'm accepting yours, as I'm some what lambdaddicted (: Thank you all for the answers! – Rubens Feb 01 '13 at 00:34
4

You want a one-liner equivalent of this:

for i in xrange(1, 7):
    value = value.nextSibling

This is one line:

for i in xrange(1, 7): value = value.nextSibling

If you're looking for something more functional, what you really want is a compose function, so you can compose callme() (or attrgetter('my_prop'), or whatever) 7 times.

Community
  • 1
  • 1
abarnert
  • 354,177
  • 51
  • 601
  • 671
2

In case of BS you can use nextSiblingGenerator() with itertools.islice to get the nth sibling. It would also handle situations where there is no nth element.

from itertools import islice

nth = 7
next(islice(elem.nextSiblingGenerator(), nth, None), None)
root
  • 76,608
  • 25
  • 108
  • 120
2

Disclaimer: eval is evil.

value = eval('value' + ('.nextSibling' * 7))
rodrigo
  • 94,151
  • 12
  • 143
  • 190
1

Ah! But reduce is not available in Python3, at least not as a built in.

So here is my try, portable to Python2/3 and based on the OP failed attempt:

[globals().update(value=value.nextSibling) for i in range(7)]

That assumes that value is a global variable. If value happens to be a member variable, then write instead:

[self.__dict__.update(value=value.nextSibling) for i in range(7)]

You cannot use locals() because the list comprehension creates a nested local scope, so the real locals() is not directly available. However, you can capture it with a bit of work:

(lambda loc : [loc.update(x=x.nextSibling) for i in range(7)])(locals())

Or easier if you don't mind duplicating the number of lines:

loc = locals()
[loc.update(value=value.nextSibling) for i in range(7)]

Or if you really fancy one-liners:

loc = locals() ; [loc.update(value=value.nextSibling) for i in range(7)]

Yes, Python can use ; too 8-)

UPDATE:

Another fancy variation, now with map instead of the list comprehension:

list(map(lambda d : d.update(value=value.nextSibling), 7 * [locals()]))

Note the clever use of list multiplication to capture the current locals() and create the initial iterable at the same time.

rodrigo
  • 94,151
  • 12
  • 143
  • 190
  • whoa, man! I'll try this one too. And now I'm quite afraid I don't know which answer to choose ^^ – Rubens Jan 31 '13 at 22:43
  • well you can just put `try: from functools import reduce; except: pass` at the top, and then the code is portable with Python 2 and 3 – newacct Feb 01 '13 at 09:20
  • 1
    I'm pretty sure that this is `locals` abuse -- especially since it's [**not guaranteed**](http://docs.python.org/2/library/functions.html#locals) that modifying locals will modify the current namespace (in which case you'd just end up with a list of `None`, and no mutated `value`) – mgilson Feb 01 '13 at 14:55
1

The most direct way to write it would be:

value = reduce(lambda x, _: x.nextSibling, xrange(1,7), value)
newacct
  • 119,665
  • 29
  • 163
  • 224
  • very nice one, too; I guess this is somehow a shortening on the vars from the accepted answer. Just a question: wouldn't `reduce(lambda x, _: x.nextSibling, xrange(1,7), value)` suffice, as the assignment is already performed in the reduce process? – Rubens Feb 01 '13 at 09:33
  • @Rubens: what do you mean assignment? How would that be possible? Python is pass by value. A function cannot assign to the caller's variables. – newacct Feb 01 '13 at 09:34