Python idioms I wish I'd learned earlier
prooffreaderplus.blogspot.com
prooffreaderplus.blogspot.com
The nice thing about a Counter is that it will take a collection of things and... count them:
>>> from random import randrange
>>> from collections import Counter
>>> mycounter = Counter(randrange(10) for _ in range(100))
>>> mycounter
Counter({1: 15, 5: 14, 3: 11, 4: 11, 6: 11, 7: 11, 9: 8, 8: 7, 0: 6, 2: 6})
Docs: https://docs.python.org/2/library/collections.html#counter-o..._ in python is commonly used as a variable name for values you want to throw away. For example, let's say you have a log file split on newlines, with records like this:
logline = "127.0.0.1 localhost.example.com GET /some/url 404 12413"
You want to get all the URLs that are 404s, but you don't care about who requested them, etc. You could do this: _, _, _, url, returncode, _ = logline.split(' ')
There's no special behaviour for _ in this case; in fact, normally in the interactive interpreter it's used to store the result of the last evaluated line, like so: >>> SomeModel.objects.all()
[SomeModel(…), SomeModel(…), SomeModel(…), …]
>>> d = _
>>> print d
[SomeModel(…), SomeModel(…), SomeModel(…), …]
Which I think is basically the same behaviour; you run some code, you don't assign it, so the interpreter dumps it into _ and goes on about its day.The second half of this is correct, but it has nothing to do with whether the language is statically or dynamically typed. It's a tweak to the parser, mostly.
The issue is that there are languages (like C) where typing is static but weak, so e.g. booleans are also integers and can have integer operations like '>' applied to them. In other words, the problem is that in C True == 1 and 1 > 2 is a valid expression. In Python, which has strong(er) types, this expression can have an unambiguous meaning if you want to add the feature to your parser.
Your discussion of types here is all wrong - it's true that C treats booleans as if they were integers, but Python does, too:
>>> (3 > 4) < 2
True
>>> 3 > 4 < 2
False
>>> 3 > (4 < 2)
True
It has nothing to do with types. >>> 0 < x < 1
as opposed to >>> 1 > x > 0
Following the number line and placing x there is nicer IMHO.
Other than that, very nice trick.As an example for what I was specifically trying to show here, though, that doesn't let me distinguish things quite as clearly.
I believe that (a < x < b) gives the same value as at least one of (a < x) and (x < b) for any value of x.
x < a:
a < x gives false
a < x < b gives false
a < x < b:
everything gives true
b < x:
x < b gives false
a < x < b gives false
So there's no way to get both of the parenthesized versions to disagree with the unparenthesized version in a single example.>>> True + 0
1
>>> type(True)
<type 'bool'>
>>> type(1)
<type 'int'>
Edit: oops, I'm wrong. "bool" also inherits from "int":http://stackoverflow.com/questions/8169001/why-is-bool-a-sub...
It's 100% a tweak to the parser.
Assuming filmor is correct, in practice as it happens to be implemented in the Python code base it is not 100% a tweak to the parser - changing the structure of the produced AST means tweaks down the line. I think the change to the parser is still the most meaningful piece, though, even there.
Obviously, that's not what's going on in Python...
It could be smoother and more generic if Haskell permitted specifying defaulting for non-numeric types. But it does support things like:
*Main> 2 < 3 < 4 < 4 :: Bool
False
*Main> 2 < 3 < 4 <= 4 :: Bool
True
*Main> 2 <= 3 < 4 <= 4 :: Bool
True
*Main> 2 <= 3 > 4 <= 4 :: Bool
False
*Main> 2 <= 6 > 4 <= 4 :: Bool
TrueTransforming code into Beautiful, Idiomatic Python by Raymond Hettinger at PyCon 2013
https://speakerdeck.com/pyconslides/transforming-code-into-b... and https://www.youtube.com/watch?v=OSGv2VnC0go&noredirect=1
Topics include: 'looping' with iterators to avoid creating new lists, dictionaries, named tuples and more
>>> print "* "* 50
to quickly print a separator on my terminal :)Previous discussion on python idioms from 300 days ago: https://news.ycombinator.com/item?id=7151433
Python overloads "+" as concatenate for strings. This also applies to lists. So
[1,2,3] + [4,5,6] yields [1,2,3,4,5,6]
This is cute, but not what you want for numerical work.Then, viewing multiplication as repeated addition, Python gives us
[1,2,3]*4 yields [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
This is rarely what was wanted.Then there's numpy, which has its own array type, needed because Python's array math is slow. Numpy arrays have different semantics - add and multiply perform the usual numeric operations. You can mix numpy arrays and built-in lists with "+" and "*". Mixed expressions are evaluated using numpy's semantics. Since Python doesn't have type checking, it's possible to pass the wrong kind of numeric array to a function and have it treated with the wrong semantics.
Moral: when designing your language, using "+" for concatenation is tempting, but generalizing that concept comes back to bite you.
[1,2,'q',[1,('a',2)]] + 4
yield? The reason why numpy lets you do math operation on each element in an array is because you can safely assume that each element is a number. You can assume absolutely nothing about the types of the elements in a list.Just because you can define semantics for nonsense doesn't mean you should.
(also, your issue is not with "nonsense semantics", it's with "my idea of how this operator should've been overloaded is different from their idea", and perhaps is even a beef with the idea of operator overloading in general, though if you like numpy I think you wouldn't like losing operator overloading)
[1,2,'q',[1,('a',2)]] * 4
With element-by-element operations, that would be [1 * 4,2 * 4,'q' * 4,[1,('a',2)] * 4]
giving [4, 8, 'qqqq', [1 * 4, ('a', 2) * 4]]
applying that again, assuming that tuple * scalar is also applied element-wise gives: [4, 8, 'qqqq', [4, ('a' * 4, 2 * 4)]]
and ends up with [4, 8, 'qqqq', [4, ('aaaa', 8)]]
I can't think of any case where that's meaningful.Also, what should this do:
x = [1]
x.append(x)
print(x + x)
print(x * 4)
? Currently these print: [1, [1, [...]], 1, [1, [...]]]
[1, [1, [...]], 1, [1, [...]], 1, [1, [...]], 1, [1, [...]]]
because the print function knows how to handle recursive definitions. Do all of the element-wise operations need to handle cyclical cases like this? I think numpy can get away with not worrying about this precisely because, as
wodenokoto pointed out, it can assume a flat structure.I don't know what else you would have expected...
[n * 4 for n in [1, 2 3]]
or
map(lambda n: n * 4, [1, 2, 3])
I do non-numeric scientific computing. (Meaning, I touch numpy about once a year.) My own code does things like
[0] * N # could be replaced with something like numpy.zeros()
[QUERIES_FPS] * 501
to_trans = [None]*256 # constructing a 256 byte translation table
# (I could use zeros(), but used None to force an exception
# if I missed a cell)
self.assertEqual(self._get_records(simple.record*2),
[(simple.title, simple.record)]*2)
# I parse a string containing two records and test I should
# be able to get the (id, record) for each one
["--queries", SIMPLE_FPS, "-k", "3", "--threshold",
"0.8"] + self.extra_args # Construct a command-line from 2 lists
These idioms also exist in the standard library, like: webbrowser.py: cmdline = [self.name] + [arg.replace("%s", url)
sre_compile.py: table = [-1] + ([0]*len(prefix))
ntpath.py: rel_list = [pardir] * (len(start_list)-i) + path_list[i:]
traceback.py: list = ['Traceback (most recent call last):\n']
list = list + format_tb(tb, limit)
So while I think you are correct, in that "+" causes confusion across multiple domains with different meaning for "+", I think the moral is that operating overloading is intrinsically confusing and should be avoided for all but the clearest of use cases.There is no best generalization to "+". For example, if you pick the vector math meaning, then regular Python would have that:
["A", "B"] + ["C", "D"] == ["AC", "BD"]
which has its own logic, but is likely not what most people who haven't done vector math expect. for item in orders:
do_something_with(item)
So if `foo` is usually `[Order(...), Order(...), ...]` but due to a bug elsewhere, sometimes `foo` is "some string". Then you get a mysterious exception somewhere down in `do_something_with` or one of its callees at run time, and all because the above snippet calls do_something_with('s'), do_something_with('o'), etc.In my experience, this behavior is so seldom what is wanted that it should be removable (with a from __future__ style declaration) or just off by default.
def unhex(s):
"""Get the integer value of a hexadecimal number."""
bits = 0
for c in s:
c = bytes((c,))
if b'0' <= c <= b'9':
i = ord('0')
elif b'a' <= c <= b'f':
i = ord('a')-10
elif b'A' <= c <= b'F':
i = ord(b'A')-10
else:
assert False, "non-hex digit "+repr(c)
bits = bits*16 + (ord(c) - i)
return bits
Here's another example of iterating over characters in a string, from pydoc.py: if any((0xD800 <= ord(ch) <= 0xDFFF) for ch in name)
It seems like a pretty heavy-weight prohibition for little gain. After all, you could pass foo = open("/etc/passwd") and still end up with a large gap between making the bug and its consequences.Not sure what it adds, but I don't quite understand it yet and perhaps there's something magic in the context of MIME quoted printable that I'm missing.
Based on my reading, there's nothing magic. The context is:
elif i+2 < n and ishex(line[i+1:i+2]) and ishex(line[i+2:i+3]):
new = new + bytes((unhex(line[i+1:i+3]),)); i = i+3
I tweaked it to new = new + bytes((int(line[i+1:i+3], 16),)); i = i+3
and the self-tests still pass. (I also changed the 16 to 15 to double-check that the tests were actually exercising that code.)It's not part of the public API, so it looks like it can simply be removed.
Do you want to file the bug report? Or perhaps it's best to update http://bugs.python.org/issue21869 ("Clean up quopri, correct method names encodestring and decodestring")?
https://docs.python.org/3/library/functions.html#int
So that is actually standard. Maybe I just don't know what you mean by public API though.
Its tricky; if you want to do vectors, use numpy.
So it was good design decision not to bother with math semantics for general use datastructure.
And besides Python has nice general syntax for elementwise operations if you don't care about performance:
[x*y for (x,y) in zip(xs,ys)]
I agree it would be better not to implement + for lists at all.But a list is much more than that - conceptually it's any ordered collection of things, and not necessarily even the same type of thing. Overloading `+` to mean concatenation and `*` to mean replication means that the operators can work with any kind of list, not just lists that are mathematical vectors.
If you do want a mathematical vector, you should use a numpy array - not only are you making it clear that you have a vector of numbers, but your operations will be more efficient (because using a numpy array guarantees that the elements in it are all of the same type, so you don't have to do type dispatching on every element).
With that being said, if I want to merge two lists and apply an operation on each, I don't see what's the issue with:
In [1]: a = [1,2,3]
In [2]: b = [5,6,7]
In [3]: c = a+b
In [4]: c
Out[4]: [1, 2, 3, 5, 6, 7]
In [5]: d = [x*4 for x in c]
Out[6]: [4, 8, 12, 20, 24, 28]In particular, #7 is something that I didn't even know existed, and I've been hacking around for 2+ years.
Instead of:
mdict={'gordon':10,'tim':20}
>>> print mdict.get('gordon',0)
10
>>> print mdict.get('tim',0)
20
>>> print mdict.get('george',0)
0
I've always done the much more verbose: class defaultdict(dict):
def __init__(self, default=None):
dict.__init__(self)
self.default = default
def __getitem__(self, key):
try:
return dict.__getitem__(self, key)
except KeyError:
return self.default
mdict=defaultdict(0)
mdict['gordon']=10
mdict['tim']=20
print mdict['gordon']
10
print mdict['tim']
20
print mdict['george']
0
I'll be sure to make great use of the dictionary get method - I'm embarrassed to admit how many thousands of times I could have used that, and didn't know it existed.I clearly am going to have to spend a few hours today grokking everything that collections.* has to offer. Thanks very much.
There's a lot of hidden gems.
Your idea is nice in a syntactic sugar way, also, the default being a part of the dictionary rather than the get function makes it copyable.
I'll give you another gem that could be interesting: else clause in for loops
https://speakerdeck.com/pyconslides/transforming-code-into-b...
whereas dic.get with default value keep returning you the default value without touching your dict.
europa - The dictionary is only modified if you are using a method to modify it. When you are just passively querying it, it's not impacted.
class defaultdict(dict):
def __init__(self, default=None):
dict.__init__(self)
self.default = default
def __getitem__(self, key):
try:
return dict.__getitem__(self, key)
except KeyError:
return self.default
mdict=defaultdict(0)
mdict['gordon']=10
mdict['tim']=20
print mdict['gordon']
print mdict['tim']
print mdict['george']
print mdict
10
20
0
{'tim': 20, 'gordon': 10}from collections import defaultdict s = 'mississippi' d = defaultdict(int) for k in s: d[k] += 1 d.items()
[('i', 4), ('p', 2), ('s', 4), ('m', 1)]
opt = {0: do_a,
1: do_b,
3: do_b,
4: do_c}
opt[option]()And it's something I do on occasion. For example:
https://github.com/ubernostrum/webcolors/blob/master/webcolo...
I'd love to hear why you think it's not ideal.
Having said that, just because I consider something more Pythonic doesn't mean I prefer it. I've worked in a lot of languages over the years and still work in several in addition to Python. I really enjoy Python, but I prefer techniques that are more universal in many cases. For example, I prefer the idiom of looping over a list index to using Python's "enumerate" in most cases, because index looping is a common cross-language idiom and enumerate usually doesn't offer any benefit I value more than universal obviousness.
Other things such as Python's `for item in items` looping style are both VERY Pythonic and much nicer than, say, index looping, so I would almost always prefer such idioms.
The above switch -> function pointerish thing is clear to me from years of C/C++, but it is both less generally applicable across languages than if-elif... and less Pythonic, so I would prefer the if-elif... approach.
Obviously a matter of preferences, but since you asked....
I sometimes see this in code and think to myself, whoever wrote this needs to learn themselves some idiomatic python :) I don't think there's much point trying to force stuff that makes sense in one language into another language. Play to a languages strengths and all that.
I think you're right about if-elif generally being more powerful than the jump table. Though the jump table is useful in that you can define it in one place and use it in another.
I'm not the poster you posed that question to. But for me, the one big drawback of using that idiom is that the function signatures have to be identical. So you either have to resort to args/kwargs, or you have an additional intermediary method between the actual "guts" of what you're calling, and the "switch" statement.
Or you live with the fact that you're passing unused/unnecessary parameters to your functions.
s = {'square': lambda x: x **2, 'simple': lambda x: x, 'cube': lambda x: x ** 3}
s.get('square', lambda x: 0)(10)
s.get('nonexistent', lambda x: 0)(10)
Substitute lambdas for real functions and you have a powerful switch case.If your dict keys are just numbers, then no, probably not. But strings mapping to functions, and in some cases objects and other things, are often used to substitute for numerous if and elif statements.
func = getattr(self, 'do_%s' % thing)
func(args)
I've seen in some codebases :)It's one of the examples in Forth. Plan 9 uses that technique in C a lot too.
This example is ramfs which creates an in memory file system (that you can mount in Unix btw, in 166 LoC)
http://swtch.com/usr/local/plan9/src/lib9p/ramfs.c
fsopen, fsread, fswrite, fscreate are C functions declared in the same source file :
Srv fs = {
.open= fsopen,
.read= fsread,
.write= fswrite,
.create= fscreate,
};
fs is then passed to a library which calls them as needed.Edit: Looked it up, dispatch table is in Wikipedia:
It's worth mentioning that this is a somewhat controversial practice. Guido has even discussed removing C-style string literal concatenation:
http://lwn.net/Articles/551438/
You may wish to consult your project's style guide and linter settings before using it.
I think in most cases it's better to use triple quotes. And if the content of these variables isn't exclusively shown in the shell, you should use translation files anyway.
$ cat triple.py
def foo():
print """this is a triple quoted string
this is a continuation of a triple quoted string"""
if __name__ == '__main__':
foo()
$ python triple.py
this is a triple quoted string
this is a continuation of a triple quoted string
This is really warty. In bash you can mostly get around this with <<- for here documents (which removes leading indentation, so even if you're defining a here doc at a place that itself is indented, you don't have to align the text to the left column yourself). The man page in my version suggests it only works for leading tabs, not spaces, though.e.g.
$ function usage() {
cat <<-END
this is a here document that
spans multiple lines and is indented
END
}
$ usage
this is a here document that
spans multiple lines and is indented
$ s = "\n".join(["one","two","three"])The performance hit of doing this kind of thing really adds up in a larger app.
def foo():
print """\
this is a triple quoted string
this is a continuation of a triple quoted string"""Having multi line prints in functions add a lot of noise in my opinion. When i read code, i dont normally care about the content of messages being printed.
http://book.realworldhaskell.org/read/characters-strings-and...
from __future__ import print_function
import textwrap
t = """
Hello there
This is aligned
"""
# Need strip to get rid of extra NLs
print(textwrap.dedent(t).strip())Sure, it's just syntax sugar, but it saves a lot of keystrokes, especially if the variable name is long.
Is Python the only language with this feature?
if 2 > 3 and 2 < 7
becomes
if 3 < 2 < 7
No. See, e.g., http://stackoverflow.com/questions/4090845/language-support-...
> perl6
> 3 < 4 < 5
TrueWhat does 5 > 4 > 3 give?
(< a b c d) ;; T if a < b < c < d
(<= a b c d) ;; T if a < b < c < d
Also: (lcm a b c d ...) ;; lowest common multiple
(+) -> 0
(+ a) -> a
(+ a b) -> a + b
(+ a b c) -> (a + b) + c
(*) -> 1
(* a) -> a
(* a b) -> a * b
(* a b c) -> (a * b) * c
Is it just syntactic sugar? (< a b c) evaluates a, b and c only once, which matters if they are expensive external function calls or have side effects. (and (< (foo) (bar))
(< (bar) (baz)))
isn't the same as (< (foo) (bar) (baz))
By the way, this could be turned into a short-circuiting operator: more semantic variation. Suppose < is allowed to control evaluation. Then an expression like (< a b c d) could avoid evaluating the c and d terms, if the a < b comparison fails. a < b >= c
?Obviously, you can express it slightly more verbosely:
(and (< a b) (>= b c))
(rel a < b >= c)
evaluates a, b, c once, left to right, and then performs the comparisons between the successive evaluated terms. $ cat rel.lisp
(defmacro rel (&rest args)
(loop for expr in args by #'cddr
for g = (gensym)
collect g into gens
collect `(,g ,expr) into lets
finally (return `(let ,lets
(and
,(loop for (left op right) on args by #'cddr
for (lgen rgen) on gens
while rgen
collect `(,op ,lgen ,rgen)))))))
$ clisp -q -i rel.lisp
;; Loading file rel.lisp ...
;; Loaded file rel.lisp
[1]> (macroexpand '(rel))
(LET NIL (AND NIL)) ;
T
[2]> (macroexpand '(rel x))
(LET ((#:G3219 X)) (AND NIL)) ;
T
[3]> (macroexpand '(rel x < y))
(LET ((#:G3220 X) (#:G3221 Y)) (AND ((< #:G3220 #:G3221)))) ;
T
[4]> (macroexpand '(rel x < y >= z))
(LET ((#:G3222 X) (#:G3223 Y) (#:G3224 Z))
(AND ((< #:G3222 #:G3223) (>= #:G3223 #:G3224)))) ;
T
[5]> (macroexpand '(rel x < y >= z < w))
(LET ((#:G3225 X) (#:G3226 Y) (#:G3227 Z) (#:G3228 W))
(AND ((< #:G3225 #:G3226) (>= #:G3226 #:G3227) (< #:G3227 #:G3228)))) ;
T
Could use some error checking, obviously, to make it a production-quality macro. (AND ((...) (...) ...)))
should be (AND (...) (...) ...)
I haven't run the generated code once, yet I can debug it: such is the power of the HN development environment.The fix, of course, is to splice the comparison expressions into the AND:
`(let ,lets
(and
,@(loop for ... ))) ; comma splat, not comma (<= 1 2 2 5 6 6 9)
and for making = actually useful (works on nested structures properly)."Is this sorted", and "are these equal" are intuitive and useful concepts in programming and you shouldn't need to reimplement them each time you need them.
def sideEffects():
print "Called."
return 5
if 0 < sideEffects() < 10:
# sideEffects is only called once from random import shuffle
deck = ['%s of %s' % (number, suit) for number in '2 3 4 5 6 7 8 9 10 Jack Queen King Ace'.split(' ') for suit in 'Hearts Clubs Diamonds Spades'.split(' ')]
shuffle(deck)But I've seen it many times so it's probably pythonic
Avoids lots of ", "
It avoids having to do a big change if you need to add a new item with spaces in it.
('Hearts', 'Diamonds', 'Spades', 'Clubs')
and has less opportunity for typos and syntax errors. If I was concerned about performance I would replace it with a tuple, but it was Good Enough for a quick example.It's not about performance, but about one more incidental step when reading the code. Small cost, but it's there.
But you have a good point. YAGNI is YAGNI.
(I'm glad I'm not the only one who was thrilled to discover enumerate()!)
If you google 'os vs subprocess python' there are a few stackoverflow and quora threads comparing them.
tl;dr
* Security (including avoiding holes like shellshock)
* Unification of process handling under one core library.
* Easier detection of errors in processes.
* Easier control of stderr/stdout.
* Universal newline support.
* Elimination of race conditions with .communicate()
Subprocess uses OS under the hood but offers an abstraction that mostly works on all platforms, e.g. the way that "communicate" is implemented on Windows differs considerably from how its implemented on Unix.
1. Am I the only one that really loves that `print` is a statement and not a function? Call me lazy, but I don't mind not having to type additional parentheses.
5. Dict comprehensions can be dangerous, as keys that appear twice will be silently overridden:
elements = [('a', 1), ('b', 2), ('a', 3)]
{key: value for key, value in elements} == {'a': 3, 'b': 2}
# same happens with the dict() constructor
dict(elements) == {'a': 3, 'b': 2}
7. I see D.get(key, None)
way too often.8. Unpacking works in many situations, basically whenever a new variable is introduced.
for i, el in enumerate(['a', 'b']):
print i, el
{key: value for (key, value) in [('a', 1), ('b', 2), ('a', 3)]}
map(lambda (x, y): x + y, [(1, 2), (5, -1)])
Note: the last example (`lambda`) requires parentheses in `(x, y)`, as `lambda x, y:` would declare a two-argument function, whereas `lambda (x, y):` is a one-argument function, that expects the argument to be a 2-tuple.If you think the parentheses are bad, why wouldn't you prefer a language like Ruby where you can omit them generally? Leaving them out for just one special construct seems so insufficient as a cure.
However, Python's `print` statement is (1) well-known, (2) very useful (for prototyping, debugging, in REPL), and (3) shouldn't in general be present in production code (logging should be used instead). Therefore, omitting parentheses would help ease debugging and exploring (REPL prototyping), while not making code more ambiguous in general. Yes, it's a special case, but `print` is also has very special, very specific use-case.
In [9]: %autocall Automatic calling is: Smart
In [10]: def foo(a, b): return a + b ....:
In [11]: foo 3, 4 -------> foo(3, 4) Out[11]: 7
`
map(print, range(10))
It can also take some kwargs that allow you to do things that are a bit clunky with the print statement.Which is the more intuitive of the following?:
print(errmsg, file=sys.stderr)
print >>sys.stderr, errmsg
(I didn't even know about the latter until I found it in someone's code and looked it up) Also, suppressing line endings: print "cats",
or print("cats", end='')
My lazy-brain prefers the statement. My sensible/code-review brain prefers the function.The rest could probably be special-cased in a backwards-compatible way as well. This is currently not valid Python 2.0 syntax:
print "cats", end='', file=sys.stderrHonestly, I'm reasonably happy with the split; there don't seem to be many compelling reasons to shoehorn all the extra bits back into statement-print other than
a) removing a single pair of parens
b) being backwards compatible (but then the old code wouldn't be using those new features anyway, and would still have to support that nasty bitshift hack.
D.get(key, None)
is just syntax noise (in the best case - in the worst case, it signifies someone who doesn't know/understand Python). D.get(key)
should be used instead, or D.get(key, "whatever")
if required. D.get(key, None)
since I forget that dict.get and getattr() have different behavior in the case of missing keys/attributes...> lambda (x, y): x + y
This syntax is removed in python 3: http://legacy.python.org/dev/peps/pep-3113/
additionally, if you want to override print in python2, you need to replace the stdout stream with your own inbetween buffer object, which also has the downside of being global
foo = bar if qux is None else baz
They're particularly interesting when combined with comprehensions. ['a' if i % 2 == 0 else 'b' for i in range(10)]
Though this particular example can be expressed much more concisely. ['a', 'b'] * 5 foo = qux == NULL ? bar : baz;
Of course, C does not do list comprehensions. foo = [baz, bar][qux is None] ['a' if i % 2 == 0 else 'b' for i in range(10)]
very cool!I was on aware of code like
['a' for i in range(10) if i % 2 == 0] ['a' if i % 2 == 0 else 'b' for i in range(10)]
very cool!I was only aware of code like
['a' for i in range(10) if i % 2 == 0]When I first started using Python around 1999, it didn't even have list comprehensions. Code was extremely consistent across projects and programmers because there really was only one way to do things. It was refreshing, especially compared to Perl. It was radical simplicity.
Over the decade and a half since then, the Python maintainers have lost sight of the language's original elegance, and instead have pursued syntactical performance optimizations and sugar. It turns out that Python has been following the very same trail blazed by C++ and Perl, just a few years behind.
(At this point Python (especially with the 2 vs. 3 debacle) has become so complex, so rife with multiple ways to do even simple things that for a small increase in complexity, I can just use C++ and solve bigger problems faster.)
It's certainly up for debate whether named tuples and enums, various kinds of metaprogramming and decorators might be making the language more complex for fairly little gain... but this article talks about the `enumerate` function, about string formatting and dictionary comprehensions. Simple, straightforward stuff with no downsides.
a = [_ * 2 for _ in range(10)]
is a lot more pleasant than: a = []; for _ in range(10): a.append(_ * 2)
It also gives Python a lot more information about your actual intent. Suppose "range(10)" were actually "giant_list". Hypothetically, the list comprehension could pre-allocate len(giant_list) elements instead of calling list.append that many times. That's potentially a huge performance win.You see Python as getting more complex. I see it as getting less complex by giving concise alternatives to common idioms.
[0] http://www.wikihow.com/Make-Phirni-%28a-Rice-and-Milk-Dish%2...
Can anyone link to good explanations of list comprehensions and lambda functions?
http://www.pythonforbeginners.com/basics/list-comprehensions...
I also wish that ranges were an actual proper set implementation - so you could, for example, take intersection and union of ranges.
And I wish that Python had an explicit concatenation operator.
You need to store all possible numbers between the lower and upper bound, which isn't exactly workable for (for example) floats.
d = dict((key(x), value(x)) for x in xs)I'm not entirely sure how to interpret the PEP header. It dates back to 2001 and was updated in 2012. It's probably in python since 2.3 but maybe 2.7(2010)/3.0(2008).
http://www.rafekettler.com/magicmethods.html
http://sahandsaba.com/thirty-python-language-features-and-tr...