7 ms·
Of these, only += on tuple elements and indexing with floats strike me as WTFs, and many others are evidence of the author really going out of their way to look
by ak217 11y ago
Of these, only += on tuple elements and indexing with floats strike me as WTFs, and many others are evidence of the author really going out of their way to look for behaviors to nitpick on. The absolute majority of these are things that Python programmers either will never encounter, or can easily explain from Python "first principles" (e.g. most of the nitpicks on string and sequence behaviors).
I can tell you what I consider WTF worthy - things that I and others have undoubtedly been bitten by because they are ubiquitous. Those are mutable defaults, lack of loop scoping, and "global"/"nonlocal" scoping. Oh, and add the crazy "ascii" I/O encoding default on 2.7 (and the inability to cleanly reset it) to that list.
- cma 11y agoBool acting as an integer instead of requiring a conversion (True//(True + True) == False*25).
- ubernostrum 11y agoPython originally didn't have a boolean type, and people used integer 0 and 1 as stand-ins for boolean values. When the boolean type was added, first (before the type existed) "True" and "False" were added as built-in aliases for integer 1 and 0, and then bool was implemented as a subclass of int, locked down so only two instances (True and False, with integer values 1 and 0) could exist. So while it looks weird there is a rational backwards-compatibility reason for it.
- yokohummer7 11y agoIf that were the sole reason, it should have been fixed when introducing Python 3. It seems to be much less controversial than other breaking changes in Python 3. I think the more important reason is that Python doesn't really have an emphasis on the boolean type in general. The `if` statement works for every type, in contrast to other languages allowing only the boolean type in `if`. It is very common to use `if string_or_number_or_anything: blah blah` in Python. While I won't make a stance whether this is a good thing or not, it is still awkward that Python allows implicit conversion between `bool` and `int`.
- crdoconnor 11y ago>So while it looks weird there is a rational backwards-compatibility reason for it. That was a while ago, however. This behavior ought to be phased out in favor of treating it as entirely its own type.
- Veedrac 11y agoThing is, it's not actually there for backwards compatibility. Guido says as much: http://stackoverflow.com/a/6865824/1763356 http://stackoverflow.com/a/6865824/1763356 Alex Martelli's answer on the same page goes into more depth. I get the worries, but this is Python - you should know what domain you're working on anyway, because you have to. It's a different philosophy to statically typed languages, and since having True and False as aliases is convenient I'm personally glad for it: # Truth as samples sum(x > 10 for x in xs) (numpy.random.randint(0, 10, 1000) == 7).mean() # Several properties are made obvious assert True > False my_flag ^= True
- crdoconnor 11y ago>I get the worries, but this is Python - you should know what domain you're working on anyway, because you have to. It's a different philosophy to statically typed languages The only real benefit to this appears to be some shortcuts that shave a few characters off and do so, IMO, at the expense of readability. E.g. this reads easier for me: len([x for x in xs if x > 10]) Than this does: sum(x > 10 for x in xs) Martelli may call this stuff contortions but to me it feels more natural.
- Veedrac 11y ago> E.g. this reads easier for me: len([x for x in xs if x > 10]) At the expense of allocating a new list, maybe. But the `sum` convention is faster, space-efficient and cleaner. It's also pretty obvious once you've seen it.
- crdoconnor 11y agoOk with generators then... I'm sure some people will look at this and think that it's summing all of the numbers over 10 in xs: sum(x > 10 for x in xs) Whereas with this, which is explicit, it's substantially less likely they'll think that: sum(1 for x in xs if x > 10) Again, I don't see the point of maintaining the weakened type system for a few minor 'clever' barely-short cuts like this. Look at what that kind of thinking did to perl. If python started throwing exceptions on all the code that treated True and False as integers, I'm pretty sure all the fixes done to accommodate that would probably make the code cleaner and easier to understand.
- bpicolo 11y agoYeah, things liked the nitpick on (1,2,3) == sorted((1,2,3)) seem not really `wats`. sorted returns a list, surprise surprise. The only actual surprise on this list is, I agree, that += operator. Especially because tup[0] = tup[0] + [1] fails as expected.
- aldanor 11y agosorted is always a list, whereas reversed is an iterator (at least in Python 3)
- masklinn 11y agoSame in Python 2. A collection can even override "reversed" to provide their own reversed collection/iterator[0], not so for `sorted`. [0] https://docs.python.org/3/reference/datamodel.html?highlight=data%20model#object.__reversed__ https://docs.python.org/3/reference/datamodel.html?highlight...
- tsujamin 11y agothe fact that calling sorted(b) where b is a reversed object affects the following comparison "sorted(b) == sorted(b)" is wierding me out.. Ref: http://pastebin.com/VQs9GSzp http://pastebin.com/VQs9GSzp
- ngoldbaum 11y agoThe += on tuple elements thing kind of looks like a bug in CPython.
- bpicolo 11y agoAgreed. tup[0] = tup[0] + [1] fails as expected.
- maxerickson 11y agoThe semantics of augmented assignment are (slightly) different than normal assignment: https://docs.python.org/3.4/reference/simple_stmts.html#grammar-token-augmented_assignment_stmt https://docs.python.org/3.4/reference/simple_stmts.html#gram...
- aldanor 11y agoIt's quite obvious where it comes from though, it first calls __iadd__ on the list and that succeeds, and then it calls __setitem__ on the tuple which fails; however, the first operation already mutated the list. This has nothing to do with tuples, I think, as one can constructs many other examples just like this one.
- ngoldbaum 11y agoI stand corrected. My initial comment was just a gut reaction to the weirdness of an operation on builtins raising an error but still having side-effects.
- hueving 11y agoThe indexing with a float was on a dict. If you can index with a string, why not a float? If it's hashable, it's good!
- ubernostrum 11y agoYup. If hash(x) == hash(y), then some_dict[x] and some_dict[y] will return the same value. The int/float is just a convenient red herring on that one, since it's an easy way to get two different-looking keys which hash the same, combined with the double red herring of the initial "can't index" example being a list (which does require integer values for indexing).
- jepler 11y agonope. Here I use the trick that for most ints, hash(i) == i to quickly find two values with the same hash and put them in a dict. >>> k1 = "foo" >>> k2 = hash(k1) >>> hash(k1) == hash(k2) True >>> d = {k1: "v1", k2: "v2"} >>> d[k1] 'v1' >>> d[k2] 'v2' Now that I've been pedantic, I hope I get this right: in Python, it is equality (==) that defines same-ness for dicts and sets. But if x == y, then you have to ensure that hash(x) == hash(y). Python uses this characteristic to make the initial check for dict and set membership integer comparisons on the hash, but when two items have the same hash, Python goes on to distinguish them on the basis of equality.
- rspeer 11y agoOh, while we're talking about things with predictable hashes, here's a Python WTF that I enjoy: class A: def __hash__(self): return -1 print(hash(A())) Prints -2. For the same reason, if you ever need an easy hash collision, use the hashes of -1 and -2.
- rspeer 11y agoI'll explain the reason, BTW: a hash value of -1 isn't valid in CPython because that integer is reserved as a flag value. So any hash method that returns -1 quietly gets its value changed to -2.
- ketralnis 11y ago+= on lists is an annoying one: def myfunc(mylist): mylist += [1,2,3] is NOT the same as: def myfunc(mylist): mylist = mylist + [1,2,3] Try this on each of those definitions: a = [4,5,6] myfunc(a) print a The first one mutates 'a', the second only reassigns (and discards) a temporary `mylist`. Put another way, += is not just syntactic sugar. With lists, it actually mutates the original list.
- pikzen 11y agoWhich is what every language with a C inheritance does. += has always mutated the original value, I don't know why you expected it to be different. To me the real wtf is the second, where python creates a variable with the same name and discards it.
- chei0aiV 11y agoWhich is what every language with a C inheritance does. Arguments are passed by value and put into ordinary variables, I don't know why you expected it to be different.
- ketralnis 11y agoWell no. What would you expect this code to do? void increment(int x) { x += 1; } void printit() { int y = 0; increment(y); printf("%d\n", y); } C doesn't have lists so I had to use an int here, but the Python example does the equivalent of that code printing 1, not 0. (But only for lists. That same Python code with ints does what you expect.)
- albertzeyer 11y agoIt is syntactic sugar to call mylist.__iadd__. The behavior to modify it inplace is common also for other non-pure data structures. I'm not sure but I think that list.__iadd__ is even more tricky: When the new object still fits into the allocated array, it will modify it inplace, otherwise it returns a newly allocated list and doesn't modify it inplace. So you cannot rely on __iadd__ modifying a list (or anything else) inplace.
- crdoconnor 11y agoTruthiness values are also WTF worthy (although not mentioned above). if not x: do_something() Where do_something will occur if x is None, zero, 0.0, a blank string or even midnight in some versions of python. This has bitten me multiple times. It's a violation of python's explicit over implicit. It's an example of weak typing in an otherwise strongly typed language. There's really no good reason for it. IMO, if x isn't a boolean, that line should just throw an exception. If you want to check for x being 0.0 or "", you should do x == "" or x == 0.0.
- josegonzalez 11y agoWhy not do `x == False` then? Arguing about a language not being as strongly typed when you're not being as explicit about your own types seems pretty roundabout.
- masklinn 11y agoYou want `x is False`, because `0 == False`: Python didn't originally have integers so `0` and `1` were used for `False` and `True` respectively. In order not to break existing code using equality comparisons (rather than implicit truthiness) or typechecking, when booleans were introduced `bool` was made a subclass of `int`, and `False` and `True` equal to respectively `0` and `1`. This is one of the warts which wasn't changed in Python 3.
- crdoconnor 11y ago>Why not do `x == False` then Because it's no more explicit than "not x" and because it's 5 characters longer. There's always a trade off to be made between clarity and verbosity. Judging by the number of bugs I've squashed caused by the developer either not realizing or simply forgetting that ("", 0, 0.0, midnight, False, [], {}, None) all resolve to false, I've come to believe this is one of those times where the clarity should probably take precedence. You can go to the other extreme by making everything explicitly statically typed which will make your code much more verbose (e.g. see Java). I believe that trade off isn't worth it either, despite the small increase in type safety.
- aexaey 11y ago3 different kinds of NaNs is pretty rad too. No idea what's happening here: >>> x = 0*1e400 >>> set({x, x, float(x), float(x), 0*1e400, 0*1e400}) set([nan, nan, nan]) >>> set({x, float(x), 0*1e400, 0*1e400}) set([nan, nan, nan]) >>> set({x, float(x), 0*1e400}) set([nan, nan]) EDIT: it gets worse: >>> x = 0*1e400 >>> y = 0*1e400 >>> z = 0*1e400 >>> set({x, x, x}) set([nan]) >>> set({x, y, z}) set([nan, nan, nan])