views:

283

answers:

7

I want to convert the following code:

...
urls = [many urls]
links = []
funcs = []
for url in urls:
   func = getFunc(url, links)
   funcs.append(func)
...

def getFunc(url, links):
   def func():
      page = open(url)
      link = searchForLink(page)
      links.append(link)
   return func

into the much more convenient code:

urls = [many urls]
links = []
funcs = []
for url in urls:
   <STATEMENT>(funcs):
        page = open(url)
        link = searchForLink(page)
        links.append(link)

I was hoping to do this with the with statement. As I commented bellow, I was hoping to achieve:

def __enter__():
    def func():

..code in the for loop..

def __exit__():
  funcs.append(func)

Of course this doesn't work.

List comprehensions is not good for cases were the action searchForLink is not just one function but many functions. It would turn into an extremely unreadable code. For example even this would be problematic with list comprehensions:

for url in urls:
  page = open(url)
  link1 = searchForLink(page)
  link2 = searchForLink(page)
  actionOnLink(link1)
  actionOnLink(link2)
  .... many more of these actions...
  links.append(link1)
+2  A: 

Lose the line <STATEMENT>(funcs):

Edit:

I mean: why would you do this? Why define a new function for each page? Why not just do this?

urls = [many urls]
links = []
for url in urls:
    page = open(url)
    link = searchForLink(page)
    links.append(link) 
telliott99
c'mon man.. it's just an example. Of course you can do your suggestion but it's not my question.
Guy
Meh. I voted +1, then though "hang on a minute" and unvoted, and now I decided to vote +1, and I can't. This is really the only answer that makes any sense so far.
Lennart Regebro
but it doesn't answer the question..
Guy
@Guy: Yes it does. It tells you how to rewrite the code in a way that doesn't include that extra function call that you didn't want to have.
Lennart Regebro
There. I edited it so I could vote. :) Just added a space.
Lennart Regebro
Sure it does. The only thing you actually ask is "Ideas?" This is a good one. It's cleaner than your working example, and it doesn't have functions that modify variables that aren't passed into the function. If there's something you're looking for that this doesn't do, please state what it is clearly in your question.
jcdyer
+6  A: 

It makes no sense to use with here. Instead use a list comprehension:

funcs = [getFunc(url, links) for url in urls]
Ignacio Vazquez-Abrams
This will call the function, he wants to return function objects.
Lasse V. Karlsen
@Lasse: `getfunc()` already returns a function object.
Ignacio Vazquez-Abrams
I don't want to use the function `getFunc()`. That's the point
Guy
You really do. http://code.activestate.com/recipes/502271/
Ignacio Vazquez-Abrams
I love how spoiled people get after they start learning Python. You have to write that as a separate function?! GASP
FogleBird
+2  A: 

There are only two ways to create functions: def and lambda. Lambdas are meant for tiny functions, so they may not be very appropriate for your case. However, if you really want to, you can enclose two lambdas within each other:

urls = [many urls]
links = []
funcs = [(lambda x:
            lambda:
              links.append(searchForLink(open(x))))(u)
         for u in urls]

A little too LISPish for my taste.

Max Shawabkeh
+1 Say "LISPish" five times fast...
AJ
agree.. to lispy for me.
Guy
+1  A: 

You should not use "with" to do this (even though, given that it's Python, you almost certainly could, using some bizarre side-effect and Python's dynamicism).

The purpose of "with" in Python is, as described in the docs, "to wrap the execution of a block with methods defined by a context manager. This allows common try...except...finally usage patterns to be encapsulated for convenient reuse."

I think you're confusing Python's "with" with the Javascript/VisualBasic "with", which may be cosmetically similar but which is effectively unrelated.

Peter Hansen
+1  A: 

Good old itertools.

from itertools import imap
links.extend(imap(searchForLink, imap(open, urls)))

Although, maybe you'd prefer functional.

from functional import *
funcs = [partial(compose(compose(links.append, searchForLink), open), url) for url in urls]
for func in funcs: func()

I don't think it's worthwhile creating a class for with use: it's more work to create __enter__ and __exit__ than it is to just write a helper function.

ephemient
that's pretty crazy. The best answer yet - thanks. I think I do understand what `with` does. I was hoping to stick `def func():` in the `__enter__` and `funcs.append(func)` in the `__exit__`. But, obviously, it won't be that easy.
Guy
+5  A: 

A bit unconventional, but you can have a decorator register the func and bind any loop variables as default arguments:

urls = [many urls]
links = []
funcs = []

for url in urls:
    @funcs.append
    def func(url=url):
        page = open(url)
        link = searchForLink(page)
        links.append(link)
Ants Aasma
"Unconvential", to say the least. Still, eerily beautiful ;)
Torsten Marek
I think that the function `func` with get over-ridden every time. So I'll only get left with the last one ie the function that takes care of the last url. Won't I?
Guy
+1: This is really clever! @Guy: default arguments are evaluated at definition time, so the binding is Ok.
Max Shawabkeh
Brilliant trick!
Paul Hankin
+1  A: 

You may be better using generators to achieve the delayed computation you're after.

def MakeLinks(urls):
    for url in urls:
        page = open(url)
        link = searchForLink(page)
        yield link

links = MakeLinks(urls)

When you want the links:

for link in links:
    print link

The urls will be looked up during this loop, and not all at once (which it looks like you're tring to avoid).

Paul Hankin