[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)