开发者

lisp-style style `let` syntax in Python list-comprehensions

开发者 https://www.devze.com 2022-12-14 06:24 出处:网络
Consider 开发者_如何学Pythonthe following code: >>> colprint([ (name, versions[name][0].summary or \'\')

Consider 开发者_如何学Pythonthe following code:

>>> colprint([
        (name, versions[name][0].summary or '')
        for name in sorted(versions.keys())
    ])

What this code does is to print the elements of the dictionary versions in ascending order of its keys, but since the value is another sorted list, only the summary of its first element (the 'max') is printed.

Since I am familiar with let from lisp, I rewrote the above as:

>>> colprint([
        (name, package.summary or '')
        for name in sorted(versions.keys())
        for package in [versions[name][0]]
    )]

Do you think this violates being Pythonic? Can it be improved?

Note: For the curious, colprint is defined here.


Why not exploit tuples?

colprint([(name, version[0].summary or '')
      for (name, version) in sorted(versions.iteritems())])

or, even

colprint(sorted([(name, version[0].summary or '')
             for (name, version) in versions.iteritems()]))

Also, you may consider (in my first example) removing the []s, because that way you get a generator instead of a list (which may or may not be useful, since I'm guessing this'll print the whole array, so you won't be saving any evaluations).


I wouldn't use the "tricky for clause" (or "let-equivalent") in most cases, but I would if it's the natural way to avoid repetition, especially costly repetition. E.g.

xs = [(y, y*1.2, y-3.4) for z in zs for y in [somefun(z)] ]

looks much better to me than calling somefun three times!-) So, it's worth keeping in mind, even if probably not worth using where it does not remove repetition.


So you're using "for x in [y]" as a substitute for "let x y".

Trying to emulate language's syntax in another language is never a good idea. I think that the original version is much clearer.


As Tordek says, you can use items() or iteritems() in this case to avoid the issue:

colprint(sorted((name, packages[0].summary or '')
                for (name, packages) in versions.items()))

Moving the sorting outside is a nice touch.

[Note that the use of items() changed the sorting order slightly - it used to be by name with ties resolved by original order (Python sort is stable), now it's by name with ties resolved by summary. Since the original order of a dict is random, the new behaviour is probably better.]

But for other uses (such as Alex Martelli's example), a "let"-alike might still be useful. I've also once discovered the for var in [value] trick, but I now find it ugly. A cleaner alternative might be a "pipeline" of comprehensions / generators, using the "decorate/undecorate" trick to pass the added value in a tuple:

# You could write this with keys() or items() - 
# I'm just trying to examplify the pipeline technique.
names_packages = ((name, versions[name][0]) 
                  for name in versions.keys())

names_summaries = ((name, package.summary or '')
                   for (name, package) in names_packages)

colprint(sorted(names_summaries))

Or applied to Alex's example:

ys = (somefun(z) for z in zs)
xs = [(y, y*1.2, y-3.4) for y in ys]

(in which you don't even need the original z values, so the intermediate values don't have to be tuples.)

See http://www.dabeaz.com/generators/ for more powerful examples of the "pipeline" technique...


You can move the sorting to the end to avoid some intermediate lists.

This looks a bit nicer i guess:

colprint(sorted(
        (name, version[0].summary or '')
        for (name,version) in versions.iteritems())
    ))

Python3 can do even better:

colprint(sorted(
        (name, first_version.summary or '')
        for (name,(first_version,*_)) in versions.items())
    ))
0

精彩评论

暂无评论...
验证码 换一张
取 消