[HN Gopher] Understanding Python through its builtins ___________________________________________________________________ Understanding Python through its builtins Author : tusharsadhwani Score : 327 points Date : 2021-10-10 15:13 UTC (7 hours ago) (HTM) web link (sadh.life) (TXT) w3m dump (sadh.life) | d_burfoot wrote: | Great article. This is what I come to HN to find. | DangitBobby wrote: | Very well written. Fun little tidbit, Django abuses the fact that | bools are ints in it's partition util: | | https://github.com/django/django/blob/01bf679e59850bb7b3e639... | int_19h wrote: | It does make for some amusing code golf techniques, e.g.: | print([ f"{(not x % 3) * 'Fizz'}{(not x % 5) * | 'Buzz'}" or x for x in range(1, 20) ]) | hultner wrote: | Where? I don't see anything in that code relying on that unless | I'm Sunday blind. | iezepov wrote: | `results[predicate(item)]` here they get the first and the | second elements of a tuple. Essentially it's `results[False]` | and `results[True]` | hultner wrote: | Oh, that's obvious now. Must be that Sunday night | blindness. | NegativeLatency wrote: | Results has 2 elements indexed by 0 and 1 (it's a tuple not a | dict) | matsemann wrote: | > List comprehensions are basically a more Pythonic, more | readable way to write these exact same things | | More pythonic maybe, but you can't have more than a single | expression in a list comprehension without it becoming completely | unintelligible. I also often miss other standard list features. | Reduce, flatmap, indexed versions, utils like first of predicate, | split, filternonnull etc | weatherlight wrote: | Anything remotely interesting like that is dumped in itertools. | | Python's creator, Guido van Rossum, doesn't like | functional/functional-ish programming a lot. That's well-known. | | Guido: "I value readability and usefulness for real code. There | are some places where map() and filter() make sense, and for | other places Python has list comprehensions. I ended up hating | reduce() because it was almost exclusively used (a) to | implement sum(), or (b) to write unreadable code. So we added | built-in sum() at the same time we demoted reduce() from a | built-in to something in functools (which is a dumping ground | for stuff I don't really care about :-)." | matsemann wrote: | Yes, lots of them are available. But I also would like to be | able to call .sum() on my iterable at the end of a chain, | instead of having to mentally unwrap | sum(map(filter(filter(map(...)))) | aasasd wrote: | I bet a Clojure-style 'threading' arrow function can be | easily done in Python. arrow(my_list [map | args] [filter args] [filter args] [map args] [sum]) | | For cases when function argument positions differ, you'll | need some special var in your module to signal where to | inject the list. arrow(my_list [map | map_func arrow.list_here]) | | Won't be surprised if something like this already exists, | but I can't think of keywords to search for that aren't too | generic. | dragonwriter wrote: | > But I also would like to be able to call .sum() on my | iterable at the end of a chain | | Yeah, one thing I like about Ruby over Python is the fluent | code th former allows of that style. | | People talk about Guido not liking functional style, but | that explains comprehensions over map/filter, but not | function-sum() over method .sum(). | weatherlight wrote: | I strongly dislike python, I often wonder if it would have | been as popular if Guido didn't work at Google early on. | tored wrote: | Why do you strongly dislike Python? Language design, | standard library, community, leadership? | int_19h wrote: | Guido joined Google in 2005, and it was already plenty | popular by then. | glaucon wrote: | FWIW Python was fourteen years old when GVR joined the | Borg. That doesn't address how popular it was but I think | it's reasonable to say it was well established. | int_19h wrote: | But you only ever need to unwrap one sum(). And with | sequence comprehensions, what you get instead is sum(... | for ... if ... for ... if ...) - I don't think that's | improved by rewriting it as (...).sum(). | glaucon wrote: | > "I value readability and usefulness for real code" | | Amen. | | There are plenty of languages where your code ends up looking | like an entry in an obfuscation competition without even | trying. If you're using Python, and working for me, I expect | the code to be readable by anyone. | | And, no, I don't give a toss whether the code is three times | the length it might have been if it was dangerously, and | expensively, obscure. | weatherlight wrote: | if you come from a imperative background and everyone is | used to working in imperative languages... I guess thats | _anyone_. | | Someone coming from, say ruby, or javascript would find | list comprehension jarring. You can't compose them and you | basically have to rewrite them when its time to extend | them. | | readable by anyone is pretty subjective. | franga2000 wrote: | Quite often, chains of map/filter/reduce/whatever are more | readable because you can see the flow of data, like you | were looking at a factory production line. List | comprehensions and traditional prefix functions (e.g. | map(iterable, function)) completely break the visual chain | that makes basic functional code so readable. | | Like, which of these make more sense? | | strList.filter(isNumeric).map(parseInt).filter(x => x != 0) | | [ x for x in [ parseInt(s) for s in strList if isNumeric(s) | ] if x != 0] | | filter(map(filter(strList, isNumeric), parseInt), lambda x: | x != 0) | | And it's not like Python doesn't have the language features | to implement the first pattern. Map,reduce,filter,etc. | could simply be added to the iterable base class and be | automatically usable for all lists, generators and more. | pansa2 wrote: | I believe list comprehensions would be much more readable | if they were written the other way round: | [for x in [for s in strList: if isNumeric(s): | parseInt(s)]: if x != 0: x] | | Nesting them is still ugly, but can often be avoided | using an assignment expression: [for s | in strList: if isNumeric(s): if (x := parseInt(s)) != 0: | x] | pansa2 wrote: | > _Map,reduce,filter,etc. could simply be added to the | iterable base class_ | | Surprisingly, Python doesn't have an iterable base class! | `list.__bases__` is just `object`. | kgm wrote: | This is a neat article, but it does have some errors. | | One subtle point that the post gets wrong: | | > So where does that come from? The answer is that Python stores | everything inside dictionaries associated with each local scope. | Which means that every piece of code has its own defined "local | scope" which is accessed using locals() inside that code, that | contains the values corresponding to each variable name. | | The dictionary returned by `locals()` is not literally a | function's local namespace, it's a _copy_ of that namespace. The | actual local namespace is an array that is part of the frame | object; in this way, references to local variables may happen | much more quickly than would be the case if it had to look each | variable up in a dictionary every time. | | One consequence of this is that you can't mutate the dict | returned by `locals()` in order to change the value of a | function-local variable. | | Another, less-subtle error in the post is this: | | > int is another widely-used, fundamental primitive data type. | It's also the lowest common denominator of 2 other data types: , | float and complex. complex is a supertype of float, which, in | turn, is a supertype of int. | | > What this means is that all ints are valid as a float as well | as a complex, but not the other way around. Similarly, all floats | are also valid as a complex. | | Oh, no no no. Python integers are arbitrary-precision integers. | Floats are IEEE 754 double-precision binary floating-point | values, and as such only support full integer precision up to | 2^53. The int type can represent values beyond that range which | the float type cannot. | | And while it is true that the complex type is just two floats | stuck together, I would very much not call it a _supertype_. It | performs distinct operations. | | > Accessing an attribute with obj.x calls the __getattr__ method | underneath. Similarly setting a new attribute and deleting an | attribute calls __setattr__ and __detattr__ respectively. | | Attribute lookup in Python is _way_ more complex than this. It 's | an enormous tar pit, too much so to detail in this comment, but | __getattr__ is most often not involved, and the `object` type | doesn't even _have_ a __getattr__ method. | wizzwizz4 wrote: | The "complex > real > int" thing is true in mathematics. In | Python, `bool` inherits from `int`. | kgm wrote: | Yeah, but we're not talking about pure mathematics. We're | talking about floats, and I find that it's very important to | be clear about the limitations. It's easy to get some nasty | bugs if you start assuming that you can cram just any int | into a float. | | And I have no objections to the article's description of the | bool type. | tyingq wrote: | In a somewhat similar way, this post about hacking the import | system to load modules from strings finally helped me understand | how imports work: | | https://cprohm.de/blog/python-packages-in-a-single-file/ | collsni wrote: | You know what helped me with python? Breakpoints inside vscode's | module. It all just kinda clicked. | yuy910616 wrote: | python actually has a build-in `breakpoint()` function. I think | it brings up a repl at the line. | | I've been using that instead of print debug, it's been great. | int_19h wrote: | It's actually customizable via the PYTHONBREAKPOINT | environment variable and sys.breakpointhook(). The default | does pdb.set_trace(), which gives you a built-in debugger | prompt (not a REPL) at that location. But it can be set to | execute arbitrary code, and most Python IDEs make it behave | like "normal" breakpoints. | Pearse wrote: | This is such a good write up for someone like myself still trying | to get their head around Python. | | I can appreciate this must have taken considerable effort, It | reads really well. Thank you! | tusharsadhwani wrote: | I was concerned if my writing style will click with people, | this is good to know :) | submeta wrote: | Nicely written article! - Slightly off topic: I love seeing and | reading Python code. Used to see many flaws in the language | (things like `.append` changing the object, returning `None` | instead of creating a copy and returning that), but after ten | years of working with Python I really appreciate its versatility, | it's ubiquitous availability, the large number of libraries and | the community. There's nothing I can't solve with it. It's the | swiss army knife in my pocket, available whenever I need to solve | something by coding. | brundolf wrote: | > Used to see many flaws in the language (things like `.append` | changing the object, returning `None` instead of creating a | copy and returning that) | | I think it's pretty off-base to call this a "flaw". Immutable | structures have their place and can be very helpful where | appropriate, but making its core primitives work this way is | far outside the scope or the philosophy of Python. If you want | otherwise, you're really wanting an entirely different | language. And there's nothing wrong with that! But I think it | would be a "flaw" for Python to make these operations | immutable, even though I love immutability personally. | radarsat1 wrote: | > (things like `.append` changing the object, returning `None` | instead of creating a copy and returning that) | | This would be horrendously inefficient without immutable data | structures like Clojure's. Very few languages have that, so | it's a strange assumption to make, especially for a language as | old as Python. | | Although it is a very nice feature of Clojure. | l33tc0der wrote: | One of the many reasons why I love Clojure so much! | | Rich implement his own brand of persistent data structures | which makes Clojure's immutability a lot more efficient. | radarsat1 wrote: | Thanks yeah, _persistent_ is the term I was looking for, | did not come to mind as I haven 't used Clojure in a few | years. | Blikkentrekker wrote: | It should also be worth nothing that _Clojure_ sacrificed | quite a bit to make this as efficient as possible. | | "persistent vectors" are certainly an interesting data | structure that strike a compromise between fast indexing and | being able to relatively quickly create a copy where only one | element changes, but it's a compromise and indexing is made | slower to allow for the latter. -- They also take up more | memory on their own but are allowed to share memory with | their copies. | | I will say that my ideal language contains them in the | standard library alongside standard vectors that index in | constant time. | | Further, it should be noted that much of the performance talk | is on the assumption that accessing from memory is truly | random access; -- with the existence of c.p.u. caches that | assumption is not entirely accurate and accessing from | contiguous rather than scattered memory in practice is | considerably cheaper so one also pays the price for their | being scattered more in memory. | adrusi wrote: | Random access into a clojure vector is going to need more | memory lookups than conventional sequential buffer array (I | don't recall the constants used in the implementation, I | think it's either 4 or 8 lookups). | | But when you're indexing into the vector sequentially, the | memory layout plays rather well with memory caching | behavior, and most lookups are going to be in L1 cache, | just like they would be in a conventional array. | | So lookups are a bit more expensive, but not as much more | expensive as one might imagine. | Blikkentrekker wrote: | How? I don't see how that's possible. | | The actual data of a Pvector is not in contiguous memory | but scattered however the _JVM_ wills it, and on top of | that in order to find which address to retrieve it, an | algorithm that runs in logarithmic time with respect to | the length of the vector must be used opposed to a | constant time one. | | How can most lookups end up in L1 cache if an element | that is 32 indices removed is statistically likely to be | arbitrarily far removed in memory? | | Of course, all of that is not that material to begin with | given that most elements will be pointers to begin with | so the actual objects wither they point will already be | arbitrarily scattered and it simply adds one more pointer | indirection, but for unboxed types such as integers it | does play a factor. | tusharsadhwani wrote: | Thank you! | | Things like `list.append` modifying in-place might feel like a | flaw to some, but I think Python is really consistent when it | comes to its behaviour. If you ask a person who comes from an | object-oriented world, they'll say it only makes sense for a | method on an object to modify that object's data directly. | | There's always ways to do things the other way, for example you | can use x = [*x, item] to append and create a new copy, while | being quite a bit more explicit that a new list is being | created. | int_19h wrote: | One related area where Python is not consistent is operators | like +=. | | In pretty much all other languages that have them, the | expected behavior of A+=B is exactly the same as A=A+B, | except that A is only evaluated once. Now lets look at lists | in Python: xs = [1, 2] ys = xs | ys = ys + [3] print(xs, ys) | | This prints [1, 2] [1, 2, 3], because the third line created | a new list, and made ys reference that. On the other hand, | this: xs = [1, 2] ys = xs ys | += [3] print(xs, ys) | | prints [1, 2, 3] [1, 2, 3], because += changes _the list | itself_ , and both xs and ys refer to that same list. | | (Note that this is not the same as C++, because in the | latter, the variables store values directly, while in Python, | all variables are references to values.) | | The worst part of it is that Python isn't even _self_ | -consistent here. If you only define __add__ in your custom | class, you can use both + and += with its instances, with the | latter behaving normally. But if you define __iadd__, as list | does, then you can do whatever you want - and the idiomatic | behavior is to modify the instance! | | For comparison, C# lets you overload + but not +=, and | automatically synthesizes the latter from the former to | enforce the correct behavior. | pokepim wrote: | Huh I never even thought we would need to create copy of an | object when adding new item to it (like a new item to list | for example). Is there any drawback on doing that in standard | pythonic way? I actually learned to program using Python and | it was my first language. Since then I only used JS. In both | I like using functions a lot and rarely dabble in OOP since | it is more conveniet to me. | tyingq wrote: | You can control the behavior manually, like: | first = [0,1,2] second = [*a,3] # first is unchanged, | second = [0,1,2,3] | | Or second=itertools.chain(first, [3]), which avoids the | copy. | | Though, to me, it's asking for trouble later. | [deleted] | sgeisenh wrote: | You often lose performance in traditional imperative | languages when aiming for persistence. | | When you have immutability guarantees (like in many | functional programming languages like ML or Haskell) you | can avoid making copies by sharing the parts of the data | structure that don't change. | | If this kind of thing interests you, you should check out | Chris Okasaki's book "Purely Functional Data Structures". | eximius wrote: | "avoid making copies" dors not always equal | "performance". Depending on your access patterns, having | the data colocated can be more important. | | But immutability sure is nice when you can have it. | tusharsadhwani wrote: | whether mutating data is better than creating a new copy | for everything is a really long debate about immutability | and functional programming, with good points on either | sides, but that's really beyond the point here. | | In my opinion, you should use whichever method makes your | code easy to read and understand for your usecase. | tyingq wrote: | >Python is really consistent when it comes to its behaviour | | True, though you end up with things like: ' | '.join(thelist) | | Instead of thelist.join(' ') | | Because of the somewhat aggressive mantra to be consistent. | pdonis wrote: | In the cases you give, the original list is not being | mutated; a new object (a string, not a list) is being | created. So it does make sense not to have it be a method | call on the list. | Marazan wrote: | But that's good. Because a string just needs to know aboit | interable to perform that operation whereas every iterable | would need to implement it's own join if you had it the | other way around. | goatlover wrote: | Yet Ruby and JS manage to do it somehow. To me it seems | natural that join should be a method on the iterable, and | I always have to pause to remember Python is different. | aidos wrote: | How does that work? Don't you have to effectively convert | your general iterable to an array and then join on that? | Array.from(iterable).join(...)? | globular-toast wrote: | I don't think it should be a method at all. It's just a | function: join(iterable, separator). It can also be | implemented with reduce naturally: `reduce(lambda x, y: x | + separator + y, iterable)`. | Redoubts wrote: | Reduce sounds like a really slow way to do string | building | globular-toast wrote: | Oh yeah, it's horrendous, my point was just that it's | functionally equivalent and makes more sense as a | function than a method on either object. You can actually | call it like this if you want, though: | `str.join(separator, iterable)`. | [deleted] | ptx wrote: | The way it's managed in JS, digging the function out of | the prototype to apply it, can be done in Python as well. | But unlike JS you won't normally have to, thanks to the | method not being defined only on one specific type of | iterable. | | JS: Array.prototype.join.call(["one", | "two", "three"], "|") | | Python: str.join("|", ["one", "two", | "three"]) | [deleted] | robluxus wrote: | It's more about flexibility than consistency. | | str.join() and bytes.join() can support all iterable type | arguments. | | Better than trying to implement a join (or two) on all | iterables. | Alex3917 wrote: | > things like `.append` changing the object, returning `None` | instead of creating a copy and returning that | | The obvious question is why it can't return a reference to the | list instead of returning None. I feel like if I've been using | the language on an almost daily basis for ten years now and I | still get burned by that all the time, then it's just a poorly | designed feature. | canjobear wrote: | The advantage of mutating operations always returning None is | that you can easily tell whether a mutation is happening by | looking at the code. If you see y = f(x) that means x is | unchanged, whereas if you see just f(x) on a line that means | something stateful is happening. | brundolf wrote: | Agreed. JavaScript's Array.sort is an example of this. Most | of JavaScript's other array methods return a new array and | people get used to chaining them, but sort mutates the | array and also returns a reference to it. You can actually | get pretty far before being bitten by this so long as | you're sorting already-copied arrays. But then one day you | hit a bizarre bug caused by behavior that's been sneaking | past your radar the whole time. | macintux wrote: | There are counter-examples: functions that return values | while also having side effects, cases where the developer | simply made a mistake. | | Coming from a language with baked-in immutability, Python's | behavior in this regard was very difficult to get used to. | Xavdidtheshadow wrote: | I really like ruby's method naming convention for this: | | * `sort(arr)` returns a sorted copy of the input * | `sort!(arr)` returns the sorted original | | methods that return booleans end in `?`, like `arr.sorted?` | | It's just a convention, but it's a nice way to let the | writer know what will happen. | canjobear wrote: | The ! convention is ok but I don't think it's optimal | because, in the presence of higher-order functions and | related concepts, it's often not clear if a function | should be marked as !. | | For example if I have a map function that applies a | function f to a sequence, should I call it map! because I | might pass in a function f that mutates the input? If so | then it seems like any function that takes a function as | input, or any function that might call a method on an | object, should get marked with ! just in case. But if I | don't mark it that way then the ! marking is not as | informative: I might end up with a line consisting only | of non-! functions which still mutates the input. | zarzavat wrote: | map! would mean a function that performs a map in-place | on the array by replacing the values. So it would depend | on if the callback was encouraged to mutate the array or | discouraged from doing so. | dmurray wrote: | I'll just point out that this is originally from Scheme | (I think... Maybe Scheme got it from a previous Lisp) but | borrowed by Ruby. Neither Scheme nor Ruby do a perfect | job with sticking to the naming convention, at least if | we include popular libraries, but it is very handy and | intuitive. | | https://stackoverflow.com/a/612588 | aftbit wrote: | random.shuffle() has bitten me that way a few times too: | array = random.shuffle(array) | | because I expected it to return a copy or reference, instead | making my array None. | | It would also enable chaining operations: | array = array.append(A).append(B).sort() | | In-place vs immutable copy is a language design choice with | tradeoffs on both sides, but there's no reason that I can see | to not return a reference to the list. | | Perhaps recognizing this is really the job of an external | linter. Sometimes I wonder if the future of enforcing | canonical formatting on save like "gofmt" or "black" will | extend to auto-correcting certain goofy errors on each save. | | mypy would yell at you about this, but afaik type-checked | python still isn't the norm. | int_19h wrote: | In Python, a function that makes and returns a copy would | be idiomatically named shuffled(). Consider sorting: | xs.sort() # in-place ys = sorted(xs) # copy | | As for functions returning the object - I think it's a hack | around the absence of direct support for such repetition in | the language itself. E.g. in Object Pascal, you'd write: | with array do begin append(A); | append(B); sort; end; | | Or better yet, in Smalltalk: array | append(A); append(B); sort. | | https://en.wikipedia.org/wiki/Method_cascading | eximius wrote: | In addition to this, I highly recommend just reading the | codebase. I haven't written C since college and it's remarkably | readable. | | I once tried to catalogue all the stdlib operations which release | the GIL, meaning if you use only those (well, only those "heavy" | bits, you can still use other small blocking glue bits), you can | do "real" multithreading. | | It was a fun exercise! | hultner wrote: | There's a really nice (although old now) walk through of the | cpython code base on YouTube. I watched it on a long 24 hour | flight between Canada and Sweden a couple of years back. | | Edit: Found it! You're in for about 9 hours of quality | watching. | https://youtube.com/playlist?list=PLwyG5wA5gIzgTFj5KgJJ15lxq... | submeta wrote: | Excellent, thanks for sharing! | dharmab wrote: | Agreed. CPython makes readability and maintainability a | priority. | matheusmoreira wrote: | > I highly recommend just reading the codebase | | Me too. When I used to write Ruby I read a lot of CRuby source | code. I achieved a much deeper understanding of the language | that way. Even answered some really fun stackoverflow | questions. | | Now the first thing I do when I see a new language is read its | source code. | riazrizvi wrote: | And it helps much more in the actual job of writing software | than in say, practicing coding puzzles | PostThisTooFast wrote: | "Builtins?" | escanor wrote: | > for index, item in enumerate(menu): | | should be | | > for index, item in enumerate(menu, start=1): | | for the example to be correct :) | sireat wrote: | I am a pretty average Python programmer(5 years teaching, 15 | years writing). | | I still wonder what was the reasoning for allowing creation of | local objects with the same name as builtins. | | Okay it can be nice to redefine pprint as print I suppose. | | Still how many sum, list, min, max, dict(!) have been erroneously | redefined in beginner tutorials and beginner code. | | From my experience sum and list suffer the most. | | Sure there are linters that will warn you but there should be a | setting for the interpreter (as in -Werror in GCC) to disallow | this silliness. | nneonneo wrote: | Python itself doesn't disallow this because there are quite a | lot of builtins with useful names - for example, `file`, `id`, | and `hash` to name a few. Disallowing setting these would be | tantamount to adding a bunch of new keywords to the language, | which they've been quite loathe to do in general. | | A good linter will catch these, so in production environments | you usually don't run into issues. I agree that it can be a | beginner trap though! | wpietri wrote: | No idea what their actual reasoning is, but here's how I think | about it: | | This is better for novices, because otherwise you create a | whole bunch of land mines for people who are desperately trying | to get something done. If they aren't aware of the built-in | then they aren't trying to use it. Insisting that they become | aware of something they don't want right then will be | frustrating. | | It's also better for experts, in that they're generally aware | they're overriding a built-in and are doing it on purpose, and | if not they'll have an IDE or linter reminding them. | | To me, I see tooling as a spectrum from supportive to | controlling. Python is very much on the supportive end. It | feels controlling when I get interrupted because some | programmer who has never met me programmed a tool to insist I | do things their way. That would very much include insisting I | respect a bunch of names they decided long ago to put in the | global namespace. | int_19h wrote: | Consider what'd happen whenever a new builtin gets added. | tusharsadhwani wrote: | I think linters are really effective to figure out such issues | in professional code. | | For students for example, I'll have to agree. Maybe having a | flag or environment variable that teachers can set up for it | would be a nice idea. You should start a thread on the python- | ideas mailing list about this, and it might get somewhere :) | ps173 wrote: | I am not even half way through it and I now understand how python | actually works under hood. This is great for understanding how a | lot of interpreted languages work | nneonneo wrote: | Cute fact about __debug__: it is one of the only ways to get | compile-time conditionals in Python. Performing a comparison with | `if __debug__:` will output byte code for the ensuing statement | if and only if the interpreter is in debug mode - notably, in | `-O` mode, it will not even generate a load of __debug__ and a | conditional jump, and acts as if the statement didn't exist at | all. | BiteCode_dev wrote: | Unfortunatly you often can't use -o because of the 3rd party | libs that didnt' get the memo and use assert for error | checking. | | We still have -X dev and sys.flags but it's runtime only. | alshel wrote: | Care to name any 3rd party libs in particular? | human_error wrote: | Starlette, SQLAlchemy. | BiteCode_dev wrote: | Nope, I just remember my server crashing years ago so I | stopped ever since. | | Would be interesting to scan pypi and check if this is | still a thing. | | Maybe create a bot warning lib authors. | zarzavat wrote: | Seems like a better idea would be to make a separate flag | "--remove-assertions" for people who desire that. | tusharsadhwani wrote: | that is indeed interesting. Mind if I add this in the article? | globular-toast wrote: | I knew that `assert` behaved in a similar way. Turns out it's | actually equivalent to an `if __debug__:` | https://docs.python.org/3/reference/simple_stmts.html#gramma... | Fordec wrote: | Not going to lie, if I could enable stricter compile time | conditions without debug mode that would be a very welcome RFC | cinntaile wrote: | Are there any good books that deal with writing pythonic code? As | well as being focused on more intermediate or advanced features | like this? If the book is project focused that's a bonus. | Performance trade-offs another bonus. | tusharsadhwani wrote: | I can personally recommend Fluent Python (its 2nd edition is | about to come out in a couple months) for learning these | intermediate/advanced concepts, and Python Cookbook for code | examples using many of these features. | | I don't know any books for projects per-se, maybe HN will know! | [deleted] | usrme wrote: | I would recommend "Robust Python" by Patrick Viafore. It | teaches you a lot about type annotations (among other thing) | and gave me personally a whole new way of looking at the code | that I write. | tracyhenry wrote: | Searching python on HackerNews readings (a site I built): | https://hacker-recommended-books.vercel.app/category/0/all-t... | The results include _Fluent Python_ recommended by | tusharsadhwani | disgruntledphd2 wrote: | Fluent Python is definitely a good book, I knew a whole bunch | of the stuff in this article because of it. I only got to | Chapter 9, but it legitimately made my Python much, much | better. | dehrmann wrote: | In a way, learning Python is harder for people experienced with | another language because so much of the content you find is for | first-time programmers. | | That said, I think I had good luck with Writing Idiomatic | Python. | matsemann wrote: | Yes! | | When I learned Kotlin, I just read through the docs, and then | knew of basically all the different concepts in the language. | | For Python, the docs were comparably very bad. For instance, | Decorators aren't mentioned even once in the "The Python | Tutorial". In "The Python Language Reference" (if one even | bother to read such a dry document) it's barely mentioned in | passing. How should a new user know it's a concept and how to | apply it? And the language reference links only to a glossary | item, and none of them specify how parameters in a decorator | is supposed to work. | | Pretty frustrating experience, put me a bit off the language | from the get-go. | tored wrote: | As a non-Python dev I tried the other day to read up how | decorators work in Python using the docs, can't say it | helped. | mattficke wrote: | Effective Python [0] is my favorite book in this category. | | [0] https://effectivepython.com/ ___________________________________________________________________ (page generated 2021-10-10 23:00 UTC)