14 ms·
WTFPython: Exploring and understanding Python through surprising snippets
- beckingz 4y agoThis is a fun and well written set of examples, which shows and explains the cause of the counter intuitive behavior.
- behnamoh 4y agoIn Python, I've always run into the problem of expression-vs-statement. For example, in my mind I want to do this: (a++ if a==b else b++) + 1 Or something like this (borrowed from https://stackoverflow.com/questions/65024477/walrus-operator-in-list-comprehensions-python https://stackoverflow.com/questions/65024477/walrus-operator...): from datetime import datetime timestamps = ['30:02:17:36', '26:07:44:25', '25:19:30:38','25:07:40:47'] timestamps_dt = [ datetime(days=day,hours=hour,minutes=mins,seconds=sec) for i in timestamps day,hour,mins,sec := i.split(':')] But this type of stuff is always greeted with Python's inflexible syntax. Recently, I learnt about Rackets's idea that "everything is expression" on (https://beautifulracket.com/appendix/why-racket-why-lisp.html#:~:text=Beautiful%20Racket%20and%20Practical%20Typography,is%20a%20descendant%20of%20Lisp https://beautifulracket.com/appendix/why-racket-why-lisp.htm....) and was blown away to realize these are valid Racket codes: ((if (< 1 0) + *) 42 100) or (+ 42 (if (< 1 0) 100 200)) The second one has an equivalent in Python: 42 + (100 if 1 < 0 else 200) But the first one does not: 42 (+ if 1 < 0 else *) 100 > SYNTAX ERROR. Maybe it's time for me to get my hands dirty with Lisp.
- divbzero 4y agofrom operator import add, mul (add if 1 < 0 else mul)(42, 100)
- masklinn 4y ago> But the first one does not: > > 42 (+ if 1 < 0 else *) 100 Of course it does: from operator import add, mul (add if 1 < 0 else mul)(42, 100)
- klysm 4y agoOkay but not really, I’m lispy languages everything is an expression. And it’s monoiconic
- deleted 4y ago[deleted]
- saila 4y agoThe first example can be written like this in Python >= 3.8: a = 1 b = 2 ((a := a + 1) if a == b else (b := b + 1)) + 1 although that's a lot to cram into a single line IMO. The closest I could come up with for the second example is this (noting that the datetime constructor requires both the year and month): from datetime import datetime timestamps = [ "2022:04:30:02:17:36", "2022:04:26:07:44:25", "2022:04:25:19:30:38", "2022:04:25:07:40:47", ] datetimes = [ datetime(*map(int, timestamp.split(":"))) for timestamp in timestamps ] That said, it'd probably be better to use datetime.fromisoformat() or datetime.strptime() rather than manual parsing. And you could maybe use the operator module[1] for the last example, like this: import operator as op (op.add if 1 < 0 else op.mul)(42, 100) [1] https://docs.python.org/3/library/operator.html https://docs.python.org/3/library/operator.html
- chrismorgan 4y agoThere’s a rather important semantic difference in what you wrote: a++ produces the value before incrementing, whereas (a := a + 1) is like ++a and produces the value after incrementing. Presuming this is indeed what behnamoh meant by a++, that `(a++ if a==b else b++) + 1` would be better written `++a if a==b else ++b`, which would then become the clearer `(a := a + 1) if a == b else (b := b + 1)`.
- mypalmike 4y ago> For example, in my mind I want to do this: > > (a++ if a==b else b++) + 1 Why? This is absurdly unreadable. Why not create a function, give it a name, and use simple if/else constructs. Help the next developer out so they don't need to figure out your tricky expression.
- pmontra 4y ago> (a++ if a==b else b++) + 1 Please rewrite this as if a == b: a += 1 else: b += 1 and don't leave the other developers (or yourself two months from now) scratching their head about what that code does. You have more important things to do than trying to understand your code again and again. Furthermore, does that + 1 at the end run before or after the ++? > (a++ if a==b else b++) + 1 I had to try it out (with a syntax that can run) >>> a = 1 >>> b = 2 >>> (a+1 if a == b else b+1) + 1 4 # the expression evaluates to 4, wtf! >>> a 1 >>> b 2 But this is not a += 1 which is syntax error and the point of your post.
- systemvoltage 4y agoWalrus operator IMO is a huge mistake. It saves fuck all time/space, but introduces an entire new operator that probably added more complexity, edge cases and quirks than the benefits it provides. Terrible idea. Worse, no one uses it. I've yet to come across anyone that advocates it or remembers it.
- asicsp 4y agoI use it for simple cases: >>> if m := re.search(r'(.*)s', 'oh!'): ... print(m[1]) ... >>> if m := re.search(r'(.*)s', 'awesome'): ... print(m[1]) ... awe
- systemvoltage 4y agoThe question one has to ask is whether it is worth the additional complexity and a dedicated operator. I am sure it is ever so slightly useful, but I am not convinced it is worth the trouble. Feature creep is programming language's worse enemy after a certain maturity level. I absolutely love Go in this matter. They took forever to add Generics and generally sides with stability over features.
- plumeria 4y agoThe let expression syntax in OCaml is a nice alternative: let some_func () = 5 let a = some_func () in if a < 5 then "<5" else ">=5"
- jreese 4y agoI actually just used it to what I would say was a perfect example of where it legitimately improved code readability while maintaining pythonic constructs: values = [ value for line in buffer.readlines() if (value := line.strip()) ] Previously, I would have needed to either duplicate effort like: values = [ line.strip() for line in buffer.readlines() if line.strip() ] Or used a sub-generator: values = [ value for value in ( line.strip() for line buffer.readlines() ) if value ] Or rewritten it altogether using a (slower) for loop calling append each time: values = [] for line in buffer.readlines(): line = line.strip() if line: values.append(line) The assignment expression is perfect for this sort of use case, and is a clear win over the alternatives IMO. Edit: fixed initial example
- KelvinAnderson 4y ago
- cmsefton 4y agoPrevious discussions: https://news.ycombinator.com/item?id=21862073 https://news.ycombinator.com/item?id=21862073 (2019) https://news.ycombinator.com/item?id=26097732 https://news.ycombinator.com/item?id=26097732 (2021)
- satwik 4y agoHi, maintainer of the repo here, thanks for sharing :) If anyone's willing to go through those examples with an interpreter on the side, you can check out https://www.wtfpython.xyz/ https://www.wtfpython.xyz/ It's built with pyiodide, the only limitation is you may not get correct results for the examples that are version-dependent. The UX might not be great, especially on mobile (happy to hear ideas on how to improve it), but it does the job for now :)
- albert_e 4y agoLooks great but one quibble -- the "dark mode" simply flips the black and white of normal webpage and code blocks ... which is 50-50 can we use a different shade of grey for code blocks in dark mode?
- satwik 4y agoRight, I agree with this. Reducing some contrast by using shades of grey will look more pleasant. I'll get this adjusted, thanks for the tip!
- Beltiras 4y agoThank you for this. Even as a senior Python developer I didn't know many of these.
- satwik 4y agoGlad you found it informative. I've found senior developers to have polarized opinions about the usefulness of the collection, so always good to see a review in favorable direction :)
- bjarneh 4y agoThat Python uses the exact same string if they contain the same immutable content can probably be a bit confusing. But if you want truly confusing behavior look at how Java treats numbers wrapped in their object type: public class Main{ public static void main(String[] args){ Integer a = 300; Integer b = 300; System.out.printf(" %s == %s: %s\n", a, b, (a == b)); a = 100; b = 100; System.out.printf(" %s == %s: %s\n", a, b, (a == b)); } } // output 300 == 300: false 100 == 100: true When combined with Java's auto-boxing, this can create some serious confusion
- tekknolagi 4y agoCPython does this too (`is` is roughly the same as Java's `==`): >>> a = 300 >>> b = 300 >>> a is b False >>> a = 100 >>> b = 100 >>> a is b True >>> CPython has a small set of canonical objects for integers.
- bjarneh 4y agoLuckily you don't typically compare numbers in Python with 'is'. In Java you should not compare Integer objects with '==' either of course, but '==' is typically used with numbers, and the auto-boxing features of Java makes it very easy to shoot your self in the foot with this stuff. I once ran into a bug which had been there for years, since some Integer values had never crept over that magic limit, and auto-boxing allowed the numbers to be treated as int's all over, so it really took me some time to figure out what was going on...
- deleted 4y ago[deleted]
- jreese 4y agoThis is only true for string literals. Strings constructed at runtime may (and often will) end up as separate objects: >>> a = "this is a long sentence in a string literal" >>> b = "this is a long" + " sentence in a " + "string literal" >>> a is b False >>> id(a) 4386376272 >>> id(b) 4387721264 >>> a == b True
- ezoe 4y agoI have to write Python for the job right now and this language has pitfalls everywhere. Glad I avoided this language so far. I don't want to use Python for a software more than a few dozen lines or involving multiple developers. This language was designed to unintentionally shoot your foot so many times.
- praptak 4y agoMy experience with Python is that a mid sized code base does not survive a full staff turnaround. So somewhere around the time the last original author is gone, the codebase becomes a haunted graveyard - scary to everyone. Also, unit tests are not a substitute for static typing - they obviously help a lot but don't prevent the degradation. This is based on 3 different companies, of which all had decent developers who did follow good practices.
- ngc248 4y ago>>> My experience with Python is that a mid sized code base does not survive a full staff turnaround. So somewhere around the time the last original author is gone, the codebase becomes a haunted graveyard - scary to everyone. This is 100% true for medium to large codebases. Also because of the dynamic typing just looking at the codebase, it is very hard to understand the "shape" of variables and data. Of course this is improving now because of the type hints, but still it is comparatively hard.
- raverbashing 4y ago> it is very hard to understand the "shape" of variables and data And is it in, let's say Java, with several inheritance levels and very opaque types? It's one thing I struggle with Sure you know a TypeA has method do_stuff() and returns a TypeB but what actually is happening, beats me. Then you chase down TypeA and find out the actual implementation is spread across TypeA0, TypeBaseA and you can't make sense of anything
- praptak 4y ago
- NmAmDa 4y agoI learned a couple of things there, seems like a very organized way of learning.Just wish more of this approach is implemented by others. Also the discussions in comments makes me wonder if HN should implement syntax highlighting feature.
- praptak 4y agoI have a mildly surprising snippet which I believe they don't have. It's about interaction of class and instance attributes with behavior of "X += Y". Python expands it to "X = X + Y" but only if X implements "+" but not "+=" class A: a = (1,) # tuples are immutable and don't have "+=" b = [1,] # lists have "+=" obj = A() obj.a += (2,) # this creates obj.a obj.b += (2,) # this modifies A.b print(A.a, A.b) The snippet prints "(1,) [1,2]".
- iknownothow 4y agoOh, that's a wtf for sure. More to be scared of and more paranoid I'm going to appear to junior devs while reviewing merge requests. If I'm not mistaken there's a chapter in Effective Python about this, where they recommend never using lists or dicts as Class attributes or default values for parameters and instead use factories or use None and then initialize them with default values when a new instance is created.
- roflyear 4y agoYeah it is a little tricky, but using them as class attributes can actually be handy in some circumstances. Note to readers unfamiliar with Python: class attributes is a special thing, different than instance attributes. Definitely one of the trickier things with Python for sure.
- praptak 4y agoThanks for bringing this up, I didn't make the connection to default args. I think this behavior is actually "default args" but reversed. In case of default args it is the mutability that causes the nasty surprise (ooops, all calls share the same object, which is mutable). In case of the "+=" it is the _immutability_ that causes the surprise - I would not expect "obj.a += ..." to create an instance attribute that shadows the class one. I think the practical conclusion here is "don't call += on objects that don't support it - because it may work and this is not what you want!)".
- iknownothow 4y ago
- makach 4y agoI picked up Python again a couple of months ago, and I am reexperiencing my love/hate relationship with the language. Reading through the documentation in WTFPython I get some kind of affirmation of my bias against python. I hate it, but I also love it, but I hate it. It is just so "je ne sais quoi"-- The docs would benefit from some explanations on whys.
- Beltiras 4y agoHow I stopped worrying and learned to love Python.
- thfh 4y ago
- psychoslave 4y agoInteresting. I would be interested with a WTFRuby. As I lately explored Ruby deeper, I was surprised that Complex would remove #% as well as most Comparable operators. https://stackoverflow.com/questions/72314247/how-can-ruby-complex-class-have-comparable-in-its-ancestors-when-it-has-none https://stackoverflow.com/questions/72314247/how-can-ruby-co... https://stackoverflow.com/questions/71439853/why-ruby-doesn-t-provide-a-complexemodulo-method https://stackoverflow.com/questions/71439853/why-ruby-doesn-...
- cuteboy19 4y agoThe wtfs about id and is are not deserved. These operations are not used in normal code and are explicitly implementation defined. I suppose they should have named it __is and __id. I don't see why anyone not hacking the interpreter would care about these though
- Liquid_Fire 4y agoI agree about id() but `is` is absolutely used (and needed) in normal code. But generally not on strings or integers, which are the types that have weird results with it. Simple example: any sort of graph algorithm that needs to know if two nodes are the same node.
- cuteboy19 4y agoYou should really use a nodeid in that case and compare nodeids. So that you can serialize your graph
- ghostkba 4y ago
- ghostkba 4y ago
- Sohcahtoa82 4y agoI've seen these before and every single time, it's mostly based on a misunderstanding of certain language features that only a beginner coder would do (for example, not knowing that Python does not do block-level variable scoping, so loop variables leak) or an abuse of an implementation detail (for example, integers under 255 being singletons). "is" is not the same as "==". Confusing the two is a rookie mistake, yet is treated like a "gotcha".
- eslaught 4y ago> not knowing that Python does not do block-level variable scoping, so loop variables leak For what it's worth, it took me a long time to internalize this (more than ten years after first learning Python), I think partly because the way most people teach Python is to say what scopes Python has instead of saying what scopes it doesn't have. While it's bad style to abuse this, I would definitely consider this a part of Python that a lot of working developers probably don't understand well.
- d0mine 4y agoYes. Scopes are complicated e.g., class Foo: a = [1,2,3] b = [4,5,6] c = [x * y for x in a for y in b] that leads to NameError: name 'b' is not defined https://bugs.python.org/issue3692 https://bugs.python.org/issue3692
- deleted 4y ago[deleted]
- moffkalast 4y ago> The Walrus operator (:=) was introduced in Python 3.8, it can be useful in situations where you'd want to assign values to variables within an expression. Ok that is infuriating. They dragged their feet through the dirt on how the switch statement is useless and pointless, but then they add some random bullshit like that that nobody ever asked for.
- digisign 4y agoRelated, they also added a match statement with footguns. Python has "jumped the shark" in its recent envy of other languages. :-/ The existing "as" would have worked instead of walrus. I've never seen anyone use the additional flexibility that walrus affords because the code becomes too complicated and better at that point to just use two lines.
- raymondh 4y agoIMHO this is one of the worst ways to improve your Python skills. Instead, focus on what the normal, robust, beautiful, clear, and idiomatic solutions to problems. There is an enormous amount of quality training material available as well as excellent source code to major projects. Most people would be better off just spending a hour or so with the tutorial at docs.python.org. I get people coming to me asking for a job teaching Python when they don't know of some the material in the tutorial. Most have never read the FAQs and aren't aware of common solutions to common problems. To me, if you want to build your skills, start there. Get in the habit of reading docs, even boring ones, and occasionally read some of the source code from your favorite libraries. That said, if you've already a very strong Python programmer, one possible use for these snippets is to help you root out any last, misconceptions of minor details. But, you should resist the urge to show-off or use almost any of these "skills". There are a lot of cute things we can do with chained comparisons, but the only sensible things are: lo <= x <= hi or f(x) == g(x) == h(x). Anything else is too weird for communicating with the human beings. Likewise, you should only use "is" for None tests until you have strong understanding of identity guarantees.
- deleted 4y ago[deleted]
- chaostheory 4y agoI feel that it’s a decent approach for anyone already familiar with Python, since it’s making you think how the platform works underneath the surface vs just understanding it from the shallow language syntax level.
- lmc 4y agoIn the normal course of events, absolutely. This is hacker news though :-)
- raymondh 4y agoFair enough :-)
- gnramires 4y agoI've been thinking it would be useful to create a sub-language of python with only essential features (removing hard to explain features such as 'is', ':=', and a plethora of non-essentials) -- that is, fully backward-compatible with python. This would also make restricted interpreters easier to write as well. I know micropython exists, but the goal is more minimal size than simplicity and ease to learn.
- Aachen 4y agoThere are some genuinely surprising ones, but is it just me who seems to think these are for 80% either well-known beginner's mistakes (== vs. is, underscored names) or just begging for problems with operator precedence (by omitting parentheses) and variable scoping (by shadowing variables)? Random example I'm currently reading is: x, y = (0, 1) if True else None, None. The interpreter considers the last None outside the if statement. Gee, so you thought to need parentheses at first but not for the second one, and now it's a pitfall that parentheses are missing? The readme goes so far as so say "I haven't met even a single experience Pythonist till date who has not come across one or more of the following scenarios". Uh huh. Not me. Add parentheses when in doubt, and when not in doubt, add parentheses for readability. In any language. Not excessively, like everyone knows what "if x == 2:" does without them in python, but for anything less obvious or more complicated than a=2*2+3, just do it.