[HN Gopher] This is valid Python syntax
___________________________________________________________________
This is valid Python syntax
Author : ankitg12
Score : 92 points
Date : 2023-06-11 11:10 UTC (11 hours ago)
(HTM) web link (www.bitecode.dev)
(TXT) w3m dump (www.bitecode.dev)
| EddieJLSH wrote:
| Fun syntax gotcha that I saw recently was a YAML MAC address that
| were all numbers like 11:22:33:11 which Python was parsing as a
| sexagesimal number so some libraries were throwing type errors
| dec0dedab0de wrote:
| Oh man, I seriously just wretched at the idea of putting an
| import in a lambda. That was sick, and awesome.
| Waterluvian wrote:
| Python has so much potential for doing the most horrible
| things. I love it. I love seeing what people come up with.
| gonzo41 wrote:
| Until you find them in production. Then you git blame and go
| take a long look in the mirror thinking about what you've
| done.
|
| In the early aughts the terseness of python seemed great. Now
| as I age I find i have less time for it.
| IshKebab wrote:
| I don't think terseness is the issue. It's the fact that it
| lets you do anything, even if it's a terrible idea.
| `__subclasses__` is my favourite example of that.
| Waterluvian wrote:
| I feel the exact same way. Python gave me so much
| flexibility and escape hatches when I did dumb design
| things as a novice. Now as a veteran I just want a boring,
| strong static language with clear boundaries and
| behavioural expectations
| sureglymop wrote:
| I just always found it nice as a prototyping language. If
| you have an idea of how you want to do something or solve
| some problem, create a working prototype in python. Then
| create a more polished version in another language.
| pedrovhb wrote:
| > While not as powerful as destructuring in other languages (I
| still wish we could unpack dicts as JS does with objects)
|
| Like this? In [1]: a = {1: 10, 2: 20}
| In [2]: b = {3: 30, **a} In [4]: b Out[4]:
| {3: 30, 1: 10, 2: 20}
|
| Or pattern matching? In [6]: match b:
| ...: case {3: somethingelse, 1: foo}: ...:
| print(somethingelse, foo) 30 10
| babel_ wrote:
| I think they mean {a: x, b: y, *c} = d, which is a nice idea
| now we have a stable order to dicts, but I've rarely come
| across times when I needed this that I can't simply use
| .items() and such for, or care about more general pattern
| matching like values for specific keys, which you can just do
| a, b = d["a"], d["b"] with little loss of clarity, sure {"a":
| a, "b": b} = d is cute and all, and maybe we'll oneday see it
| now we have match as syntactic precedent, but I don't really
| feel concerned by its absence.
| tuukkah wrote:
| They mean you can unpack lists: a, b, *rest =
| args
|
| So why not dicts: "a": a, "b": b, **rest =
| kw_args
|
| Also, in JS the destructuring does not fail because of extra
| elements even if you don't capture them. Further, in JS you
| don't need the quotes, and a: a can be shortened as simply a.
| You'll need the dict curly braces though. Thus the same in JS,
| which you see a lot e.g. for properties of a component in React
| code: { a, b } = props;
|
| It's quite nice with no excessive "line noise" and even makes
| up for JS functions lacking real keyword arguments.
| bandrami wrote:
| As a Perl guy none of that is even slightly distressing to me
| 1024core wrote:
| Python is slowly morphing into Perl... :-D
| bionhoward wrote:
| Lol, your definition of "valid syntax" is wayyy different from
| mine. I wouldn't ever use crap like that, it's too unreadable to
| be "valid syntax" even if the interpreter can handle it. Can we
| all collectively agree to include our own brains in the
| definition of "valid code?"
| dinom wrote:
| Lol, and they say Perl is bad.
|
| https://www.perl.com/pub/2000/01/10PerlMyths.html/#Perl_look...
|
| This isn't meant as flamebait, I'm just pointing out you can
| write obfuscated code in any language.
| ptx wrote:
| Strange article. I thought it was going to show that these
| features interact in some unexpected way that causes weird bugs,
| but it all seems to work as expected.
|
| So the conclusion, I guess, is that code doesn't make a whole lot
| of sense when it uses every available feature for no particular
| reason and foreign languages for variables names?
| raincole wrote:
| This article just reminds me how unexpected JavaScript is,
| unlike Python.
| BiteCode_dev wrote:
| Author here.
|
| There is no conclusion to the article, the weird syntax is just
| a mean to an end:
|
| - This makes people curious, so they read.
|
| - This gives an opportunity to show a lot of cool features of
| the language in a short format. As the post mentions, Python's
| learning curve is smooth but long, so it's nice to be able to
| introduce some tricks to coders that would otherwise not be
| exposed to them.
|
| - This demonstrates those things indeed, as you said, work
| together. Even if Python is a language on which a lot of things
| have been bolted on after the fact, it's still decently
| integrated.
|
| - This allow me to insert a warning about not abusing them
| because even if Python is a readable language, you can always
| go too far. It's good maintain this message in the community.
|
| - It's just fun, really. It's an article I would enjoy reading.
| babel_ wrote:
| Must admit I wasn't quite sure what the point of the first
| one was. 0xBEAF is literally just 48815, as is 0x_B_E_A_F, as
| the numeric literals now ignore interleaved "_", and you just
| format it as a float after-the-fact as normal in an f-string.
| So why? There's nothing particularly special in the use of
| syntax here, so hardly a curious or weird artifact, let alone
| abuse.
|
| > * has a completely different meaning
|
| I'd personally rephrase this, as the idea around splatting is
| that it symmetrically can "gather" and "spread" multiple
| values, albeit with the syntactic limtations of starred
| expressions (likewise with *).
|
| You do seem to confuse syntax for semantics: [][:] = [...] is
| a semantic trick to avoid binding a sequence, if you did a =
| [][:] = [1,2] you'd find a == [1,2] because it just passes
| along the reference to the right hand side, so without
| another layer it's just skipping binding it, and that's the
| major trick with the second one, being that it "discards" the
| first value when destructuring this way (so long as that
| first value is a sequence). This can be done "better" with _,
| *_ destructuring, which probably weirds more people out to
| discover that shadowing in a statement is legal (and the
| unused/shadowed variable is "better" as it works even when
| the first value isn't a sequence, making [][:] more fragile).
| People with knowledge of other languages might get confused
| by _ being a variable, but that's a known stumbling block.
|
| Naturally, everyone has their own journey through discovering
| Python (or whatever language) and may or may not touch upon
| many parts of it syntactically and semantically. However, as
| far as "weird" syntax that has valid semantics goes, I'd say
| this is far from esoteric or unusual (where's the walrus?),
| and some parts are just the natural expression (such as with
| the splatting). Really, the only code smell I find here is
| the inlining of everything and redundancy (i.e. splatting a
| generator into a list)!
| agumonkey wrote:
| as a semi plt nerd, i like to push grammars a bit deeper than
| average application, thanks
|
| and often indentation helps keeping track of structures
| [deleted]
| BoppreH wrote:
| I don't find that so bad. They look complicated but behave as
| expected, and would only be a problem if you inherit a code base
| from a lone wolf without common sense.
|
| Python has plenty of warts[1], but I don't think the syntax is
| one of them (yet). Some of the more concerning warts:
| # Mutable defaults in function parameters. def
| fn(arg=[]): arg.append(1) print(arg)
| fn() # [1] fn() # [1, 1] fn() # [1, 1, 1]
| # Loop variables are shared across executions.
| lambda_list = [lambda: print(i) for i in range(5)] for fn
| in lambda_list: fn() # 4 # 4 # 4
| # 4 # 4 # Iterating over an exhausted
| generator yields an empty list. numbers = (i for i in
| range(5)) assert 1 in numbers
| print(list(numbers)) # [] # I hate this one
| because it could have easily been an error, it's # hard
| to track down, and lots of stdlib functions return generators.
| # Chained operators can sometimes be very unintuitive.
| should_be_ascending = True assert 1 < 2 ==
| should_be_ascending # AssertionError because it was
| interpreted as # `(1 < 2) and (2 == should_be_ascending)`
|
| [1]: https://github.com/satwikkansal/wtfpython
| [deleted]
| babel_ wrote:
| I agree that functions sharing the references to their
| arguments is awkward (though useful on occassion), however the
| alternative is to re-instansiate the arguments by a shallow
| copy, a deep copy, or full-on re-evaluation, all of which have
| their problems and are significantly slower (calls are already
| slow in Python as is!) so it makes sense on balance for many
| use cases, especially as most defaults in practice are
| immutables like numbers and strings (so it's really just the
| usual warts for references to mutables).
|
| The others are a little less concerning to me. Sure, the lack
| of fine-grained scoping is annoying, but that's just how Python
| operates; the example confuses the point via the lack of
| variable capture in lambdas, when fn is called it does a normal
| locals/globals lookup for i, and the lack of scope means the
| last value of i spills outside of lambda_list, meaning all
| evaluate to 4, but this is primarily a problem with how lambda
| works without capture (again, scoping in Python is what it is).
| I've even begrudgingly (ab)used Python's lack of scoping on
| occassion, albeit mindfully aware of when I can and when it is
| actually defined, because it is very easy to cause problems
| with and generally best avoided.
|
| Empty generators to empty lists isn't a huge concern in my
| experience, as it lines up with empty lists, sets, and dicts as
| the behaviour for comprehensions and how to handle them when
| there's nothing. Also, your example for it, uh, doesn't work as
| you say (it prints [2,3,4]).
|
| Operator precedence is always awkward in every langauge that
| has it, and operators are still awkward in those that don't,
| and Python's is no exception, though it's largely better than
| C's -- in Python, comparisons share precedence and are lower
| than most other operators. I feel there isn't really a right
| answer to the problem as is, though ==/!=/in/is maybe shouldn't
| be part of chaining (but chaining is defined over all
| comparison operators [1] so that's probably not going to
| change). As always, when in doubt, use parenthesis.
|
| [1]:
| https://docs.python.org/3/reference/expressions.html#compari...
| crabbone wrote:
| Syntax is pretty much one of the downsides of Python.
| Especially the new additions, including f-strings, match-case,
| type annotations, async-await, data-classes etc. The largest
| downside is that they hugely blew up the language complexity
| for no tangible gains. It's like you had a crappy bicycle
| before, but after an "upgrade", you have a crappy bicycle that
| doubles as a crappy ice-cream maker.
|
| This means that anyone who wants to write a parser for Python
| from scratch is screwed. The language was garbage before, and
| writing a parser for Python was already very difficult, but
| after more junk was added to it, it just became unnecessarily
| more difficult. So, if you might have had a glimmer of hope to
| have tools that analyze the source code and do something with
| it, even as trivial as highlighting... well, there's less and
| less hope of that happening.
|
| So, OPs complaint about f-string is sort of valid. OP didn't
| really look for a very bad example though, but bad examples are
| hard to refine into more concise form. Fundamentally, what
| sucks about f-strings beyond them being completely unnecessary
| is the situations where one has to deal with multiple
| incoherent escaping rules, s.a. when one has to interface with,
| eg. logging module or string.Template or str.format() or % in
| general. Another vomit-inducing combo is raw strings combined
| with f-strings (i.e. building regular expressions through
| interpolation).
|
| Yet another downside of f-strings is the departure from object-
| oriented approach. The benefit of str.format() was it being
| object-oriented, which allowed for extensions in the same way
| objects can be extended. f-strings are an extension of the
| language syntax, but Python doesn't have an easy general way to
| extend its syntax (barring the fact that you can abuse the
| import system to import altered Python sources s.t. they have
| desired syntax).
|
| But, again, these disadvantages are hard to present as a one-
| liner, and for many simple uses of the language, which is the
| majority of its uses they are inconsequential. Unfortunately,
| that majority of uses is also transient and contributes nothing
| back to the language... so, it could be confusing to rely on
| the majority's opinion when it comes to judging the quality of
| various language features.
| mixmastamyk wrote:
| You don't write a parser from scratch, you use the ast
| module.
|
| The rest of this is excessive negativity trying to sound
| superior while ignoring history.
|
| (I even agree about the match statement and walrus, but
| fstring is a very useful compromise.)
| otherme123 wrote:
| I'm using walrus a lot, can't find any negative: it's clear
| what it does, and the code feels cleaner.
| gcbirzan wrote:
| First one is annoying, but it makes sense, to some degree. You
| say the default is this particular instance that gets created
| when you define the function.
|
| Second is just how things work, the function is a closure that
| evaluates the non local variable when you run it.
|
| Third one... Iterating over the generator does NOT yield an
| empty list, it raises StopIterator. As does trying to iterate
| after a generator is done, so there's no way for list to know
| what's happening. You can argue that's a bad design, but I'm
| not so sure, an iterator is something you need to make sure you
| don't try to iterate over twice...
|
| I don't get the 4th. Comparison operators have the same
| precedence, so yeah.
| BoppreH wrote:
| > First one is annoying, but it makes sense, to some degree.
| You say the default is this particular instance that gets
| created when you define the function.
|
| Could just as easily be an expression that's reevaluated on
| each call. In fact it's what most other languages do, making
| it twice as bad a decision. Even Javascript gets it right:
| function fn(arg=[]) { return arg; } fn() === fn()
| // false
|
| > Second is just how things work, the function is a closure
| that evaluates the non local variable when you run it.
|
| That's again a decision made by Python. Some other languages
| behave as if each iteration had declared a different
| variable. Here's Javascript again, you can even use `const`
| for the iterating variable: const fns = [];
| for (const i of [1, 2, 3]) { fns.push(() =>
| console.log(i)); }; fns.forEach(fn => fn());
| // 1 // 2 // 3
|
| > Third one... Iterating over the generator does NOT yield an
| empty list, it raises StopIterator.
|
| Sorry, my phrasing was imprecise. It's not doing 'yield []',
| but an exhausted generator does behave like an empty
| generator. Maybe they could have made it so the first
| exhaustion raises StopIterator, and subsequent ones a
| different exception? Like reading from a closed channel.
|
| > I don't get the 4th. Comparison operators have the same
| precedence, so yeah.
|
| It's not precedence, it's Comparison Chaining, the same
| feature that enables '1 < a <= 10'. If the operators had
| simple precedence, it would be equivalent to '(1 < a) <= 10'
| or '1 < (a <= 10)', but Comparison Chaining evaluates it as
| '(1 < a) and (a <= 10)'. Useful for specifying ranges, but a
| foot gun in other scenarios, like in my example.
| gpm wrote:
| > Second is just how things work, the function is a closure
| that evaluates the non local variable when you run it.
|
| In python, yes. In other languages, not necessarily. There
| are two ways that this could reasonably not be the result:
|
| 1. For i in iterator could create a new variable i (shadowing
| the old i) on each iteration of the loop. This isn't how
| python works (which also means you can do things like access
| i after a for loop terminates) but there are languages that
| work that way.
|
| 2. Capturing an integer could _copy_ that integer, instead of
| copying a pointer to a value that can change in the future.
| Again, not how python works, but how some languages work.
|
| The first in particular I think is more intuitive in other
| languages, and leads to other footguns in python as well,
| e.g.: for i in range(5): for i
| in range(10): pass
| print("Finished loop iteration", i) # I meant 0, 1, 2, 3, 4.
| Instead I got 9 9 9 9 9
| accoil wrote:
| I can see how it happened, but the first one does not make
| sense. Most people would expect argument defaulting to be per
| execution. If the argument is None, instantiate it with this
| new value.
|
| I'm curious what making it shared and mutable achieves.
| travisjungroth wrote:
| On the generator example, it doesn't yield an empty list. It
| continues to return an empty iterator. This couldn't have
| easily been an error because one aspect of generators is they
| can be dynamic. There could be an API call in there, where it
| polls for new results. Raising a "NoMoreForever" error or
| something would break the interface.
| BoppreH wrote:
| You're right, "empty list" is an imprecise description. But
| couldn't they have made it behave like Go channels, for
| example? Read it until exhaustion, then close it, so that
| subsequent reads (or calls to 'next()') fail with something
| other than StopIterator.
|
| I know it's not backwards compatible and hence a no-go now,
| but the original decision puzzles me.
| [deleted]
| formerly_proven wrote:
| 1 and 2 are the scoping rules (since Python has no variable
| declarations [1], making all blocks scopes would be pretty
| awkward and require tons of "xy=None # reassigned later" and
| nonlocals), 3 should result in [2, 3, 4], not [].
|
| [1] It actually does nowadays, ever since non-assigning
| annotations came around.
| BoppreH wrote:
| Correction, the empty iterator example should have been `assert
| 5 not in numbers`, or another expression that exhausts the
| generator.
|
| And to everyone saying that the behaviors are correct because
| of scoping rules, or iteration protocol: I know. Those are not
| compiler bugs, but bad design decisions. Every example here has
| a more intuitive behavior in another language.
| actionfromafar wrote:
| Someone(tm) should make a Python-to-Python compiler which
| converted all normal constructs into weirdness like this.
| uncharted9 wrote:
| For me, one of chatGPT's most frequent uses is to convert all
| of these one-liner statements into logical multi-line code that
| my feeble brain can process.
| lysecret wrote:
| Biggest footgun in python IMO are mutable default parameters. It
| amazes me that someone thought this was a good idea.
| lysecret wrote:
| Also a most random note here:
|
| mutable /'mju:t@bl/ adjective liable to change.
|
| I like the emphasis that it's liability haha
| Spivak wrote:
| That's not really what's going on. Python has default values
| where, say Ruby, has default expressions. def
| myfunc(x=[]) puts x end
|
| In Ruby is the same as def
| myfunc(x=lambda:[]) print(x())
|
| in Python when the default is needed. The benefit is
| performance, you only have to evaluate the default once at
| function definition time. I won't say it's not a footgun but
| it's also I think a sane choice when in Python it's common to
| have functions with many default parameters.
| o1y32 wrote:
| "the benefit is its performance"
|
| Sure, but when you design a language you are striking a
| balance between many things (which is why some features could
| take years to finalize and implement). In this case, the gain
| in performance is likely minimal, but the confusion and
| surprise it causes does a lot of damage.
|
| (I recently read through the entire series of conversation
| with Anders Hejlsberg on the design of C# which is very
| insightful https://www.artima.com/intv/anders.html)
| sfink wrote:
| For lists, use a tuple as default, since it's immutable.
| def foo(allowed=()): truthy = ['true', 'True']
| truthy += allowed # most things work
| allowed.append('yes') # error
|
| It's not perfect (`truthy = ['true', 'True'] + allowed` will
| _not_ work).
| MawKKe wrote:
| while that works, it does not fully document what is expected
| from caller. And AFAIK using tuples like that is not very
| common. I'd prefer something like: def
| foo(allowed: Optional[List[ElemType]] = None):
| allowed = allowed or [] ...
| crabbone wrote:
| You are too naive when you think that someone _thought_ about
| it. Many things in Python happened because that 's how they
| were implemented initially _without_ much thinking or at all.
|
| There was no plan, and, in the initial stages, I'd imagine that
| the author(s) were surprised that they'd even gotten this far.
|
| Famously, the whole object system in Python was implemented in
| like a few days. That included the design and testing too... It
| did see some changes over time, but there's a lot of petrified
| turd there that's increasingly difficult to get rid of.
| macintux wrote:
| Unpacking used to be more powerful. I was mostly oblivious to the
| Python community when I started working on a project and
| discovered that you could unpack tuples in function heads, like
| Erlang, and started using that frequently.
|
| Then I discovered I was using Python 2 and it was EOL, and that
| Python 3 had dropped support for that "because no one uses it."
|
| Maddening.
| bettercallsalad wrote:
| We had a dev in our team who would love to use these one liner
| cryptic expressions and challenge the minds of code reviewers.
|
| It was all fun until he left and others had to refactor bunch of
| code. One liners like that often have very little benefits IMHO.
| svilen_dobrev wrote:
| most of these are loooong time there. list[:] replacement is
| since ever. (used for reversing a list, when there wasnt .reverse
| method)
|
| But my beloved one, is the sequence of these changes, in time:
|
| ver.134: x = ('a', 2)
|
| some change in requirements made 2nd value unneeded, so it
| became:
|
| v167: x = ('a',)
|
| which is one day, "optimized" to:
|
| v193: x = ('a')
|
| Funny thing is, and x[0] will still deliver 'a' as it was before.
| Now if that 'a' is not constant but some parameter, and it's not
| 1 character long string..
| 2h wrote:
| > a, b = b, a
|
| > (don't know why this is so famous, I never, ever used that in
| prod)
|
| This allows you to sort an array in place. It's heavily used in
| Go:
|
| https://godocs.io/sort#example-package
| roelschroeven wrote:
| A variant is useful to update two (or more) dependent variables
| in one go without having to introduce temporary variables. For
| example to calculate the greatest common divisor using the
| Euclidean algorithm: def gcd(a, b):
| while b > 0: a, b = b, a % b return
| a
| babel_ wrote:
| Swapping variables around is also really handy for patterns
| like "alter list a until stable", especially with the walrus :=
| operator allowing you to avoid declaring b outside of the while
| loop, keeping the intent clearer.
| BiteCode_dev wrote:
| Yeah but in Python list.sort() takes care of that.
| falcor84 wrote:
| True, but every once in a while we want to sort something
| that isn't a list and/or with a custom sorting algorithm.
| tgv wrote:
| You can always write something like {var tmp=a;a=b;b=tmp;} in
| any language if you want to sort in place.
| 2h wrote:
| > in place
|
| that doesn't mean what you think it means.
| Etherlord87 wrote:
| Do you know what it means? It seems irrelevant to the
| discussion, introducing a new variable won't make you
| unable to sort in place. You could even allocate memory on
| the heap and still sort in place.
| 2h wrote:
| > Do you know what it means?
|
| yeah, it means no temporary variable.
| Etherlord87 wrote:
| The difference here is, a,b=b,a will use one less bytecode,
| by taking advantage of ROT_TWO
| jonnycomputer wrote:
| God, is this going to be what I see in my next python coding
| interview?
| iblaine wrote:
| Idea for a new linter; throw a warning if it sees extreme non-
| intuitive uses of Python. Given the trajectory of Python
| improvements over the past few years, this linter may not be
| far off.
| Sosh101 wrote:
| If so, then I'd be happy to fail.
| guenthert wrote:
| > Inverting variables:
|
| Non-native speaker here and even after 20+years every once in a
| while it bites, but I know that as 'swapping variables'. Where
| does the inverting come from? Related to the 'inverting a tree'?
| coke12 wrote:
| It's possible they are trying to avoid making people think of
| memory swapping which is existing jargon and usually means
| something more complex.
| charcircuit wrote:
| Shu Zi is numeral. It doesn't make sense as a translation for
| numbers in that function.
| randallsquared wrote:
| > _if you use "_" in hex, and interpolate it with a 2 decimal
| precision_
|
| ...or if you don't. The '_' doesn't change anything or add
| anything, here.
| kkirsche wrote:
| I learned about Python dev mode in this, thanks!
|
| For anyone else:
|
| https://docs.python.org/3/library/devmode.html
___________________________________________________________________
(page generated 2023-06-11 23:01 UTC)