> lack of typing information isn't "free solo" hard but it certainly makes life a lot less pleasant.
One of the projects I work on has switched to full type hinting along with heavy mypy¹ usage, and it has become an absolute pleasure to work with. Along with hypothesis², I can't recommend mypy enough. It must be said that retrofitting either to an existing project is a lot of work though.
What I can't reconcile with myself is, if I'm going to add type hinting in Python, why not use a language where my efforts of adding type hints result in performance gains? I get why it's nice for a team, but it seems like a lot of work for half the potential benefit of a typed language.
I note with interest that modern python has type hints.
Years ago the lack of visible types was often flouted as a benefit (i.e. terser code, less boiler plate, easier for learners to understand etc).
I eagerly await the addition of optional "scope hints" of { and } ! ;-)
Sarcasm aside, now that there are type hints and python programmers need to type as much as Java/golang/c# programmers, why not just write in those languages?
Genuine question - what are the benefits of starting something new in python (assuming all things being equal - i.e. equal knowledge in java/golang/c# and no legacy reasons forcing you)? Why would anyone pick writing type-hinted python Vs a fully explicitly typed compiled language?
> I eagerly await the addition of optional "scope hints"
Scope hinting is simply called braces; `from __future__ import braces`.
> Genuine question ...
IMO assuming all things are equal seems to miss how things actually are. The pluses still fall on Python some times, and other languages at other times. Things do vary; developer availability, library availability, target system support, certification story, tooling, even curbside appeal can be legitimate on some occasions.
I'm trying not to pick on your individual language examples, as firing digs seems to be missing the point we're discussing. Or perhaps it is the very point, as two of them wouldn't have even been in my list.
I /feel/ like this gist is largely on the money, and also uninteresting at the same time. All languages suck, and often suck in different ways for different users. We just have to pick the one that is working best at the time, and that we're best at working with.
I will say that I do like the act of writing a pro and con list for a language or tool though(which is why I read the linked doc), and I often push co-workers to do so when they're suggesting things. Sometimes it shakes a bad choice out just with the simple act of jotting down a 5-point list.
Unless, of course, there actually is an ideal language out there. In which case I retract my point ;)
It makes scope visual and consistent. IMO it's less dumb than having scope defined by pairs of curly braces which are impossible to read without indentation and require extra effort to keep matched - unless you try to automate the matching with a good IDE.
In the era that python was created in, autoformatters were not a thing and I appreciated the forced indentation. Nowadays with autoformatters being standard, the downsides of semantic white space is not worth it.
This list is so deliciously sophomoric. The best one is, and I quote:
"""To many other weirdo bits of magic syntax, like [list comprehensions]"""
Obviously without actually proposing how comprehensions could be made better one has to hope the author would say he likes the equivalent Haskell better, but there is a strong doubt that is not the case.
Complaining about having to cast generators to list... seems like the kind of dev that has their code randomly run out of memory until a senior dev comes and fixes it.
It is annoying! And doesn't always save memory. In theory a "sufficiently smart language" should have a feature to understand that `thing[3]` can be translated to "call `next()` 3 times and gimme the last thing", not in "turn this completely to a list, that may take a ton of memory, although I only need that one third element".
Generators should be treatable as lazy lists in the end, and lists should have a common interface whether they are lazy or eager. Someone should figure out a way to have us write code at a higher abstraction level and have same interface for lazy vs eager data structures.
...but it's not gonna happen in a dynamic language like Python. And I can't say I like the "solution" of having the entire language be lazy like Haskell either :|
We're stuck with "casting generators" for now, I guess, but it really does suck!
You don't need to cast the generator, you can do:
next(itertools.islice(my_generator, n, n+1))
With that said...
> In theory a "sufficiently smart language" should have a feature to understand that `thing[3]` can be translated to "call `next()` 3 times
This might be a newbie trap, because next() isn't the same as indexing. What happens if I perform `thing[7]` followed by `thing[5]`? Should performing `thing[7]` put 1-6 in memory and turn the object into a generator-list hybrid?
> Should performing `thing[7]` put 1-6 in memory and turn the object into a generator-list hybrid
You're right here. Probably can't work like that since generators are too general, you can't expect them to be rewindable or to not have side effects... prob you'd need a more specialized concept like a "lazy list" that would be a subtype of generator with some extra restrictions that would make it possible to implement the "hybrid" structure as an implementation detail without changing semantics.
Anyway... it would be too much work and probably would turn into a footgun.
I personally find list comprehensions in python pretty horrible.
They seem to exist only to do lots of stuff in one single line of code. You end up with totally impenetrable unreadable perl-esq garbage write-once-read-never code that is too clever for its own good. And people say python is easy to learn and good for beginners...!
A better approach would be something like Java Streams/.net Lync/RxX pattern IMO. Explicit, clear, no magic, logical.
" impenetrable unreadable perl-esq garbage write-once-read-never code " - I had about the same impression when I first saw it. I think ternary operator is as far as I am willing to go ;)
You can decompose any list comprehension into it's equivalent for loop fairly easily. I don't see what is so magic about them other than the language having a special compiler level optimization for them?
g = [i for i in list if x == 3] -> g = []; for i in list: if x == 3: g.append(i);
I see people frequently say that list comprehensions are special-cased and highly optimized by the compiler, but I did a few experiments recently with `timeit` and found that using `map()` is on the order of 1.5x faster in all the cases I tested. I think that either winds up being faster than a manual for loop, though
dic = {k: v for k, v in dic.items() if k in other_dic and v == "bar"}
How could that be improved? That's 3-4 LOC minimum in any other language
My main grip is python's ternary operators, since the True value is evaluated before the condition, if you are doing ternaries on things that might throw exceptions the False value has to come first
value = 0 if key not in dic else dic[key] * 5
rather than (throws indexerror if key isn't in dic)
There are a number of precedence rules you need to keep track of to parse the list comprehension. There are two different syntaxes that do the same thing. Arguably you need to do the same if you don't already know how the threading macro in the my second example works.
I posted due to your claim regarding all other languages necessitating increased verbosity. I should have left it lie, as I didn't intend to promote a language war, just to post a counter example. My apologies.
It appears textually first but the evaluation order is the same as the classic ?: ternary. ```[][0] if False else 'foo'``` will not raise an IndexError.
List comprehensions are one of those abuse subject features (like most things) where light simple use is fantastic but some people just take it too far into unreadable nastiness.
list comprehensions can be elegant and actually improve readability. However, I do agree that it can be easily misused. I have seen many junior Python programmers writing super long, complicated comprehensions that hurt my brain. They think it is pretty cool just because their solutions are one-liners.
For example, instead of putting someone's article down as sophomoric, you could explain what's different and possibly better about Haskell list comprehensions.
There's a huge difference between a classroom environment or academic symposium, and an internet forum. Here, when people attack and take swipes at each other, discussion slides downhill so fast that it becomes an existential issue for the forum. Having HN not destroy itself the way internet communities usually do has been the main goal here since pg created the site over a decade ago, and the guidelines here are written with all that experience in mind.
I used to think similarly about this to what you express, because I've always enjoyed reading about the sort of discourse in which devastating wit is exchanged. But eventually I realized that it doesn't translate into this context at all. This is a case of 'the medium is the message'. When you have millions of people who don't know each other all potentially interacting at the same time, the dynamics are so different as to be incommensurable with, say, small debates, literary journals, elite social events, and other places where the groups are small and highly cohesive. Here are some previous explanations about this:
Django is ok if you have a web site with content and forms that map nicely onto SQL tables. Any 1998 website, basically, with not too much of a load. Any other setup, and it gets messy.
> Needing to put dict property names `{'in': 'quotes'}.
It seems he just doesn't want to type quotes and is OK with breaking everything else to get that. Which is fine, but no one else supports that change request.
>Does the author not realize that you can use (almost) anything as a dict key?
You can do that in JS too, it's just coerced to string, so you can do e.g.
{foo: "bar"}
where foo is not a variable, but assumed to be the string foo. In Python you can't, because you couldn't tell if it's foo the string, or a reference to an existing (or non-existing) foo variable.
That said, in JS, not only you're limited to using (real or coerced) strings as keys to Objects, but you also need to use the square bracket:
{[foo]: "bar"}
syntax, so it can tell that you need it to use the value of the variable foo as the key (else it will understand {"foo": "bar"}).
> a big part of my frustration comes from having a JavaScript background
Yeah, I thought so when I was reading the list of "problems". In fact, many of those are features (e.g. lists & tuples being different, list comprehensions, lazy evaluation, distinction between maps/dicts and objects, ...) but the author doesn't actually understand them. I hate to sound so negative, I guess I just didn't realize how bad a programmer knowing (only/mainly) JavaScript causes you to be.
Complaining about things you don't understand is pretty bad practice. The parent poster was pointing that out. Sure, the Javascript dig is unnecessary, but the language currently holds a position as the spot of first language many junior engineers employ.
Most popular programming languages got popular by aiming for the LCD in some capacity, e.g. Java. It's the reason why we're not all writing LISP or Haskell.
I don't think that's the main reason why and is probably a small factor at best.
Java rode a couple of different trends at the same time: it provided a somewhat smooth upgrade path from C++, cross-platform compatibility, and an astronomical marketing budget.
Many languages in the top-10 that we believe are popular or mainstream got there by having one of platform lock-in, smooth upgrade from a prior language, or a killer, exclusive app. It's a lot less about what is good or objectively better and more about being in the right place at the right time.
I've heard some claim that Python got there by being slow and steady. At one point I think I would have agreed but it does have a killer app and that's in the analysis, scientific and machine learning communities.
> I guess I just didn't realize how bad a programmer knowing (only/mainly) JavaScript causes you to be.
Hey now - not all of us ECMA devs are total crap, but, I guess I know a fair bit of other languages. This guy is just totally ... I have words for it but I'd rather not get the mods on me.
I struggled with an appropriate way to say this but I think he's super pretentious and just sounds frustrated with learning a new language. I mean, we've all been there but typically we don't post ignorant BS to HN/GitHub about it... Every time I jump into a new language I'm always stubbing my toes on things I find "awkward", only to learn later that there's usually a reason people design languages certain ways.
If he wants to shoot his social media reputation in the foot as a developer then so be it I guess =/
I struggle with learning new languages from time to time and there's always been a learning curve from starting out to being proficient.
But while Python itself I think provides an awesome developer experience, django seems less than awesome, and it's taken a while for me to feel proficient in Django.
Personally, I'm not a huge Django fan, but then again lots of people aren't huge Laravel/Symfony fans which is my web framework of choice if I'm building a monolith web app/API "software bus" sorta solution without roping in Java.
That all said, I do love me some Python though! I use it as a general purpose tool to get semi-complex things done that are more scripting/job oriented vs. needing the orchestration that Django brings to the table. You could say it's a "cog" for me vs. the overall machine/engine.
It's on my TODO list to get into some other Python framework libraries because I've seen lots of promising ones pop up since Django, if anyone has suggestions of what to poke my nose into I'm all ears!
There's some annoyances that have accumulated in python, like the inheritance patterns and too many underscored things. Decorators are kinda weird, too.
I think Python is pretty awesome, but to pretend that it's super clean is a bit of a stretch.
(a,) for a single-elem tuple-- avoiding colliding with (a) as a paren'd expression-- is wonky, too.
All the brackets are currently spoken for:
[] = list
{} = set / dict
<, > = less / greater than
I suppose you could overload <, > but I'm not sure that's much better.
Honestly, the more I've learned different languages the more I've come to the conclusion that little syntax changes like this are minor, for the most part, and what really changes the power and feel of a language are the abstractions that it allows for.
<> avoids syntactic ambiguity just fine-- There won't be an identifier or constant before it. <1,2,3> <1> and <> are all plausible-looking and you don't need to guess how to do 1 element or 0 element things if you know <1,2,3>.
> Honestly, the more I've learned different languages the more I've come to the conclusion that little syntax changes like this are minor, for the most part,
Sure, it's minor. But it's one of the few things I tripped over learning python and had to google repeatedly to get to stick in my head.
The fact that python has relatively few cases of weirdness like this-- compared to say, perl-- is a big part of what makes python nice.
The tuple notation is one of those places where the obvious way is a distraction. A non-empty tuple doesn't need the ()s - it's the "," which makes the tuple! The following two are equivalent:
x = 1,
x = (1,)
Of course, then there's no way to express the empty tuple using commas, which is where () comes in.
Using (a,) is the belts-and-suspenders way of saying "this is a 1 element tuple".
These all just seem related to syntax, not actual program structure or capabilities. The only program structure thing I see is list comprehensions, which is one of Python's great strengths. Ironically they say Ruby is more pleasant to write, when Ruby has the deepest structural flaws of any dynamic programming language.
Well in an industry where people are often “Code should be written to make sense for humans first, machines second.“ syntax concerns seem pretty valid.
As a Python coder going back to 2008, I can see the point the author makes in isolation.
I disagree with the title though. Even though I mostly write Rust, Go, and C anymore, I am fine with digging into Python as needed. I cringe at thoughts of using Ruby or JS
I’ve always been super happy using Ruby. Never had any experiences that would cause me to label it in the way that you have. However, I am interested to know what those flaws are. Could you elaborate?
Disclaimer: I do ruby for my day job, and python for fun
Disclaimer 2: I don't hate ruby, despite all these criticisms. If I had to pick a worst dynamic language it would be PHP.
Things I don't like about ruby:
- two string types, symbol and string, with string being the mutable by default one.
This means given a random thing back from an api, you don't know whether to do thing[:id], thing["id"], or thing.id
This has been acknowledged as a pain point by Matz, which is why in ruby 3 strings will be immutable by default.
- Simultaneously too many names for things and not enough.
Is it .length or .size or .capacity (probably not capacity)? is_a?, kind_of?, or instance_of?? Why can I do .select or .keep_if but not .filter?
- Too many function types.
Do you want a method, a block, a proc, or a lambda? There are subtle differences between each, so choose wisely. I'll note that python suffers from this too (method, function, lambda, comprehension).
- Too much emphasis on magic
Novice rubyists get frequently bitten by all the advanced (and very hard to google) ruby concepts. How do you know what arr.map(&:id) is without already knowing that it's calling symbol.to_proc? How about $1 $? $! (if you know what all these do, you're a better rubyist than I am).
Since the author of the referenced post criticizes django for being too magical, try rails. In addition to the names of files mattering a ton, there's the routes DSL, the migrations DSL (which is not well specified in the guides), and ActiveSupport, which you only realize you're using when it's gone.
- Horrible error messages
undefined method :[] for nil:NilClass (when you try to get something out of a hash and it's nil)
cannot convert Symbol into Integer (this is on an array being returned where a hash is expected)
Also, if you get a stack trace, it's completely inscrutable. Python's stack traces (at least ipython's) show you both the line number and the line in question (often with context).
It doesn't matter about its quirks. The most important thing nowadays is that it's reached critical mass. We're at the point where even non-developers can now cobble together a bit of python code to do simple things to make their jobs easier. This means it's here forever, IMO.
I just made a note. I do not really give a flying hoot to what happens to Python. I only use it in places I do some consulting since they require it. I do not use it for my own products.
I'm not disagreeing with this, but you'd be surprised just how much VB still exists in the wild. Literally billions of dollars in revenues in financial service industry alone. Some trading desks use VB and Excel for the majority of trading. It's an abomination of epic proportions and well beyond critical mass, but it still exists precisely because of the reasons you mention - non-developers with some technical competency depending on it.
Whole other debate on benefits and drawbacks if this sort if thing however..
That's not what the article is about. And most interpreted languages (bash, ruby, perl, JS, ...) are not implemented in themselves, because it makes no sense (who interprets the interpreter?).
However, Pypy is a Python interpreter that is written in (a compiled subset of) Python 2.
Sure Python has many problems, but these aren't a great selection.
> The syntax for classical inheritance. Half of each Django app is super().__init__(args, *kwargs). At least you don't have to pass arguments to super anymore.
> Too many magic __double-underscore__ methods and properties that you have to just memorize.
Better to just start using http://attrs.org or dataclasses so you don't have to do all the manual work. Classes without boilerplate.
> Too many top-level built-in functions that (a) you have to just memorize, and (b) get really ugly. You end up with stuff like list(map(...)). I haven't used so many nested parentheses since my early days in PHP. Guido's explanation makes sense in theory, but is really annoying in practice.
> Too many other weirdo bits of magic syntax, like [list comprehensions].
List comprehensions are good, they just take 5 minutes of getting used to.
> Django specifically is so full of magic words, and its documentation is so convoluted, that I've basically given up on documentation altogether and just look at the Django source code now.
> Needing to put dict property names `{'in': 'quotes'}.
Or use `dict(foo=5)`. Python mappings can contain non-string keys, so `{5: 1.2, 6: 3.4}` maps ints to floats.
> You have to cast your data back to a list/tuple after using enumerate() and map().
Don't use map, use comprehensions.
> Different syntaxes for lists and tuples.
They are different objects, why would they have the same syntax?
> foo['bar'] returns a KeyError, so you have to do foo.get('bar')... or in some cases getattr(foo, 'bar', None), but not in others because getattr and .get are different things.
Python is a different language from Javascript.
> You can't just tack on flags to /regular_expressions/ig.
Yeah that's annoying.
> All the goofy string literals: f' ', u' ', r' ', etc.
Sure Python has many problems, but these aren't a great selection.
Yes. I have my disagreements with Python, but those are not it.
The article author is mostly complaining about ways Python differs from Javascript.
- Agree that the mechanism for talking about parent classes wasn't very good. Multiple inheritance usually adds complication without adding much necessary functionality. That's not just a Python problem. It's a leftover from viewing objects through an "A is-a B" lens, one of the dead ends of early AI.
- Python has too much gratuitous dynamism. Any thread can find and mess with any code and data in another thread. The implementation has to support that, which knocks out many valuable optimizations. The language model, and the original implementation, use "everything is a dict", which implies "slow".
- One consequence of the above is Python's terrible one thread at a time thread system, with the "global interpreter lock". The "multiprocessing" hack to get around that is ugly and uses too much memory, since each subprocess has its very own Python system instance. To some extent, the "async" add on is yet another hack to get around the limits of threading.
- Another consequence is a tendency to call C code where Python performance is terrible. The C code has to carefully obey the rules of the Python system. Mostly it does.
- Optional typing is a marginal idea, but unchecked marginal typing is just weird. Language design seems to be converging on implicit static typing - result variables are automatically typed whenever possible. Go, Rust, and now C++ (with "auto") took that route.
- And, of course, the botched Python 2 to 3 transition set Python back for a decade.
On the other hand, Python exceptions work out well. A reasonably sane exception hierarchy helps. Although the one for 2.x was better than the one for 3.x; the 2.x one made a clear distinction between external problems ("Environment errors") and internal problems.
The "with" clause system plays well with exceptions, and nested exception failures unwind correctly.
It's far better than Go's "defer". C++ and Rust try to handle this sort of thing with RAII, which never handles trouble in a destructor well.
I wish more languages did something like the D feature where method syntax and function syntax are really just syntax - i.e., str.len() is equivalent to len(str), and you use the syntax that best improves readability at your call site.
Called uniform function call syntax, an I idea I first saw in Nim and found really cool, but then found really dumb. Can't remember the reasoning for both opinions, which is a strong indicator of "probably doesn't matter".
For a while I was entertaining a thought regarding mutability and returning copy's. Like foo.bar() would mutate, bar(foo) return a modified copy. Don't know if that concept has a name or if it actually makes sense at all.
Something not mentioned in list that I had never considered until a conversation last year: meaningful whitespace is biased towards sighted programmers. According to a blind acquaintance, it is much easier to keep track of opening and closing parentheses and braces. Meaningful whitespace forces blind programmers to always check the number of tabs at the beginning of each line to make sure scope hasn't changed.
Interesting aspect. Kind of sad that our tooling is typically too simple to map something like this: Having cues for indentation changing would be easy, but it's a niche feature and thus probably won't be there.
The article has a silly premise - I don't know anybody who would claim it's a great programming language. For the moment, it's just the most practical in certain areas (notably data science and machine learning.)
Python's power comes from the fantastic array of libraries to which it provides access. Much of my Python coding involves stringing together calls to SciPy, Matplotlib, lxml, and the like. It's a super productive language so long as you don't need multithreaded performance.
I know a company, they have a floor filled with developers who think that Python is the best thing since sliced bread and any other language is abomination
TLDR: if you’re a new programmer, don’t take this seriously.
This person clearly doesn’t understand programming languages so well. Sorry to make this response an “ad hominem” one, I should revoke the claims. But it’s impossible if they don’t understand basic concepts of programming languages.
For example, complaining that a dictionary key must be place in quotes. It’s not that “the key needs quotes”, but that you’re using “a string” as a key. In fact in Python, you can use any immutable object as a key (tuples, for example). They clearly come from Javascript, Python works differently (and arguably better), and they’re complaining that it doesn’t work the way they’d like it to work.
Then complaining about list comprehensions as a “weirdo” thing. Clearly not understanding the functional paradigm and the beauty of expressions (see Smalltalk for the ultimate example of beauty and expressiveness).
Actually nowadays it's discouraged to use list comprehensions in Haskell. Even though he didn't complain about it, Python has very poor facilities for functional programming overall.
Any hashable object, technically. Especially with custom objects, the two (immutability and hashability) don’t necessarily have to overlap, although it’s often a bad idea to have hashable, mutable objects
the only requirement on a hashable instance is that it implements a proper __hash__ function. The default implementation in CPython returns the address of the memory the object is stored at, so all instances are considered unique, values play no role at all.
> For example, complaining that a dictionary key must be place in quotes. It’s not that “the key needs quotes”, but that you’re using “a string” as a key. In fact in Python, you can use any immutable object as a key (tuples, for example). They clearly come from Javascript, Python works differently (and arguably better), and they’re complaining that it doesn’t work the way they’d like it to work.
Agreed. However, there's a trick to use strings without quotes:
how is that a trick? you still can't access without quotes? the real trick is a Dict class that in the __init__ sets self.__dict__. but why would you do that just to save trying quotes
To be fair it is pretty confusing how Python and JS have two features that look very similar on the surface (dictionaries and objects) but actually work very differently. As someone who uses both languages infrequently, it trips me up.
157 comments
[ 2.9 ms ] story [ 200 ms ] threadHere are a couple of more, from someone who has worked for years in a number of languages:
- few limitations means you can easily make a serious mess, which means you are more dependent on good programmers to avoid the mess.
- lack of typing information isn't "free solo" hard but it certainly makes life a lot less pleasant.
One of the projects I work on has switched to full type hinting along with heavy mypy¹ usage, and it has become an absolute pleasure to work with. Along with hypothesis², I can't recommend mypy enough. It must be said that retrofitting either to an existing project is a lot of work though.
1. http://www.mypy-lang.org/ 2. https://github.com/HypothesisWorks/hypothesis
Years ago the lack of visible types was often flouted as a benefit (i.e. terser code, less boiler plate, easier for learners to understand etc).
I eagerly await the addition of optional "scope hints" of { and } ! ;-)
Sarcasm aside, now that there are type hints and python programmers need to type as much as Java/golang/c# programmers, why not just write in those languages?
Genuine question - what are the benefits of starting something new in python (assuming all things being equal - i.e. equal knowledge in java/golang/c# and no legacy reasons forcing you)? Why would anyone pick writing type-hinted python Vs a fully explicitly typed compiled language?
Scope hinting is simply called braces; `from __future__ import braces`.
> Genuine question ...
IMO assuming all things are equal seems to miss how things actually are. The pluses still fall on Python some times, and other languages at other times. Things do vary; developer availability, library availability, target system support, certification story, tooling, even curbside appeal can be legitimate on some occasions.
I'm trying not to pick on your individual language examples, as firing digs seems to be missing the point we're discussing. Or perhaps it is the very point, as two of them wouldn't have even been in my list.
I will say that I do like the act of writing a pro and con list for a language or tool though(which is why I read the linked doc), and I often push co-workers to do so when they're suggesting things. Sometimes it shakes a bad choice out just with the simple act of jotting down a 5-point list.
Unless, of course, there actually is an ideal language out there. In which case I retract my point ;)
And then there’s Bython... https://github.com/mathialo/bython
So you end up having the same obligation you call out as a complaint regardless?
I used an IDE with visible white space on to help with Pythons requirement?
Generators should be treatable as lazy lists in the end, and lists should have a common interface whether they are lazy or eager. Someone should figure out a way to have us write code at a higher abstraction level and have same interface for lazy vs eager data structures.
...but it's not gonna happen in a dynamic language like Python. And I can't say I like the "solution" of having the entire language be lazy like Haskell either :|
We're stuck with "casting generators" for now, I guess, but it really does suck!
With that said...
> In theory a "sufficiently smart language" should have a feature to understand that `thing[3]` can be translated to "call `next()` 3 times
This might be a newbie trap, because next() isn't the same as indexing. What happens if I perform `thing[7]` followed by `thing[5]`? Should performing `thing[7]` put 1-6 in memory and turn the object into a generator-list hybrid?
You're right here. Probably can't work like that since generators are too general, you can't expect them to be rewindable or to not have side effects... prob you'd need a more specialized concept like a "lazy list" that would be a subtype of generator with some extra restrictions that would make it possible to implement the "hybrid" structure as an implementation detail without changing semantics.
Anyway... it would be too much work and probably would turn into a footgun.
They seem to exist only to do lots of stuff in one single line of code. You end up with totally impenetrable unreadable perl-esq garbage write-once-read-never code that is too clever for its own good. And people say python is easy to learn and good for beginners...!
A better approach would be something like Java Streams/.net Lync/RxX pattern IMO. Explicit, clear, no magic, logical.
g = [i for i in list if x == 3] -> g = []; for i in list: if x == 3: g.append(i);
How could that be improved? That's 3-4 LOC minimum in any other language
My main grip is python's ternary operators, since the True value is evaluated before the condition, if you are doing ternaries on things that might throw exceptions the False value has to come first
value = 0 if key not in dic else dic[key] * 5
rather than (throws indexerror if key isn't in dic)
value = dic[key] * 5 if key in dic else 0
If I'm understanding the comprehension correctly,
Though, for readability, I'd likely write it as: Legibility is in the eye of the beholder.(for) k, v in dic.items()
which is just
k, v = (<key>, <value>) for every key value pair in the dictionary
I posted due to your claim regarding all other languages necessitating increased verbosity. I should have left it lie, as I didn't intend to promote a language war, just to post a counter example. My apologies.
dic = dic.where((k, v) => k in other_dic and v == "bar").todict()
dic.iter().filter(|k, v| other_dic.contains(k) && v == "bar").collect();
it's one line, though I'd format it as 3 for readability (list comprehension is hard to read and functional style composes better.
https://news.ycombinator.com/newsguidelines.html
For example, instead of putting someone's article down as sophomoric, you could explain what's different and possibly better about Haskell list comprehensions.
All these politeness comments are a speed bump that distracts from the real issues.
I used to think similarly about this to what you express, because I've always enjoyed reading about the sort of discourse in which devastating wit is exchanged. But eventually I realized that it doesn't translate into this context at all. This is a case of 'the medium is the message'. When you have millions of people who don't know each other all potentially interacting at the same time, the dynamics are so different as to be incommensurable with, say, small debates, literary journals, elite social events, and other places where the groups are small and highly cohesive. Here are some previous explanations about this:
https://hn.algolia.com/?dateRange=all&page=0&prefix=true&sor...
https://news.ycombinator.com/item?id=15378909
https://news.ycombinator.com/item?id=9378899
https://news.ycombinator.com/item?id=7906377
https://news.ycombinator.com/item?id=7742471
> Needing to put dict property names `{'in': 'quotes'}.
Now that's a part I like.
Does the author not realize that you can use (almost) anything as a dict key?
You can do that in JS too, it's just coerced to string, so you can do e.g.
where foo is not a variable, but assumed to be the string foo. In Python you can't, because you couldn't tell if it's foo the string, or a reference to an existing (or non-existing) foo variable.That said, in JS, not only you're limited to using (real or coerced) strings as keys to Objects, but you also need to use the square bracket:
syntax, so it can tell that you need it to use the value of the variable foo as the key (else it will understand {"foo": "bar"}).Yeah, I thought so when I was reading the list of "problems". In fact, many of those are features (e.g. lists & tuples being different, list comprehensions, lazy evaluation, distinction between maps/dicts and objects, ...) but the author doesn't actually understand them. I hate to sound so negative, I guess I just didn't realize how bad a programmer knowing (only/mainly) JavaScript causes you to be.
Either way, if the number of idiosyncracies in your language is confusing to junior developers it's absolutely something worth considering.
In python, I can have the
if __name__=='__main__': main()
at the end if a script, and easily import the script's functions into
- pytest for testing
- some other module for larger modules
- into a cli.py file to slap a Click interface on it.
What a great blend of flexibility while retaining coherence.
Why? Aiming for the lowest common denominators and the simplest systems tends to not create scalable long term solutions.
You also seem to really undercount what a properly trained junior developer is capable of understanding.
Java rode a couple of different trends at the same time: it provided a somewhat smooth upgrade path from C++, cross-platform compatibility, and an astronomical marketing budget.
Many languages in the top-10 that we believe are popular or mainstream got there by having one of platform lock-in, smooth upgrade from a prior language, or a killer, exclusive app. It's a lot less about what is good or objectively better and more about being in the right place at the right time.
I've heard some claim that Python got there by being slow and steady. At one point I think I would have agreed but it does have a killer app and that's in the analysis, scientific and machine learning communities.
> Too many other weirdo bits of magic syntax, like [list comprehensions].
I sadly have to agree with you.
Hey now - not all of us ECMA devs are total crap, but, I guess I know a fair bit of other languages. This guy is just totally ... I have words for it but I'd rather not get the mods on me.
I struggled with an appropriate way to say this but I think he's super pretentious and just sounds frustrated with learning a new language. I mean, we've all been there but typically we don't post ignorant BS to HN/GitHub about it... Every time I jump into a new language I'm always stubbing my toes on things I find "awkward", only to learn later that there's usually a reason people design languages certain ways.
If he wants to shoot his social media reputation in the foot as a developer then so be it I guess =/
But while Python itself I think provides an awesome developer experience, django seems less than awesome, and it's taken a while for me to feel proficient in Django.
That all said, I do love me some Python though! I use it as a general purpose tool to get semi-complex things done that are more scripting/job oriented vs. needing the orchestration that Django brings to the table. You could say it's a "cog" for me vs. the overall machine/engine.
It's on my TODO list to get into some other Python framework libraries because I've seen lots of promising ones pop up since Django, if anyone has suggestions of what to poke my nose into I'm all ears!
I think Python is pretty awesome, but to pretend that it's super clean is a bit of a stretch.
(a,) for a single-elem tuple-- avoiding colliding with (a) as a paren'd expression-- is wonky, too.
All the brackets are currently spoken for: [] = list {} = set / dict <, > = less / greater than
I suppose you could overload <, > but I'm not sure that's much better.
Honestly, the more I've learned different languages the more I've come to the conclusion that little syntax changes like this are minor, for the most part, and what really changes the power and feel of a language are the abstractions that it allows for.
> Honestly, the more I've learned different languages the more I've come to the conclusion that little syntax changes like this are minor, for the most part,
Sure, it's minor. But it's one of the few things I tripped over learning python and had to google repeatedly to get to stick in my head.
The fact that python has relatively few cases of weirdness like this-- compared to say, perl-- is a big part of what makes python nice.
Using (a,) is the belts-and-suspenders way of saying "this is a 1 element tuple".
As a Python coder going back to 2008, I can see the point the author makes in isolation.
I disagree with the title though. Even though I mostly write Rust, Go, and C anymore, I am fine with digging into Python as needed. I cringe at thoughts of using Ruby or JS
Things I don't like about ruby: - two string types, symbol and string, with string being the mutable by default one.
This means given a random thing back from an api, you don't know whether to do thing[:id], thing["id"], or thing.id
This has been acknowledged as a pain point by Matz, which is why in ruby 3 strings will be immutable by default.
- Simultaneously too many names for things and not enough.
Is it .length or .size or .capacity (probably not capacity)? is_a?, kind_of?, or instance_of?? Why can I do .select or .keep_if but not .filter?
- Too many function types.
Do you want a method, a block, a proc, or a lambda? There are subtle differences between each, so choose wisely. I'll note that python suffers from this too (method, function, lambda, comprehension).
- Too much emphasis on magic
Novice rubyists get frequently bitten by all the advanced (and very hard to google) ruby concepts. How do you know what arr.map(&:id) is without already knowing that it's calling symbol.to_proc? How about $1 $? $! (if you know what all these do, you're a better rubyist than I am).
Since the author of the referenced post criticizes django for being too magical, try rails. In addition to the names of files mattering a ton, there's the routes DSL, the migrations DSL (which is not well specified in the guides), and ActiveSupport, which you only realize you're using when it's gone.
- Horrible error messages
undefined method :[] for nil:NilClass (when you try to get something out of a hash and it's nil) cannot convert Symbol into Integer (this is on an array being returned where a hash is expected) Also, if you get a stack trace, it's completely inscrutable. Python's stack traces (at least ipython's) show you both the line number and the line in question (often with context).
Whole other debate on benefits and drawbacks if this sort if thing however..
There are two paths to take when one encounters something new; adapt and learn, or reject and mock. This sounds like the latter.
Care to show proof? I don't see this a lot.
The code of the official Django site for example has 8 occurences: https://github.com/django/djangoproject.com/search?q=super+_...
However, Pypy is a Python interpreter that is written in (a compiled subset of) Python 2.
> The syntax for classical inheritance. Half of each Django app is super().__init__(args, *kwargs). At least you don't have to pass arguments to super anymore.
Yes, inheritance is hard to do well, but that's generally true. Much better to prefer composition (https://en.wikipedia.org/wiki/Composition_over_inheritance)
> Too many magic __double-underscore__ methods and properties that you have to just memorize.
Better to just start using http://attrs.org or dataclasses so you don't have to do all the manual work. Classes without boilerplate.
> Too many top-level built-in functions that (a) you have to just memorize, and (b) get really ugly. You end up with stuff like list(map(...)). I haven't used so many nested parentheses since my early days in PHP. Guido's explanation makes sense in theory, but is really annoying in practice.
I agree, there should be a pipeline operator |> like F# has. A similar proposal has been made for JS. https://github.com/tc39/proposal-pipeline-operator
> Too many other weirdo bits of magic syntax, like [list comprehensions].
List comprehensions are good, they just take 5 minutes of getting used to.
> Django specifically is so full of magic words, and its documentation is so convoluted, that I've basically given up on documentation altogether and just look at the Django source code now.
Pyramid has a much more principled design IMHO. https://trypyramid.com/
> Needing to put dict property names `{'in': 'quotes'}.
Or use `dict(foo=5)`. Python mappings can contain non-string keys, so `{5: 1.2, 6: 3.4}` maps ints to floats.
> You have to cast your data back to a list/tuple after using enumerate() and map().
Don't use map, use comprehensions.
> Different syntaxes for lists and tuples.
They are different objects, why would they have the same syntax?
> foo['bar'] returns a KeyError, so you have to do foo.get('bar')... or in some cases getattr(foo, 'bar', None), but not in others because getattr and .get are different things.
Python is a different language from Javascript.
> You can't just tack on flags to /regular_expressions/ig.
Yeah that's annoying.
> All the goofy string literals: f' ', u' ', r' ', etc.
That's a good thing, not a bad thing.
> Pipfile does not work that well.
Poetry works better. https://poetry.eustace.io
Yes. I have my disagreements with Python, but those are not it. The article author is mostly complaining about ways Python differs from Javascript.
- Agree that the mechanism for talking about parent classes wasn't very good. Multiple inheritance usually adds complication without adding much necessary functionality. That's not just a Python problem. It's a leftover from viewing objects through an "A is-a B" lens, one of the dead ends of early AI.
- Python has too much gratuitous dynamism. Any thread can find and mess with any code and data in another thread. The implementation has to support that, which knocks out many valuable optimizations. The language model, and the original implementation, use "everything is a dict", which implies "slow".
- One consequence of the above is Python's terrible one thread at a time thread system, with the "global interpreter lock". The "multiprocessing" hack to get around that is ugly and uses too much memory, since each subprocess has its very own Python system instance. To some extent, the "async" add on is yet another hack to get around the limits of threading.
- Another consequence is a tendency to call C code where Python performance is terrible. The C code has to carefully obey the rules of the Python system. Mostly it does.
- Optional typing is a marginal idea, but unchecked marginal typing is just weird. Language design seems to be converging on implicit static typing - result variables are automatically typed whenever possible. Go, Rust, and now C++ (with "auto") took that route.
- And, of course, the botched Python 2 to 3 transition set Python back for a decade.
On the other hand, Python exceptions work out well. A reasonably sane exception hierarchy helps. Although the one for 2.x was better than the one for 3.x; the 2.x one made a clear distinction between external problems ("Environment errors") and internal problems.
The "with" clause system plays well with exceptions, and nested exception failures unwind correctly. It's far better than Go's "defer". C++ and Rust try to handle this sort of thing with RAII, which never handles trouble in a destructor well.
Called uniform function call syntax, an I idea I first saw in Nim and found really cool, but then found really dumb. Can't remember the reasoning for both opinions, which is a strong indicator of "probably doesn't matter".
For a while I was entertaining a thought regarding mutability and returning copy's. Like foo.bar() would mutate, bar(foo) return a modified copy. Don't know if that concept has a name or if it actually makes sense at all.
The article has a silly premise - I don't know anybody who would claim it's a great programming language. For the moment, it's just the most practical in certain areas (notably data science and machine learning.)
This person clearly doesn’t understand programming languages so well. Sorry to make this response an “ad hominem” one, I should revoke the claims. But it’s impossible if they don’t understand basic concepts of programming languages.
For example, complaining that a dictionary key must be place in quotes. It’s not that “the key needs quotes”, but that you’re using “a string” as a key. In fact in Python, you can use any immutable object as a key (tuples, for example). They clearly come from Javascript, Python works differently (and arguably better), and they’re complaining that it doesn’t work the way they’d like it to work.
Then complaining about list comprehensions as a “weirdo” thing. Clearly not understanding the functional paradigm and the beauty of expressions (see Smalltalk for the ultimate example of beauty and expressiveness).
Any hashable object, technically. Especially with custom objects, the two (immutability and hashability) don’t necessarily have to overlap, although it’s often a bad idea to have hashable, mutable objects
Agreed. However, there's a trick to use strings without quotes: