Reasons Python Sucks
hackerfactor.com
hackerfactor.com
1 (versions) and 2 (installation) have to do with the ecosystem, not the language. Unsigned packages are a real problem, but hating on community-maintained software is just weird. Most software in your local Linux distro's repository is maintained by a "community" that might just be one person working in their spare time.
3 (syntax) seems to be about not supporting the author's own highly idiosyncratic habits, which include deep nesting and putting debug code in the first column (ugh). The result actually seems better for maintainability than the author's own unconstrained code would be.
4 (includes) is in the "exactly wrong" category. Includes as in C/C++ are a horrible way to handle module interfaces. Really, just about the worst option available. Modules are far better, and if the author hates the fact that importing a module might run initialization code wait until s/he finds out about constructors (including __init__ and __attribute__((constructor)) even in C).
5 (nomenclature) is also a bit insane. Lists aren't called arrays in Python because they're not arrays. Next!
6 (quirks) criticizes quote handling in Python, but mentions bash's quote handling without a word of comment about how that's even worse. Different languages have different quoting conventions. Python's might not be perfect, but many are far worse. Get over it.
7 (pass by reference) is another totally-wrong one. Passing by value is not the universal norm in other languages, and it's not even clear what it means sometimes because of deep vs. shallow copies. Passing by reference is almost certainly better for efficiency, and often for correctness as well when copies becoming inconsistent with each other is a concern. I'd rather have pass-by-reference as the default, and have to work to make copies, than the other way around.
8 (local names) barely even make sense. Shadowing names, whether of variables or of modules/libraries, is a bad idea anyway. If you want the library and the program not to have the same name, use a prefix for each role. Expecting the language to handle this for you is just asking for trouble.
I hate hate hate language debates, but this isn't even that, it's just a misunderstanding. If this guy took a class on Python he'd figure out like 5 or 6 of these issues no problem!
I regularly wish python required some sort type indication for function parameters, because dealing with libraries that take complex objects as function parameters can be an absolute nightmare.
I wish python had some equivalent to the switch statement that didn't involve workarounds with dictionaries, because sometimes you just want a clean way to deal with a value that can take 7 different forms.
I regularly find myself wanting to write something of the form do_x() if a.y() This is stupid, but I still kind of want it since do_x() if a.y() else do_y() is valid
Edit: Also fuck this whole "it's so cool to shit on Java" thing. Grow up. You aren't in college anymore. You should recognize that Java fills its niche effectively, and does a decent job of being semi-fast, portable, easy to read and maintain, and safe.
You can use type annotations, either via the builtin lib or 3rd party libs!
https://docs.python.org/3/library/typing.html
> do_x() if a.y()
I feel this. You can use `a.y() and do_x()`, but I'm not sure how people would react :o
> https://docs.python.org/3/library/typing.html
I know, but tragically, because I am not forced to, I never do. I do appreciate that feature though. Maybe this will be the impetus I need to start taking advantage of it.
Try an ML-family language if you haven't already - I find they combine the best parts of something like Python and something like Java. There was a post pushing F# earlier today.
If you're happy to write it that way round, why not just use `if a.y(): do_x()`?
It's fine not to follow PEP8 for your own software. It's a useful guide, not a law.
I don't know the history, but it seems like that might be one of the reasons `x() if y()` isn't allowed in python. [x() if y()] if it existed, might carry an explicit else None.
On the other hand [x() and y()] does something different when y() is falsey.
Maybe I misunderstood your context, but if not, conditional expressions do exist in Python:
$ python
Python 2.7.12 |Anaconda 4.2.0 (32-bit)| (default, Jun 29 2016, 11:42:13) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
Anaconda is brought to you by Continuum Analytics.
Please check out: http://continuum.io/thanks and https://anaconda.org
>>> 1 if 1 == 1 else 2
1
>>> 2 if 1 == 1 else 2
2
>>> ^Z
$ py -3
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> 1 if 1 == 1 else 2
1
>>> 2 if 1 == 1 else 2
2
>>>
Using nested conditional expressions to classify characters:https://jugad2.blogspot.com/2017/04/using-nested-conditional...
>>> from __future__ import print_function
>>> for i in range(3, 5):
... print(i, "is", "odd" if i % 2 == 1 else "even")
...
3 is odd
4 is even
which can be shortened to this: >>> for i in range(3, 5):
... print(i, "is", "odd" if i % 2 else "even")
...
3 is odd
4 is even
Now print() is printing 3 values: i, "is", and either "odd" or "even" based on the boolean condition.That works in both Python 2 and 3.
>>> def add(a: int, b: int):
... return a+b
...
>>> add("a", "b")
'ab'
This should be an error, even if it's a runtime error.I believe Julia is an example of a dynamically-typed language which does perform such checks at run-time - including as part of its support for multiple dispatch.
On a general note I've come across a few cases, when pushing python's type annotations to their limits, that force you to put the type names in string quotes, and that makes the whole thing feel like a hack. It's better than nothing for my use cases, but if I remember correctly both mypy and enforce have problems in common that are probably coming from the way python itself is built, such as self reference in a class definition.
With pep-0563, python3.7, and `from __future__ import annotations` this should no longer be necessary. Those are some pretty detailed caveats, but I have been using it in places where I can and it is so nice.
For actual projects you can use a static type checker like mypy.
cough maybe you should be programming in Ruby ;)
do_x if a.y?
Unfortunately (or fortunately, depending on perspectiven- great power and all that), Ruby allows things that Python doesn't and it's not hard to find yourself in a write-once mud bath.I do like Python's modules over how Ruby handles them too. Ruby's import (require) is akin to C/C++, with modules as a separate idea. I prefer the style of importing exactly what you need.
But I know Ruby better so it's my go-to.
Or Perl 6. `do-stuff() if $a.y;` is the Perl6 version. You can even declare your variables to be sigil-less so you end up with `do-stuff() if a.y;` if you want. See my Perl6 Advent Calendar article for a quick tour of the language, available docs, with practical examples focusing on using Perl6's novel features to write a self documenting command line script.
https://perl6advent.wordpress.com/2018/12/16/
I've written code in everything from assembly to ML, Java, and Prolog. Perl6 is the only language I've used that I would "mind-expanding". The more I use it, the I realize how radical its design is.
At the end of the day it's a minor complaint of course.
I've always found Python lambdas to be uncanny, weird and error prone. It's kinda syntactic sugar for expression-only defs with counterintuitive scope rules. I would recommend just not using them and fall back to named functions. It's just one extra line and that way you get to name it, which is always a nice thing.
def x(y): <code goes here>
What's so heavyweight? A new line and a tab?
youngest_person = min(people, key=lambda x: x.age)
def get_age(x):
return x.age
youngest_person = min(people, key=get_age)
This may not seem like a huge difference, but using lambdas scales better.[1] Incidentally, this is the same reason why I don't miss multi-line lambdas (in Python): multi-line functions are seldom trivial.
A new line A tab A return
This does not sound heavyweight to me (in that context). In the context of a single line function I see the utility of lambdas.
You are confusing “first-class functions” and “anonymous functions”, which are completely different things. Python functions are first-class, independent of length.
Some definitions of "first-class function" require that it have the same value semantics as any other first-class type, which would include a literal syntax/anonymous functions.
What would you prefer? C-style syntax? Ruby-style `end end end`?
If that is a keyword, or brackets, or whatever, I would prefer that. You can't minify or easily lint python. And you can't easily tell if there are mixed indent methods (tabs/spaces) and IDEs struggle with it vs. a simple bracket structure.
I am with you there. I have often wished for a C-like switch(). That said, I think the best Python leans towards the functional, so instead of a switch(), it probably would be better to come up with some kind of multi-arm match more in line with modern functional language semantics. That could also be more flexible with respect to the type of the tested expression. Maybe something akin to the Rust match syntax.
> I regularly wish python required some sort type indication for function parameters, because dealing with libraries that take complex objects as function parameters can be an absolute nightmare.
Well, of course Python has type annotations. TBH, I don't use them. I tend to be rather pedantic about Sphinx doc for functions, most especially __init__() constructors. Kind of old-school and manual, but it works -- make doc and read it in your browser. The other thing I do in constructors is 1) make them very tolerant of what gets passed as a parameter, and 2) be very pedantic about raising ValueError in the right place, at the right time, with a clear message.
I have a good friend that I argue with regularly -- now this guy is a compiler front-end guru -- ex-Sun senior staff level C/C++ compiler tech lead, very smart -- but we argue regularly about Python idiom. He litters his Python with extremely un-idiomatic assert(isinstance()) to check incoming parameters. I think C poisoned this man's brain with respect to type checking.
There are two things wrong with his code: 1) It raises the wrong exception, the correct exception is TypeError, not AssertError. 2) It totally breaks __int__, __float__, etc, so I can't implement those on custom classes.
In my constructors, to the extent possible, I do "type checking by re-construction" -- parameters i and p gets run through something like:
self.i = int(i)
self.p = PClass(p)
So if you send me a string for i or something weird that implements __int__(), everything is cool. And PClass.__init__(self, x) is responsible for turning p into an instance of PClass, leaving it alone if it already is a PClass, or raises ValueError. This idiom makes type checking as tight as you want it to be, but doesn't break Python Zen.Aspirationally, anyway.
Meh, never encountered this in 20 years. Typing is helpful in large projects however, and not just with complex objects.
The "it's so cool to shit on Java" thing served an important purpose and is probably close to being retired, but it's easy not to understand if you weren't a Java programmer some time in Java's heyday. There was a culture and a set of assumptions around Java that today you could probably only find in the most stodgy and insular corporate programming environments. I helped introduce Java into a development group and ended up hating the people and practices it infected us with. The hatred is not completely unrelated to the language (which runs on a great platform and is not a horrible language) but it would never have attached itself to the language without some of the pathological culture surrounding it.
I don't know in which world you live in, but in the real world Java is by far the dominant platform for web services.
I read it as meaning 'the "it's so cool to shit on Java" thing' is close to retirement, not the language itself.
* Created in the last 5 years
* Built by an organization without a large Java heritage
I bet that number goes waaaay down, way quick.I always liked C# better than Java though.
As others pointed out, Python has optional type annotations and has had them since 3.0. They're static-only (you don't get runtime type checking from them), though, and getting what you want out of them will effectively require every single function, method and variable in not just your codebase but in every third-party dependency and all of their dependencies to be annotated.
Also, annotating "complex objects" tends to be an unreadable mess. If you have lots of functions which take complex objects as arguments, rather than a more clear list of parameters, that's probably not going to be solved by type annotations; it will be solved by rewriting the functions. In other words, you probably will not be helped by:
def foo(o: HugeLongComplexTypeDefinition) -> OtherHugeLongComplexTypeDefinition
But you would be helped by: def foo(widget: Widget, operation: WidgetOperation, tolerance: float) -> WidgetHey! Let people not like things, guy!
I put my pants on, go to work, pick up my heavy JVM, and crack away in the Java mines for 8 hours.
When I finally get home, tired, broken, achy fingered, and filled with disappointment for only making it to the AbstractAbstractFactory, rather than the AbstractFactoryFactory, I think I've earned a few "Yeah, with other type systems you'd be able to..." style complaints.
Greener pastures and whatnot.
Oh no! You were so close!
this is so hilariously wrong that it is clear that the author has never actually tried any of the things he complains about.
Python 3.7.1 (default, Oct 22 2018, 10:41:28)
[GCC 8.2.1 20180831] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> if True:
... if True:
... pass
... pass
File "<stdin>", line 4
pass
^
IndentationError: unindent does not match any outer indentation level
same error in Python 2.7.15. the caret is at the wrong place, but the error message is perfectly fine on its own.edit: I checked the CPython source, and this message actually dates back to 2000. so unless the author was using python 1.x back in the 90s, or is dumb enough to use single spaces for indentation, the story is a complete fabrication.
Point is, I'd see this problem constantly until I figured it out. And not once did it take more than a few minutes to fix it because, as you point out, it shows you where the problem is. And if you use PyCharm, or VS Code with Python extension, or a raft of any other editors, it'll bark at you long before you try and run the code, with circles and arrows, and a paragraph on the back of each one explaining the problem.
So, yeah, the author is making shit up. And needs to learn about what the Tab key does.
Those kinds of problems can be hard to track down.
print("foo")
# x is false for the problem
if x:
do_something()
print("bar")
And to the parent who has written a lot of code but never had this problem, well, some people do.> never had this problem, well, some people do.
Neophytes also have a lot of problems with matching braces, neither is a panacea to those folks. But one is easier to read and type for everyone else, for years to come.
If the function is longer or their are a couple of additional levels of indentation, and it isn't a print statement, but some additional logic, it is a hard problem to spot.
Still at some point one needs to be responsible for the code they write, braces or not. Braces are not a guaranteed solution either, if the author gives up complete responsibility.
Basically, this is a theoretical non-problem. The lack of braces pays back in readability every single day. Like +100 + -2, then complaining about the -2.
You'll also note that I've never claimed braces were better, I'm just trying to explain OP's pont.
You may not encounter it, but "Hey it works for me" is not a legitimate answer.
In many years of writing Python I have not once encountered this issue. And I didn't always use four spaces...
I agree with the other person that says this doesn't really happen in the real world with a baseline developer and editor.
"It isn't a problem for me, therefore it isn't a problem for anyone." just isn't a good way to reason about problems like this.
That said, I sure with python offered a totally optional end block thing. In many situations it would make things far more clear.
> PyPy, PyPi, NumPy, SciPy, SymPy, PyGtk, Pyglet, PyGame... (Yes, those first two are pronounced the same way, but they do very different things.) I understand that the 'py' is for Python. But couldn't they be consistent about whether it comes first or second?
First, stupid examples. PyPy and PyPi (actually PyPI, with the uppercase) are not pronounced the same, its "pie-pie" and pie-P-I.
Second, on the package names, they're third-party. They could be named pretty much anything. Why does their naming matter in any way? I haven't seen much consistency in any language I've encountered anyway. I'd even say most of the examples he gave are at least pretty self-descriptive, compared to a lot of libs in multiple languages...
Given the entire premise of the post is that he's written a list of things he hates about Python as an answer to his friend's question as to why he hates Python, I'm not sure why you think there's some ambiguity here.
I "hate" python for my own (much more petty) reasons, although the horrible naming conventions struck a cord with me, so I upvoted this article, with the hope that I'd be able to learn something from the discourse it creates.
Are they objectively subjective?
Can you explain the difference? What is a "namespace binding"? When accessing a variable's value, the interpreter's code sure looks like it treats the variable name as the name of a storage location.
> Passing a variable to a function means passing a particular namespace binding
"Passing a variable to a function" is not a thing. Passing a value to a function means passing a pointer to that value. This is the same for "foo(some_var)" and "foo(1)". Since "1" is presumably not a "namespace binding", you seem to suggest that there are different parameter passing mechanisms for these cases. There aren't.
We could call this "passing references to objects by value."
But in any case, that's the point that made me go wtf while reading through as well. Whatever you want to call it, it's the same as what C (when not passing pointers), Java, PHP, Javascript, and many many other languages use.
Which is why it's so frustrating we don't have a good, easily-understood/well-known name for it.
I have no idea why would you mention BASIC here.
An entry in a Python dictionary that is the namespace for the current scope.
For example, to repeat a response I gave to someone else upthread, compare a variable assignment in C and Python.
In C:
int i = 17;
Means "set aside one int's worth of storage and fill it with the bit pattern for the number 17".In Python:
i = 17
means "create an int object with the value 17 and bind it in the current namespace dictionary to the name i".Set aside one pointer's worth of storage on the heap, yes; not on the stack or in the program's data segment, which is what the C statement I gave does.
> and fill it with the bit pattern for the pointer pointing to the value 17 (which you may have to create first)"
Which has no analogue whatever in the C version.
Also, you completely left out the part about creating an entry in the namespace dictionary, which also has no analogue whatever in the C version, and which involves additional memory allocations.
Except for locals. (Well, OK, the Python stack is reified in the heap, but local variables are translated into small integer indices into the stack frame.)
> an entry in the namespace dictionary
Except for locals, which do not involve memory allocations or dictionary lookups.
Anyway, we're getting farther and farther away from the topic, which was that Python argument passing is no different from any other language in which everything is an object.
I meant the C stack, not the Python stack. At the C level all Python objects are allocated on the heap (except for some of the singletons like None which are statically allocated by the interpreter). (The fact that I used the term "heap" indicates that I was talking about the C level, since there is no "heap" at the Python level.)
> Python argument passing is no different from any other language in which everything is an object.
I disagree, since again you have left out the namespace binding step completely.
I don't think that's true, as the function will never be able to change the binding, and only gets a reference to the value of the binding. If the function was passed a particular namespace binding, it would be able to change this binding's value, and that's not possible.
I agree that "pass by reference" is a misleading way to describe Python functions.
More precisely, the function's arguments when it is called are a set of values for variables taken from the caller's namespace, which then get bound to the corresponding names in the local namespace. You're right that "passing the binding" doesn't really describe that process very well.
Again, this is very obviously false, since a call need not involve any variables at all:
foo(1, 2, "three")
The function's arguments when it is called are a set of values taken from the caller, yes. And those values are obtained from evaluating expressions that may involve variables. But they might not. Saying that the values are "a set of values for variables taken from the caller's namespace" is wrong.You are mistaken, and your talk of namespace bindings is just obfuscation. Python arguments are passed by pointer. Python namespaces bind names to pointers. There is no "binding" object passed in a function call.
True, in which case the values would just be constants. But they will still get bound to names in the function's local namespace.
> You are mistaken
No, I left out a case which, it seemed to me, did not affect the main point I was making. If you want to make clear that that's a possible case, fine, you've done so. But you haven't refuted (or even engaged with) my main point at all.
> your talk of namespace bindings is just obfuscation
No, it's a very important difference between what Python variables mean and what variables in language like C mean. You might think the difference is unimportant, but not everyone agrees with you.
> Python arguments are passed by pointer. Python namespaces bind names to pointers.
At the C level inside the interpreter, yes, this is true: every "object", such as the 1, 2, and "three" in your example, is a pointer to a C struct containing the object's data (sometimes including further pointers). Again, that doesn't affect my main point at all.
> There is no "binding" object passed in a function call.
I already agreed to this in my response to pstch upthread (the post of mine you originally replied to). Once more, it doesn't affect my main point at all.
OK. Your main point (now that you have shifted the goalposts) seems to be that argument passing induces a binding of the parameter name to the argument value, yes?
In the abstract, this point is meaningless since it applies equally to every other programming language. In this C function:
void foo(int x, float y, char *z) { ... }
the C compiler also has to manage bindings from variable names to the locations where their values are stored. It needs that information to find the correct values in registers or (just like in Python) on the stack.In the concrete, the point is also meaningless since those bindings do not involve dictionaries as you seem to think. For positional functional arguments, loooong before the call takes place, the bytecode compiler resolves names to stack indices, and the variables are accessed through those. Just like C can access arguments passed on the stack using constant offsets from the stack pointer.
Here is the code for the normal call fast path: https://github.com/python/cpython/blob/62be74290aca26d16f3f5...
f = _PyFrame_New_NoTrack(tstate, co, globals, NULL);
if (f == NULL) {
return NULL;
}
fastlocals = f->f_localsplus;
for (i = 0; i < nargs; i++) {
Py_INCREF(*args);
fastlocals[i] = *args++;
}
result = PyEval_EvalFrameEx(f,0);
This sets up a stack frame for the callee (on the heap, yes), then copies the arguments (which are pointers to PyObject) into slots in that stack frame numbered consecutively from 0.Access to these arguments inside the callee is via the LOAD_FAST bytecode instruction: https://github.com/python/cpython/blob/master/Python/ceval.c...
case TARGET(LOAD_FAST): {
PyObject *value = GETLOCAL(oparg);
...
which uses the GETLOCAL macro: https://github.com/python/cpython/blob/master/Python/ceval.c... #define GETLOCAL(i) (fastlocals[i])
Nowhere are name-value bindings allocated dynamically in this normal case. Python does perform dictionary manipulation for keyword args, but not for positional ones. For normal positional arguments, the mechanism is exactly equivalent to a C (or whatever) compiler pushing arguments onto the stack. Local variables are exactly what you claimed that they were not, namely names for storage locations (stack slots).I'm done with this thread now.
It's that there is an extra step involved that does not occur in languages like C.
> those bindings do not involve dictionaries as you seem to think
For positional arguments, you are correct. But, as you note, there is still an extra memory allocation on the heap, because the stack at the Python level uses heap memory, not stack memory, at the C level.
For keyword arguments, as you agree, there is a dictionary entry created.
> Local variables are exactly what you claimed they were not
More precisely, local variables inside a function that correspond to positional arguments are, for performance reasons, stored in an array of function local pointers at the C level, instead of being stored as dictionary entries (namespace bindings).
But the fact that this is a particular exception to Python's normal treatment of variables simply highlights the point I was making.
This argument really summarizes a beautiful and dangerous thing we see in the tech community far too often; you have a strong technical and scientific understanding of the system, but lack product and design thinking.
You're right. Its a problem with the ecosystem, not the language. I'm still not going to use python because of the ecosystem. If the language cares about its users, it needs to care about the Product, which includes the ecosystem. Users interact with the Product, not just the Language.
That's something recent languages like Go, Typescript, Rust, and even old languages like Java, understood quite well. Its not just about the syntax and the compiler; getting users into your language means caring about the IDE, caring about the development experience, the package management, the libraries, basically everything that a developer will touch. That doesn't mean you have to touch all that stuff as the core development team, but it does mean you need a Strategy and a Message.
The real problem here isn't that Python was used to mean the language alone when it should have been used to mean the entire ecosystem, or vice versa. The problem is that it's ambiguous. I didn't mention that points 1 or 2 are about the ecosystem to invalidate them. There are stronger refutations available. I was just trying to identify a scope that the author had left ambiguous.
What people decry about “the Python ecosystem” is simply a function of its success as a generalist computing language over 25 years. Nobody uses Typescript to build Linux distributions; nobody uses Go to build lib-gluing GUI apps; nobody uses Rust to write spreadsheet formulas. Python does all that (and more), and this of course resulted in a sprawling community, with tools pulled every other way. If any of your new shiny languages will ever achieve a similar level of popularity, you can bet their ecosystem will also become a complete mess.
However, there are obvious costs in terms of context-switching, learning curve, career flexibility, and so on, which is why general-purpose languages are so popular.
It evolves constantly. Python packaging then and now has massively changed (largely without breaking compatibility, mind you). It's not perfect, but it is much better than it was in 2005.
I know pythons big thing is backwards compatibility, and breaking things is almost the biggest sin in that community (python2...), and maybe I’m ignorant, but I just don’t see how implementing a single standard package manager with a standard manifest and lockfile would break anybodies anything. All they have to do is make it so pip still works without it, and if they want to see how to do that, just look at NPM, who before this year was notorious for errant package versions due to the lack of lockfiles, but now they have it and I think pretty much everyone was happy about it. They literally could just port bundler to Python and call it a day, the code is open source, they can look at how it works, and it’s one of the most well regarded package managers out there, I’ve maybe run into 2 issues ever with it. The only other package manager I see get more praise is cargo, which they could (should) also look at for inspiration.
I know things have been improving. When I was using Python daily, the standard was still punching in some incantations to start of Virtual Env (which still confuses the hell out of me to this day) and piping the contents of a requirements.txt file into pip (with I think at least some level of version locking). But, from what I’ve gathered is there are some solutions being built to bring pip out of the early 2000s, but they’re quite fragmented efforts, and they all fall short in different places.
Python is a great language, pip and all the bullshit that comes with it is terrible.
Now that I’ve wrote that out, I’m wondering if packages can even pin their deps required versions. I’ve only published one python package years ago, and I can’t remember if that’s possible. Which I shouldn’t have to even worry about in a modern package management system.
I know the above example wouldn’t be possible in any of the systems I mentioned above, because the aside from installing packages, they ensure that the versions of every package are compatible. That’s why lockfiles are so important, assuming you’re downloading decent packages, the package manager can assure you that they’re all going to work together, also allowing you to update without having to worry about some random deeply nested dep isn’t going to break some other package when it gets updated, making updating (usually) a breeze.
In pips current state, it seems more or less like a slightly more strict NPM before yarn made them get their shit together (still vastly prefer yarn). The only difference is your packages are wherever Venv puts them instead in a “pip_modules” folder in the project, which is at least better than a global free-for-all. Though, that also might be less of a headache than dealing with Venv, I hope it’s gotten better since I’ve used it, because that was always such an annoyance.
> Now that I’ve wrote that out, I’m wondering if packages can even pin their deps required versions. I’ve only published one python package years ago, and I can’t remember if that’s possible.
It is.
Today, if you are interested in a reproducible build environment, you will use pyenv to locally install an interpreter and stdlib, and pipenv to locally (to your project) install libraries and dependencies.
Rust is doing some interesting things with Rust 2018 Edition, where there is a complete system harmonization point. If it takes off there, I'd expect other languages to pick it up.
My question though is a bit different: Can Python catch up?
I know - strange question, but momentum is like that. It's often hard to see something going faster than you until it passes you. Perl is probably the best example, where it was way out front, and the PHP, which was way behind, but going mach 10, passed it quickly in the web space. Perl never caught up, and has lost significant relevance compared to the stature it used to command.
Consider; the reason for installing dependencies locally to your project is that it allows you to be precise about what library version you want. For example in Rust, we have Cargo.toml and I can have dependencies like primal = "0.2". I hate this model. It takes us right back to the old days of projects shipping whatever version of a library they want and never updating it. For small projects this is a constant source of security issues and code stagnation.
I groan internally every time I see a project I'm interested in is written in node. Not because I dislike the language, but because I know when I download and build it there are going to be a dozen outdated dependencies hardcoded in the config file, some with "severe" security warnings flagged by npm.
I advocate letting library developers and distribution maintainers do their jobs. Most of the time new versions of libraries are supposed to be backwards compatible. When they're not the distribution can ship multiple versions of a library and make sure they all have security updates backported.
There are cases where managing all your own dependencies is important (like closed source web applications), but I think those are exceptions. Dependencies were supposed to be a solved problem.
Seems to be active up until around July-August
That's because Python is old. It's as old as Haskell. It's older than Java and JavaScript. It's older than the Borland Delphi and C++Builder IDEs. When Python was released, C++ was only 5 years old. Python is older than Linux. Rust and Go are both older than C++ was when Python was released. Hell, when Python was released, Perl was only 3 years old.
Further, Python's age doesn't mean that we can't or shouldn't criticize it. You shouldn't get a free pass on what you could do better just because you've done other things right. We should absolutely point to what doesn't work well and lobby for improvements even if they require significant overhauls. While, yes, Guido and company have worked very hard on developer experience out of the box, it would be really nice if someone would care about system administration and the long tail, too. It's great that your engineers love your tools, but someone still has to live in the homes that they build.
It's not like anybody here is shocked or confused when they hear people complain about Python versioning being a rats nest. I'd wager we've all experienced it at least once just like we used to have problems with multiple concurrent versions of the Java Runtime Environment being installed or other fun forms of dependency hell. If virtualenv or conda are such good solutions, why do so few projects seem to use them or deploy with them? If popularity or ubiquity are the measure of what's good in the ecosystem, doesn't that suggest that there's still a problem to be solved? A language should direct programmers to styles of system design that makes system management easy and clear, and Python does not do that at all. Is there a problem with complexity? Is there a problem with lack of education?
I don't disagree, but for people paying attention, it's become really boring. "I've just switched from $language to Python [because work told me to], and this is what I hate" is almost a parody, at this point. It's a sign the ecosystem has reached ungodly proportions.
> If virtualenv or conda are such good solutions, why do so few projects seem to use them or deploy with them?
I don't know about conda, but venv/virtualenv was the deployment standard before Docker happened (I'd argue it still is, for people who won't/can't use containers). Personally, I still like to use venvs even in docker.
> If popularity or ubiquity are the measure
How do you measure popularity? So often the pip hate seems to come from a very loud minority. Meanwhile, the ecosystem continues to expand and people keep using pip/venv without any problem, because it works for most cases (now that wheels have fixed the "can't build on Windows" issue) and it's simple enough. Somebody upthread compares pip with Maven, and it makes me laugh: the Java ecosystem is shrinking and the Python one is exploding, and stuff like the user experience of Maven vs pip is among the reasons.
> there's still a problem to be solved
Of course there is, there is always one; I'm sure that you'll find Cargo critics too, once that community grows enough, and there are plenty of loud NPM haters. Trying to be all things to everyone takes careful reasoning and herding - because it's a political problem as well as a technical one, in a polis that keeps growing. Shouting PIP SUCKS!!111!! is only going to result in more crap like pipenv. The shortcomings of pip have well-known for a while, but the solution is not trivial, as pipenv demonstrated.
> A language should direct programmers to styles of system design that makes system management easy and clear
That's like, your opinion, man. One of the strengths of Python is that it's just as structured as you want it to be, and no more - even when that might not satisfy someone's arbitrary definition of a Platonic application.
Further, if you factor in the growth of languages like Kotlin and Scala the JVM is blowing Python out of the water.
The Java module experience is miles ahead of the Python one. There's no global module path where you can install different things on different machines, nothing that screws up if you accidentally run it as root, no stateful virtualenvs where different shell windows run the same project with different versions of python. You just list your dependencies in your POM (which, yes, is XML; it was the early 2000s, everyone was doing it, and it does make it easy for your IDE to put a nice editor interface on it) and that's it, you're done: released versions are immutable and transient dependency resolution is deterministic, so you don't have to worry about pinning/unpinning or anything like that. There's a well-known repository that all the important libraries are in; as and when you want to have a company private repository there are a couple of standard ones you can pick from and run, either just for your own artifacts or proxying third-party libraries from the central one as well. It all Just Works.
Maven came out 14 years ago and Python still doesn't have anything that works nearly as well.
You are seriously comparing Maven, a huge and over-complicated xml-based system that is not even installed by default and that people hate so much that there are umpteen alternatives (gradle etc), with `pip install -r requirements.txt` that works out of the box? I just can't even...
> Python still doesn't have anything [like Maven]
And I thank the Gods for that.
A post on my blog:
pip now installed with Python 2.7.9:
https://jugad2.blogspot.com/2015/03/pip-now-installed-with-p...
which mentions:
https://docs.python.org/2.7//installing/index.html
and::
https://pip.pypa.io/en/latest/installing/#pip-included-with-...
I've used Python versions before that, where you had to install pip separately, and it was not trivial (to find it / install it), although not very difficult either.
Works out of the box until you're missing a distro package that is required to build a dependency that needs to be compiled from source.
I've never used maven so I don't know if it is better or worse, but I am not a fan of languages having their own package management system that has not integration with the distro one (which probably also offers some of the same packages, and mixing them breaks things in subtle, annoying ways).
If a package owner distributes a wheel, you're good. Most packages do now.
It's about the same for packages with a native component. Local build tools are still needed.
Java stuff is slightly less likely to have a compiled component in the first place, in my entirely subjective impression. Maybe that's because of convention, or performance; I don't know. But when a compiled dependency does exist, and nobody included a prebuilt chunk of binary for your architecture in the jar, a build is needed in roughly an equivalent fashion to what pip (or npm, cpan(1), etc.) do.
https://en.wikipedia.org/wiki/Lightweight_Java_Game_Library
Whereas pyglet, seems to me like a high-level regular game engine that includes a widget library.
With decently-maintained packages on PyPI, i.e. shipping prebuilt wheels for the most common architectures, that's no longer a problem - unless you insist in using the distro package-manager, in which case you should pick it up with the distro people. If you stick to pip+venv, on most common architectures, these days chances are you only need a simple `install`.
> I am not a fan of languages having their own package management system that has not integration with the distro one
You must feel miserable then, considering it is pretty much all modern ones. Go, Rust, Node, Perl, Python, PHP, Java, even C# and friends...
There will always be tension between what developers want (the library released yesterday and can be forever tweaked) and what OS/sysadmins want (the library that has been tested for months and can be locked down). This is why platform-agnostic delivery systems for developers are so popular, and it is not going to change any time soon.
Mixing is where Python (and Perl, even though it is integrated with the distro package management) go wrong, IME. Maven is isolated from the host package management but completely (or at least completely enough, in practice); most OSes don't bother trying to ship system-wide versions of Java libraries. The OS manages the JVM (and Maven), Maven manages whatever libraries an app wants on a per-app basis, and neither interferes with the other.
This does mean you get very little help managing dependencies of JVM libraries on native libraries; fortunately those are rare enough in the JVM ecosystem that you can handle the few that do occur by hand, IME.
> there are umpteen alternatives (gradle etc)
there was ant (build tool) then came maven which is much more flexible and contains dependency management capabilities (and _is loved_ by many) nothing to add that the parent did not already say. Then came gradle which has faster build speed and features like incremental builds and a Groovy based configuration.
pip did not ship with python for a long time, and has problems of its own.
You make it sound like adding Apache Groovy for configuration to a build system is an improvement. When build systems with a declarative config language, such as make, ant, and maven, replace procedural build scripts, that's the improvement. Using a procedural language like Groovy instead is a large leap backwards because it encourages programmers to add unneeded procedural features to build scripts, creating an unmaintainable mess.
> a huge and over-complicated xml-based system
It was an attempt to show that there are not 'umpteen different alternatives' and the ones that exist, do so for a reason. Though looking back, I should have worded it in a better way.
It's not complicated. It's verbose, that's the nature of XML, but the behaviour is very direct.
> is not even installed by default
Which is the better approach because it means it's not coupled to specific versions of the language itself. You can use a single maven install to manage multiple versions of Java (or vice versa); if you need to rely on a new maven feature in a project that's stuck on an old version of Java, it's no problem.
> people hate so much that there are umpteen alternatives (gradle etc)
There will always be refuseniks (particularly for pioneers; most of the things people hated maven for are the same things people love cargo et al for) but the Java ecosystem has done a very good job of keeping everything interoperable; it doesn't matter if one of the libraries I call builds with gradle, because there's a common packaging/dependency/repository standard, so everything will just work exactly as it should.
> with `pip install -r requirements.txt` that works out of the box?
Until you come to upgrade, or until you run it the wrong way (e.g. forgetting to start your virtualenv first) and mess up your system install.
It has some performance issues but it's still quite a recent project.
Is that true? I see a lot of pom.xml files specifying version ranges for dependencies. If I have a project specify a dependency that has its own dependencies with version ranges specified, then there doesn't seem to be an easy way for me to pin all the subdependency versions. (Yarn and npm both generate a lock file automatically, pinning all dependency and subdependency versions, which seems great.) It seems like I could specify all of the subdependencies in my own pom.xml file too with specific versions, but maven doesn't appear to make that easy to do.
Version ranges are indeed nondeterministic, that's why they're discouraged by the community (and IME very rare).
Pinning specific ones is easy enough (I tend to just look at the dependency tree in eclipse and right click -> lock transitive dependency version); I can't find a way to do that for all transitive dependencies though (I've never had more than a handful of transitive dependencies that used ranges, so it's not been a problem I've had tbh).
And no one could possibly argue that Python hasn't had irregularly poor leadership for a long while. I'd go so far as to say that its success is in spite of its leadership, culminating with Guido basically giving the middle finger and walking away this year. Can't say I blame him.
It is the nature of newer languages to learn from the mistakes of past ones. But the big irregularity which wasn't learned is that these languages I listed have very strong leadership. In the case of Go, I'd argue the strongest leadership of any language ever made, often to the disappointment of the community.
Java has had Eclipse right from when users needed it for prime time.
And yes, Python's repl is barely a REPL. You are better of writing code and running it. Its not exactly Lisp experience.
And I regularly run into issues with people using non-distribution Java packages that don't really integrate as well with the OS as the packaged one would.
I feel like some of his complaints about the ecosystem there were really just complaints about his specific setup.
really?
which way?
embedded? fastcgi native? slowcgi? fpm? cli?
natively managed or via process control?
which process control?
which ini file is configured for which application again?
are my suexec/whatever wrappers working?
if not, which user/group/permission N levels up is causing the issue?
which bytecode cache to use? is that working properly?
why aren't my URL rewrites working properly?
And it's pass-by-value for all types, because for objects an address is the value.
// Java always does these:
void f(Object& o) { ... } // passes objects by reference
void g(double x) { ... } // primitives are copied
// Java can not do these:
void p(Object o) { ... } // copy of the object
void q(double& x) { ... } // reference to primitive
CPython always passes by reference (pointer), and a C extension can break the rules and modify the otherwise immutable types (int, float, complex, string, etc...).You can squint and say everything is pass-by-value because you can always pass pointers, but then we're really losing any meaningful definitions for what semantics we're describing. I mean, it's all pass-be-electron if you dive deeply enough.
It's more like `void f(Object* o) { ... }`
These cross-language comparisons have too many nuances for me to try and make simple examples. Really, my original point is that CPython does not have "primitive types" as in Java. Everything is passed by pointer to the PyObject.
Not really. "Pass by reference" is a specific term for a much different technique which is very rarely encountered these days. It means that if you pass a variable "foo" to a function, that function can assign "foo" to some other value and the value of "foo" will also be changed outside of that function.
The confusion, I think, is that true pass by reference is almost never encountered in modern programming languages and courses, and some people mistakenly use the term "pass by reference" when explaining the difference between e.g. primitive types and objects in Java.
Java passes everything by value. The value of a variable assigned to an object is indeed a "reference" to that object in memory, but that's a coincidental use of the term "reference."
C++ isn't all that rare.
I suspect we'll have to agree to disagree whether your definition of pass by reference is universal. Given your definition, can you name any language with calling semantics which are not pass-by-value? I mean, you're always passing the value of something (be it the actual value, the address of the memory where the data is stored, or the pointer to the string containing the name of the data in an associative array).
int a = 5;
int& b = a;
b = 6;
// a is now 6
Allowing an lvalue argument to be changed by a function call is something more languages support, though, like C# and VB.NET. That’s what it means to “pass by reference”. Passing a reference/pointer is an accurate description of how most languages work, which might sound similar, but is really different enough to warrant not being called that. Especially because the behaviour has nothing to do with passing: variables just work that way in general in Python, Java, JavaScript, Ruby, C#, VB.NET, Lua, ….Anyway, back to:
> I may be missing something, but I can't find any example in my mind which indicates difference between passing in Java and Python.
You’re right that C extensions can break the rules, but in the real world they don’t (because that breaks everything); immutable int objects are effectively primitives. (Modern JavaScript – in strict mode – is an example of a language that hides the implementation details of primitives better than Java.)
That's because this class of types doesn't exist in Python.
I think there were plans to change it in future versions of Java.
Even languages that are pure "pass by value" cheat pass the value of a reference for objects.
Very few languages support doing a deep copy when passing an object. I'm trying to think of a single one, and I know that none of the major ones do. C will pass an entire struct, and so long as the struct doesn't have any pointers, sure...
But even in C passing a struct by value is highly discouraged, since it can spew all over registers and the stack! I've worked with coding conventions where pointers to structs are passed, and then the called function memcpy's the struct if it needs to hang on to it, or just leaves well enough alone if it only needs to pull values.
Has the author never given a pointer as an argument to a function before? Do they make copies of their strings and structs every time they want to pass it around? I just... I just don't understand how they could think that.
If you create a list "list1", set "list2 = list1", and change list1, both of the lists change. When I learned Python, this was a major source of confusion for me. Eventually I learned that in Python, instead of nothing being a pointer as it first appears, it is actually that everything is a pointer. On the other hand, while the same things occur in C++, the assignment operator does a shallow copy and anytime you are doing a pointer copy it is explicit.
Ned Batchelder is a good resource for learning many things about python, and this talk he gave at Pycon some years ago is no exception:
What a small world...
Possibly not, many devs now-days have never used a real native language.
The whole section about references was very confused. Especially for someone who supposedly knows C.
This has been one of my own annoyances with Python as well.
Yes, specially cos it is not "pass by reference", but "call by sharing"
http://www.effbot.org/zone/call-by-object.htm
https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_sh...
How exactly do I use Python while entirely avoiding the Python ecosystem?
I get what you're saying but you're also massively nitpicking. His point is correct.
Yeah, I don't get the author at all. Using indentation is so, so, so, so, much cleaner and easier to understand, even with lots of nesting than trying to figure out if you closed all the stupid curly braces, curly braces be damned.
The standard Python developer response to that is, "Aha! You like braces because they enable your bad programming practices!"
However, I found the best way to illustrate that point is this: I asked him, "If you're never allowed to use a text editor/IDE that highlights braces or the space between them ever again would you still prefer braces to indentation?"
I had to re-explain this concept several times but eventually I think he understood my point at least a little bit...
"Aha! You're using spaces because Python lacks decent development tools! In fact, because there's no static typing you can't even make a decent IDE for Python! Spaces are a crutch!"
Sigh.
Imagine an if, inside of a foreach, inside of a function definition, inside of a class. This is not terrible code, it's perfectly reasonable.
Now you're indented four levels in. Now combine this with some rather unreasonable and outdated assumptions that PEP8 (automatically enforced in many shops and OSS projects) has, like pretending that people are still on glass terminals and that anything longer than 79 characters is a problem. It also insists on four-space tabs, so this very simple construct has eaten 20% of your line budget.
..not to mention how it makes your code harder to read. I also share the author's concern about how you're less able to separate your debug code from your actual code.
I too used to leave debug-code purposefully unindented. There are other ways to handle that and the loss is negligible to me considering there can't be misplaced braces in Python in return.
More concretely, I often find deeply indented code more difficult to read. There are more local variables to keep track of and breaking it out into a function helps encapsulate and name what's going on. Even from your description I might look to see if I could use a generator to filter the loop instead of "for" and "if" which should be more clear, fewer indentations, and easier to optimize at runtime. I do find trying to break up long line onto multiple rather annoying because diffs are more difficult to read.
I've never really used whitespace to separate my debug and actual code...which probably speaks more to my background than anything else. I have tended to put a # at the beginning of the line when debugging and next to the comment when commenting. Linters don't seem to care for that, but the code is tidied up before it's committed.
This is just plain false. PyCharm does, and I am sure there are others.
Being visually impaired and also having coded since before syntax-highlighting editors became standard, yes. A brace character is something that's easy to visually perceive; whitespace isn't.
It's definitely an opinion your allowed to have, though not using the language over it would be rather draconian.
If that is a keyword, or brackets, or whatever, I would prefer that. You can't minify or easily lint python. And you can't easily tell if there are mixed indent methods (tabs/spaces) and IDEs struggle with it vs. a simple bracket structure.
(This is a rare repost within a thread. I'm punching that card for 2018. Whitespace significance is my #1 issue with Python. I love the language, hate this feature.)
I find that the linting available in Python is better than that in c++, though c++ has better auto complete.
Minification of most of Python is possible, although shouldn't be considered of any value since it's not passed over the wire to a user.
Yes. (Though that's hardly the worst thing about Python.)
Their lack in Python bothered me too, one afternoon in the spring of 2001, then I moved on.
When I hear someone complaining about it I immediately think, this person hasn't much experience with Python, or is one of those highly inflexible pedant types.
YES!! Every time I try something other than Python that requires braces, I am like "WTF, why do I have to type this extra shit! Such an annoyance."
But it leads to another problem: hatred of typing commas in non-lisps.
The reality is that after a week or so I forgot completely about it. There are other things for sure that became an issue (took awhile to fully grok class variables vs instance variables), but the indentation was never one of them.
Regarding 1: I'm sick of people saying that the ecosystem != the language. No one is going to be able to prop up an alternative ecosystem and get more traction than the language's own ecosystem, so you are basically always stuck with whatever crazy ecosystem comes with the language. Most of what people care about is the ecosystem and the syntactic sugar relationship the ecosystem has with the language, so it is a very real criticism imo if a major programming language has a bad ecosystem.
You are stuck with the ecosystem you ship with the language, so get it right the first time.
venv has been included with Python since 3.3 (2012) and was a separate package before that. If you're having troubles with the ecosystem playing well with Python 2.6 (2008) what do you expect anyone to do? I really think OS package managers have been really negligent about multiple versions and project-level dependencies--maybe it's outside of their scope (that also wouldn't help you on Windows)? People have been griping about software specific package managers for over a decade and OS package managers have only added the most minimal support, but never addressed the reason for them.
I've argued for years, if something is critical to your business then decouple it from the OS. Of course a new version might screw up your OS and you may need it to address a specific business concern...that's just one of the problems of any interpreted language. I wish things were better out of the box, but it's not unreasonable to address yourself.
node vs web browser? linux vs unix? ubuntu vs debian? autotools vs base make?
i'm sure these aren't the best examples.
irrespective, you can (like|dislake) the language and (like|dislike) the ecosystem separately.
"oh I hate programming in XYZ it's far too verbose but it really does have a nice package management system"
yes, they are connected, but I don't criticize a hamburger by complaining about the taste of my soda..
Maybe try to not identify so hard with language choice? That way you gain the capability of processing critique without loosing your marbles.
4: Yes. Imports >> Includes in every conceivable way. However, what is actually available to import _is_ confusing. This is really a symptom of a different problem though...
5: Yes
6: Yes - picking on the quotes thing with Bash is just one of a million syntax "quirks" that make Bash a horrible no good language. ( as an operator is my vote for most egregiously mind-bending "feature".
7: Yes. Once you get used to it, Python usually does what you want in terms of passing parameters.
1 and 2: NO. The ecosystem and the language are the same thing. They're useless without each other. Every other point the author makes is dumb, but this one is utterly correct. Now, the version schism is unfortunate, but it's spilled milk. I agree with the author that it's obnoxious, but there's not much to do about it now. However - creating, sharing and installing packages in python is THE problem. And I'm not saying it's an unsolvable problem - that's what makes it so frustrating! You have a thriving, healthy community of package maintainers creating amazing libraries that allow regular engineers like me to get work done out of the box. And in many ways, as other posters have pointed out, python paved the way for making this experience better than the hell that is c++ package management. Sure.
But the current user experience of trying to give somebody an application that happens to be written in python? The story of how you take some python code you wrote, bottle it up, and make it work on somebody else's system? And the story for how they take that code and make it available to themselves on their own system? It. is. atrocious.
Which one of these tools, concepts, commands and filetypes do I need to care about: package, module, egg, wheel, bdist, sdist, distutils, setuptools, easy_install, pip, pipenv, poetry, PEX, PyInstaller, py2exe, PyPI, conda, miniconda, anaconda, virtualenv, venv, requirements.txt, setup.py, pyproject.toml, pipfile, site-packages, dist-packages, $PYTHONPATH or motherfucking .pth files??!?!
Ever been curious how sys.path gets populated at startup? Gaze into the darkness: https://github.com/python/cpython/blob/master/PC/getpathp.c
And to anyone saying "Just use a virtualenv": that is so, so not an answer to this eldritch horror. Let me ask you this - when you installed chrome, did you have to create a new virtualenv? No. If you want to effectively distribute code to lots of people, you need more than a god damn virtualenv. The whole world isn't just scientists tooling around in sandboxes.
It's often tricky to specify abstract dependencies such that people can use pip to install libraries without having trouble with other packages in their environments, and also quite tricky to provide builds with native code or links to native libraries for different architectures.
There is one big difference between Pypi and a distribution repository:
With Pypi, every contributors are truly individuals, they upload their packages alone, they maintain them alone, they basically do whatever they want. The maintainer here can likely be a single point of failure.
With distribution even if most packages are maintained by a single person, the repository content is the responsibility of the distribution as a whole. These distributions have formalized their processes and policies a long time ago. For example, if a critical security issue is found, unless something goes wrong, the package will not be left unpatched, even if the package maintainer is not reacting.
Also, Distributions put huge efforts into providing stable versions, if you are using packages from a distribution, you have some guaranties about stable APIs, and that these packages with stable APIs will actually be maintained for a few years. Even if it's not always perfect, with non-critical bugs not always fixed, it's a far cry from Pypi where the only true possibility is to hard pin every single version of your dependency tree inside requirements.txt.
This was one of the most frustrating articles I have ever read.
Telling users they are not good enough for your technology is not the way you go about these things.
Also if this works in other languages, people do expect it should work in Python.
Instead, this list is bizarre, misinformed, and largely incorrect. Other commenters have already pointed out several specifics. Two things I haven't seen yet that I'll add:
> And I pity anyone who miscounts spaces and accidentally puts in three spaces instead of four somewhere -- this can take hours to debug and track down.
This is just absolutely batshit ridiculous. You'll get an IndentationError pointing to the exact line in question. In all my years of working with people at all levels, I've never seen anybody spend more than a few minutes working out an IndendationError.
> <Pass by reference/object> is one of the big differences between procedural, functional, and object-oriented programming languages.
This is a non-sequitur, and it's plain to see. It's possible (in fact common) to write with all three of these orientations in python, and object transit has absolutely nothing to do with it.
Instead, we're presented with paragraphs written by someone who can't read a stack trace:
In [7]: def foo():
...: print('foo')
...: print('bar')
File "<ipython-input-7-47f5b52e9e07>", line 3
print('bar')
^
IndentationError: unexpected indent def foo():
nums = [...]
sum = 0
for x in nums:
print(x)
sum += x
I don't know python well, but it seems like there could be subtle issues with indentation that wouldn't cause a compiler error, but would cause the wrong output.2) On the substance of your concern: I think the evidence is clear (although I'm aware of no study) that having both syntactical control characters (typical curlies) and style-only indentation is more likely to lead to the outcome that you present, because the eye will always gravitate to read by indentation, whether it's syntactical or not. So have the indentation be the syntax makes this problem more easily avoided, not less.
IE:
def foo() {
nums = [...]
sum = 0
for x in nums {
print(x)
}
sum += x
}If I highlighted that entire function in my editor of choice and hit TAB I'd get exactly the "correct" indentation.
Almost any modern editor can automate this for you. It's one of the benefits of having a syntax for blocks. Yeah, it's extra typing for something you're going to do anyways, but now the computer can manage the style for you.
Also, and this is just my personal experience, I've learned to read by those control characters. I have a really hard time reading python because I'm subconsciously looking for the control characters!
I really think that part is really down to how you learned to program and what language you use on a daily basis.
As an aside, I think something like this is much more demonstrative of the issue you're suggesting:
def foo() {
nums = [...]
sum = 0
for x in nums {
print(x)}
sum += x
}
It's still automatically fixable, but it's way less immediately apparent that something is wrong with the indentation.That snippet is an example of not understanding indentation-based scoping at all.
void foo() {
...
for( ... )
printf(..)
sum += x
}
In python it's at least more visually obvious that the second statement isn't part of the for-body. class A:
def some_method(self):
return # something
def some_func(foo, bar):
return # something
def some_other_method(self):
return # something
They intended to make `some_other_method` a method of class `A` instead they ended up writing `some_func` the wrong place and python parser was happy. For everyone except extreme python beginners debugging this will take 10 seconds. But just a datapoint.But it's easily fixed with code collapsing tools or a structure viewer (both of which are provided in all of the major python IDEs).
To the author:
1. Java, JavaScript and C# all have types which are passed by reference. (They also have types which are passed by value.)
2. Python lists are not arrays, if by arrays you mean a C- or FORTRAN-style block of non-resizable memory which is indexed by position. Python lists are much more like the Java List or C# IList interface.
I might be misunderstanding you, but I don't think you can do this in C++. References can't be changed to point to a different object after initialization. If you have code like:
void MyFunc(Foo& ref_param) {
Foo new_foo;
ref_param = new_foo;
}
The assignment above isn't "changing the value to which a caller's variable is bound". Instead, it's running the '=' operator on the Foo object to copy the state from new_foo to ref_param. To demonstrate this, you could run the following to see that the addresses are the same: Foo original_object;
Foo& object_ref = original_object;
MyFunc(object_ref);
// object_ref still points to the same address
assert(&original_object == &object_ref);
Under the hood, C++ reference params are basically syntactic sugar for passing by pointer-value, so this behavior isn't surprising.This is a well-known distinction that goes by several names, see e.g. https://en.m.wikipedia.org/wiki/Evaluation_strategy#Call_by_...
The distinction between passing the value of a reference being used by the caller and passing a reference to the caller's stack is so rarely important or useful that there's no consensus terminology for it. The distinction that's actually relevant and useful is whether, when we write f(a), f receives the (thing we would call the) value of a or a reference to the value of a (and in particular whether f can change the value of a). In Python, f can change the value of a (that is to say, the thing Python programmers understand to be the value of a); therefore Python is pass by reference as the term is usually understood (and certainly any term for its call style that uses "value" is more misleading than calling it "pass by reference").
And the reason for that is essentially all modern languages make copies of something when passing argument parameters. The only question is what they are passing and exactly how hard the language layer works to make it look as if you are or are not passing something by reference.
I have to reach to something as obscure as Forth to find a language that does not work that way, and truly does not copy parameters into a function. (I haven't dug into Factor enough to know if under the hood it still copies parameters.) It's a very unusual choice to not copy something for a function call.
(This is one of the ways our CPUs have been optimized for the languages we run; they make this intuitively expensive operation otherwise cheaper than it would be on a naively designed CPU.)
Then of course someone turns on whole program optimization for C++, half the code gets inlined into one terrifying large function, nothing is copied anywhere, and the code runs 2x faster.
Then some poor SOB has to debug the assembly code for all of this and has nightmares for years after.
Not like I'd know or anything. :)`
> In Python, f can change the value of a
but it can't. `f` can't change the object which `a` references:
def f(x): x = 5
is a function with no visible effect. When called as `f(a)`, `a` retains whatever value it had previously.People who believe Python is "call-by-reference" don't understand this and write incorrect or overcomplicated code as a consequence. The distinction is important.
There is no consensus that "pass by reference" denotes that, whatever a wikipedia editor says. Normal working programmers do not understand "pass by reference" to mean specifically "pass a reference to the caller's stack" but only the more general concept of "pass a reference to the value of a". If you want to talk specifically about what kind of reference gets passed, you need some new terms for that, and both the new terms you come up with will be subtypes of what most of the world will continue to understand as a general category of "pass by reference". No-one outside of these arguments on HN gets confused about whether you can write a swap function in Python; people understand "pass by reference" to mean something more general and entirely true of Python (and Java and so on). You can tell by how often we see those languages described as "pass by reference".
> but it can't. `f` can't change the object which `a` references:
Yes it can. It can't make a to refer to a different object (as your f tries to), but it can change the object a refers to just fine.
No, Python doesn't even do that, because Python variables aren't names for storage locations to begin with. They're namespace bindings. Passing a variable to a Python function means transferring a namespace binding from the caller's namespace to the function's local namespace.
In C:
int i = 17;
Means "set aside one int's worth of storage and fill it with the bit pattern for the number 17".In Python:
i = 17
means "create an int object with the value 17 and bind it in the current namespace dictionary to the name i".In addition python also does have arrays if you want/need them: https://docs.python.org/3.7/library/array.html
this will not change the array which was passed in and is a purely local change in C#.
static void Change(ref int[] myArray) on the other side would change the array which was passed in.
The difference between "pass by value" and "pass by reference" for reference variables is how the variables themselves are handled. For example, if you pass a reference "by reference" to a function and set it to null, then the reference variable in the calling code is also set to null and will cause null ref exception if you try to access the object. Whereas if you pass a reference "by value", if you set the variable to null, the calling code variable isn't affected.
Pass by value and pass by reference is not about objects or types really, it's about variables.
More abstractly, Historically, in computing "list" connotes pointers and "array" connotes sequential memory. The connotations imply engineering tradeoffs. [1] "List" may make more sense for a beginning programmer. It is an arbitrary context switch for programmers writing in multiple languages. Considering that one of the driving use cases of Python has been systems programming, "list" is misleading regarding performance characteristics. [2]
[1]: For example as in Scala https://docs.scala-lang.org/overviews/collections/performanc...
[2]: googling "python arrays" returns a lot of results explaining the difference between Python's Arrays and Python's Lists. "List" is "foo => spam" and "bar => eggs" Pythonism gone too far.
In Python, Arrays are sequence types and behave very much like lists[1] In other words, even in Python, arrays have similar semantics to Javascript Arrays not Java Arrays.
What Java, JavaScript and C# call "arrays"
are each very much like what Python calls
"lists'.
In Java and C#, 'arrays' are fixed size at creation time.Both Java and C# provide 'lists' which are variable sized*
Python 'lists' are variable-sized.
Seems like a consistent naming scheme to me?
*there might be an underlying array that gets reallocated - but it's encapsulated within the list object; the reference to the list object is unchanged when this happens.
But Python predates C# and Java.
The biggest irony here is that if you wrote your code in C instead, you would actually pass more arguments by reference than in equivalent python, because you're going to use pointers for everything but primitive integer values.
However, if you reassigned your copy of a pointer in the body of a function, the original pointer would still point to the same place it did before the function was called.
That's not the same thing as actual pass-by-reference in languages like C++.
This statement is plain wrong at best and intentionally misleading at worst. 99.99% of Python 3.5 code runs unmodified on 3.7.
> At the official Python web site, their documentation is actively maintained and available for Python 2.7, 3.5, 3.6, and 3.7 -- because they can't decide to give up on the old code.
No. It's because some people still are on the not-latest version and still need access to the documentation. It's what every good project should do.
I stopped reading there. Too much wrong in too little text.
> async and await are now reserved keywords.
so unless the author is using async/await as variable names (and i wonder why they would), 3.5 code is going to run as expected.
import asyncio
@asyncio.async # SyntaxError in 3.7
def my_coroutine(...): ...
And since asyncio is one of the strongest reason to migrate to python 3.x, I bet far less than 99.99% of code written for 3.4/3.5 can be run on 3.7 with no changes.Luckly, 99% of changes were trivial. Other 1% is a nightmare.
Kubernetes-cli did, [0] inadvertently due to code generated by Swagger. [1]
That doesn't mean only that project either - it means anything _depending_ (not necessarily directly!) on it is also broken under 3.7. [2]
[0]: https://github.com/kubernetes-client/python/issues/558
[1]: https://github.com/swagger-api/swagger-codegen/pull/8401
[2]: It bit me.
class SomeMetaClass:
def __new__(name, bases, attrs):
pass
class SomeClass(metaclass=SomeMetaClass):
pass
Now suppose that SomeClass needs to use the typing module's mechanism for indicating a generic, so you could do an annotation like "SomeClass[SomeOtherType]". So you'd want to have SomeClass be a subclass of, typing.Generic[T]. That would, in Python 3 prior to 3.7, raise a TypeError due to metaclass conflict -- the generics in the typing module had their own base metaclasses. So you actually had to define an intermediate class to resolve the metaclass conflict, and do like so: class IntermediateMeta(SomeMetaClass, typing.GenericMeta):
pass
class SomeClass(metaclass=IntermediateMeta):
pass
(this was weird and rare and GenericMeta was never documented)Python 3.7 implemented PEP 560, which introduced the __class_getitem__ hook for implementing the behavior GenericMeta used to handle, and doing away with the need for the typing generics to use GenericMeta as their metaclass. So GenericMeta exists in Python 3.5 and 3.6, but no longer exists in Python 3.7, and anything which tries to reference it will break.
python3.7 -m pip install --upgrade https://storage.googleapis.com/tensorflow/mac/cpu/tensorflow-0.12.0-py3-none-any.whl
wget https://raw.githubusercontent.com/aymericdamien/TensorFlow-Examples/master/examples/1_Introduction/helloworld.py
python3.7 helloword.py
Tensorflow 0.12, released over two years (before 3.7 development had even started) runs fine.The standard library is also haphazard and inconsistent (much like JavaScript's). Take lists: some operations are methods, some are functions, some mutate the list, some make a copy, some are global, some are in a module. There's no rhyme or reason that I can tell. Modern C++ has, in my opinion, a much more well-thought-out standard library. There are very few methods/functions which exist due to historical accident (iostreams aside), and the distinctions between methods/free functions and mutators/copiers is fairly uniform.
https://www.artima.com/intv/pyscale.html
"So I never intended Python to be the primary language for programmers, although it has become the primary language for many Python users. It was intended to be a second language for people who were already experienced programmers, as some of the early design choices reflect. On the other hand, intuitively I probably stuck to many of ABC's design principles. Because although I had my criticisms of ABC, I borrowed many of its valuable elements, which eventually made Python a great language for people who aren't ace programmers or who are just learning. We now have a large community of people using Python as an educational language, teaching Python in schools. These people aren't and may never be professional programmers, but they still find some programming skills useful." -GvR
"It was intended to be a second language for people who were already experienced programmers, as some of the early design choices reflect. "
Basically, don't discount a text book where every algorithm is executable code, without first having to translate it into some other language.
It's actually a huge problem if you end up trying to hire people to build enterprise software in Python, because everyone and their mother has "5 years of Python experience", but "scripting on your own" is miles apart from "building well designed systems".
Also that rule is tautological.
The "immediacy" is there in both languages (well, in Python and modern Basic without line numbers): you can just type code without any rituals or mumbo jumbo and it will do more or less what you tell it to.
So, I decided to design a language of my own which would borrow everything I liked from ABC while at the same time fixing all its problems (as I perceived them).
http://python-history.blogspot.com/2009/01/personal-history-...
Learning to program from first principles is hard, but I reckon Racket, a lisp, with it’s wealth of teaching materials and well thought out design is about as good a way to start as any. It’s small and consistent enough to actually learn from the ground up rather than by pulling on one thread.
Let's take the list type, the public methods are: 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'
Obviously as the names imply `copy` returns a new list, and `count` returns an integer. Neither mutate the list.
For the rest of it: methods mutate the list. There are some builtin functions that do similar things, but those always a new object (sorted, reversed)
`set` is a better example: `set.union`, for example, returns a new set, while `set.add` updates in place. (To be fair, this is with Python 2.7, maybe Python 3+ is more uniform?)
Python has fallen to my second or third most commonly used language now and speed is definitely a reason.
Maybe those scoping gotchas are not beginner topics?
They are the second one of your beginners trips over them. Especially the first one is fairly easy to encounter and really vexing.
That said, Python in my experience is an excellent beginner language, and I'd still recommend or teach it.
I agree, although I suspect we have different ideas as to which parts of Python would be included in that simpler language. For example, I quite like the consistency of "everything-is-an-object".
FWIW some of the Python core developers have started to express a similar sentiment. Especially in the last few versions, where Python has got much more complex with the addition of type annotations and multiple `async/await` features. I wonder whether the retirement of the BDFL will slow down Python's rate of growth, or accelerate it?
> nested generator comprehensions behave differently than nested list conprehensions
I haven't come across this - could you give an example?
>>> [[x*y for y in xrange(1,5)] for x in xrange(1,5)]
[[1, 2, 3, 4], [2, 4, 6, 8], [3, 6, 9, 12], [4, 8, 12, 16]]
>>> list([x*y for y in xrange(1,5)] for x in xrange(1,5))
[[1, 2, 3, 4], [2, 4, 6, 8], [3, 6, 9, 12], [4, 8, 12, 16]]
>>> [list(l) for l in ((x*y for y in xrange(1,5)) for x in xrange(1,5))]
[[1, 2, 3, 4], [2, 4, 6, 8], [3, 6, 9, 12], [4, 8, 12, 16]]
>>> [list(l) for l in [(x*y for y in xrange(1,5)) for x in xrange(1,5)]]
[[4, 8, 12, 16], [4, 8, 12, 16], [4, 8, 12, 16], [4, 8, 12, 16]]
The problem is that the `x` in the inner comprehension gets somehow linked to the variable `x` in the outer comprehension, rather that the value of `x`. (This matches Python's similarly bizarre closure semantics.) So, the results are "as expected" when either (a) the inner comprehension is evaluated eagerly (first and second example), or (b) the outer comprehension is evaluated lazily (second and third examples). But if both the inner and outer comprehensions are evaluated lazily, the inner comprehensions just see whatever the "last" value of `x` was, which I think to many people is surprising. (No less so than mutable default arguments, at least.) >>> l = []
>>> for i in range(10):
... l.append(lambda: i)
...
>>> for j in range(10):
... print(l[j](), end=' ')
...
9 9 9 9 9 9 9 9 9 9
The issue isn't really closing over the variable instead of the value, but the fact that Python re-uses the same variable for each iteration of the loop. C# used to behave the same way, but its designers considered this bad enough that they made a breaking change to the language to fix it [1].Unfortunately, C#'s solution (creating a new variable for each execution of the loop body) isn't really an option for Python. It would conflict with the rest of the language, which only uses variables scoped to whole functions.
Note that Go makes the same mistake as in earlier versions of C# [2].
[1] https://ericlippert.com/2009/11/12/closing-over-the-loop-var...
I tend to assume that about all languages now, and when I need to use a new language I try to just learn that simpler language initially. I skim the documentation for the rest, but don't learn it until I actually need it (either because the functionality is necessary, or it can make the code better).
It can take a surprisingly long time to need more than the simple language.
I'd also welcome a simplified Python, from scratch as it were, but that is a giant undertaking and bigger than the 2 to 3 chasm.
Still, I don’t hate it at all. It’s good and beautiful for lot of other stuff.
I can’t help but think that the lack of Kenneth Ritz in the commit list since summer has something to do with this. I don’t recall the exact scenario, but I think there was some kind of falling out between him and the community.
So despite all the things I dislike about python, it's still my go to for a proof of concept or getting something done quickly.
1. Importing behavior is ridiculous: https://chrisyeh96.github.io/2017/08/08/definitive-guide-pyt...
2. Python libraries' documentation leave a lot to be desired (compared to good javadocs). Just because your language is dynamically typed doesn't mean you don't need to describe what the expected shape of a parameter should be.
3.
Static method? @staticmethod
Static variable? declare it outside of your methods but inside your class
Private method/var? name it: __name (but it's not really private just hard to find)
I'm sure other Java programmers have had a culture shock coming to python as well. I appreciate how concise the language is, the great community and module ecosystem, and how productive it is. But, it feels to me like there's features missing (I've only been seriously programming python for 5 months though, before I was basically just scripting and writing java code in python).Also the most fun part of learning python has been list comprehensions and itertools, they've really been a revelation. Like when I first learned Ruby procs, blocks, closures and lambdas.
Especially #2 -imo a dynamically typed language/library warrants having better documentation, not worse.
> Static variable? declare it outside of your methods but inside your class
Sounds like you're still thinking in Java :). A static method is rarely what you want (and if you do you can put it on the class like for variables); functions are first-class so you can just put them in your module.
Python’s philosophy of convention rather than constraint is a nice chance from Java’s corseted mindset. But I’ve never had to use Python in a large development team, so I don’t know how well it holds up with a large group of devs.
As an aside, if you have fun with comprehensions, itertools and lambdas, I urge you to take a look at functional programming!
I've been developing Python for upwards of 10 years, and not one single time I have ever had a legitimate use for double underscore, and I've only seen one legitimate use in the wild (involving auto-generated code and C interfacing).
You also very, very rarely need static methods.
This is why I _love_ python. Stop trying to protect me from your library. I've been doing some C# work and nothing bothers me more than having to fork and re-build an entire library just because the author didn't think I should be allowed to touch some variable that should've just been `public` or `protected`.
I don't need a library author to hard-block me from things. Put up the appropriate warning signs, then get out of my way.
Could you give examples?
- 1 & 2: just use virtualenv, js has its own version manager too (nvm) which is very useful. This is one of the reason why only python2.7 is included in OSX, since most of seasoned python devs not use it. usr/include vs usr/local/include in cpp are not easier to use / understand.
- 3: my opinion is this forces you to write readable code, in which you don't have to ask yourself where the scope starts / ends
- 4: since import use dot notation you just need to follow the path until you find a file and in this file the corresponding function. Or read the doc. Or use an IDE with autocompletion. Looking a .h files in c sometimes lead to using a wrong function judging only by its name.
- 5: They are called "list" because they are lists, not arrays in a C sense (size not fixed)
- 6: multiline strings are a mess indeed, but python3 handles them all in utf-8 (one of the reasons why it broke backward compatibility)
- 7: just like js, making them 2 of the 3 most used languages. Understand the difference between pointers and references is harder to grasp for a beginner to just remind modifying the arguments of one method is dangerous unless you know what you're doing
- 8: there is the from ... import ... which allows you to avoid this while being explicit
I agree with this so much. I've never looked at someone's python code and had a moment's confusion about where a particular function ends. On the flip side, I see plenty of randomly/confusingly indented C/C++/C# code.
Of course I hate the whitespace trickery. I hate the auto formatting that sometimes doesn't work. Those are fairly minor.
What I really, really hate is the lack of static typechecking. You often have to read lengthy swathes of code to understand how functions are actually used. The conventions in the community are terrible. I have used respected libraries where functions are designed to return a variety of types, just because, so you need to dynamically typecheck your results. Gawd.
And just as bad the fact it is impossible to encapsulate code. This underscore nonsense, don't even mention it. Sheesh. I have found that in real life, internals bleed out over the code base making code more fragile over time and harder to refactor. It has all these open source scientific libraries because professional programmers were working on other things, and that is truly unfortunate.
The lack of seriousness is demonstrated by the fact that it's frozen in time in terms of runtime data structures because cpython knows and relies on those internals. Nutso.
And then the GIL!! What the ?@.
I never understood why you would use a function like len() in an Object Oriented Language (instead of x.len ) or having to explicitly add self to every method...
The answer is simple: there is a x.len function. It's just x.__len__, the protocol. Forcing it to be exposed on every type is a waste: do you use .len, .length, .size? This duck-typing allows any object that supports the length protocol to be passed to `len()`. Simple.
Having it add itself to every object is.... not what you want at all. Some objects don't have a length.
Yes, like the tiny puny baby middle-school children who built microscopic useless toy "programs" (not even worthy of the name) at Instagram, YouTube, Dropbox...
What I really, really hate is the lack of static typechecking.
Then don't use dynamically-typed languages. Not everyone shares your tastes, nor do they have to.
And just as bad the fact it is impossible to encapsulate code.
The more you write here, the more I think C# is a great language for you. And I don't dislike C#! But you want a nice statically-typed object-oriented language with data hiding. C# is an example of one. Python is not.
And then the GIL!!
I've written at length about how the existence of the GIL is the result of tradeoffs that seemed perfectly reasonable at the time (keeping in mind that Python is older than Java, and I suspect older than the average HN commenter). With 20/20 hindsight would a different approach have been better? Sure, but if they'd had the ability to see the future in enough detail, the folks who chose those tradeoffs probably would have bought lottery tickets instead of building Python.
After trying to maintain a legacy system written in Python (where all the authors had left the company) for a year, I threw my hands up and moved on to a Scala shop. I vowed to never work again on any major project written in a language without static typing.
I think this lesson has been learned in the broader community though. Even most of my colleagues that have used Javascript for years have moved on to Typescript and never looked back.
I even disagree with the, "well it's good for small projects" line of thinking. Look - if it works there, it's only because you have net fewer bugs to catch and lines to read, so the cognitive overhead can be born. But why not dispense with that cognitive overhead in the first place and just use static types!
The one exception I make here is for shell scripting. The tight integration with the command line just makes bash the best choice for some situations. But even then, I have about a 50% success rate of estimating whether or not this "little script" will ever grow to become a production application with multiple modules and components, so today I'm even wary of using shell scripts in many cases.
I'm not convinced. When a program really is small enough to hold the whole thing in your head, writing that knowledge down is just overhead. (Of course, this reverses as soon as the program gets bigger than that).
As someone who works with multiple languages across multiple environments, the author's comments on the installation ecosystem are on point. Though, these critiques are better directed at the packagers of the distribution. I'd given up on getting current, correctly built versions of Perl, Python, R, Octave, etc. from packagers/distros, and simply build my own in a separate tree[1]. Most folks now do this in user home directories, but I still personally like the system level availability.
FWIW, I've found that Julia[2] is a far better Python than Python. You have all the advantages of a JIT compiled language which does parallelism/concurrency well, has an awesome FFI to simply use external libraries, etc.
The one valid gripe that wasn't quite there is why doesn't Python (or any other language really) manage its modules like CPAN? CPAN is amazing. It's ancient and dusty and missing obvious modern improvements, but it's still way more useful than anything else.
Searching through PyPi is pretty awful. Look for "yaml versioning" and get 10,000 projects to wade through. CPAN is hierarchial, so not only can I find what I'm looking for, it even exists on disk where I expect it.
Because of all that, a few other things happen. The low level modules are older, exposed to more users, so people find them first, improve them over time, and this influences and improves new code. Not only are there these great examples of quality code for comparison, but code reuse and extending is way more common than entirely new modules.
Testing is also really rigorous. CPAN testers framework tests across all Perl releases and computing platforms, continuously. Perl module build instructions are converted easily to Makefiles, making it easier to build and test on other platforms if you're not familiar with Perl conventions.
Most modules also provide real in-line documentation, not just one line describing what a function does. Perldoc usually gives you a real man page for any given module you're looking at, whereas Pydoc gives you a bare list of method calls and objects that maybe the author added documentation to, but often not. Which am I supposed to use, for what? Might as well go read all the code...
...oh, and unrelated, but using regex's in Python is kind of horrifying.
But, yeah ... CPAN, and C-TAN, are things to behold. Python hasn't done a great/good job with this. Npm is just plain awful. Julia's Pkg was good in the 0.6.x days ... seems to be borked now, though this may be due to the 0.6 -> 1.0 transition.
For me, Perl is my go-to scripting language. I've written some pretty intensive apps in it, which would be horrifying in other languages. It has (many) warts, though packaging isn't one of them.
I am looking at using Julia for scripting in places where I'd ordinarily use Perl ... mostly data clean up. This noted, it is very hard to beat Perl in munging data. R comes close, but writing parsers in it is hard.
Perl6 aka Rakudo is very interesting to me. Addresses many of the issues I've had with Perl in the past, and gives some truly incredible power going forward. Adoption appears to be low and slow right now. I don't actually have time to play with it now, as I've got other higher priority issues to deal with.
Versions: Ok? Versioning and fragmentation is a hard problem. Backwards compatibility is a hard problem. Moving fast and never breaking anything is a nearly impossible problem. Python sucks at it, lots of things suck at it. There are many examples of other languages (.NET Core, for example), platforms, frameworks, etc that all suffer from this. It can be a reason to avoid those things but probably not one to base a loathing on.
Also, lots of people still use Perl. Lots of people still love Perl. I don't know why.
Installation: This screams "windows only" user. Path'ing, local dependencies, and package management is relatively common and you should force yourself to be comfortable with those concepts. Just saying "I should be able to just run one thing and be done forever", albeit ideal, is naive and never going to happen.
Syntax: Yep. Spacing blows and you're always going to have stupid issues with it. I hate Python's spacing. Someone ought to create a custom interpreter that allows for using braces.
Includes: Most of these complaints sound like they're coming from someone largely silo'd in the C/C++ world of wanting to know everything. "With C, you can just look in /usr/include/*.h" - the author is admitting he's unhappy because Python isn't C.
Quirks: General complaints about other languages... and using those complaints to somehow sour Python? The quirks he does list for Python aren't even strange - they're pretty useful.
Local Names: OP probably could have just included this in quirks rather than having another point. I'm pretty sure there's actually a means of avoiding this type of import issue by some silly Python pathing shenanigans.
That's not the point at all. I mean, did you read it?
How do you mischaracterize the specific point about the casual opportunity for foreign module metaprogamming? Module initialization is bad in a pernicious way. While you can't do operator overloading, you can clobber namespaces (which was referenced). Paired with a community repository, this is exactly the same case of what's so dangerous about npm.
He complains about Python developers grep'ing directories when he admits to looking through just as arbitrary of a directory. His complaints about random code execution during import is valid but also seen as a feature _allowing_ metaprogramming. It's just as bad as C/C++ allowing clobbering over memory.
These are features that come with trade-offs. The author is focusing _only_ on the trade-offs and how they don't exist in his favored language as a reason to hate the language. That's certainly fine for his subjective opinion but not as appropriate in a blog post where he is clearly trying to persuade others.
That's a good parallel.
> My point was he's using inexperience and/or familiarity with C/C++ as a reason to "hate" it.
I have less familiarity with C/C++ than Python and I hate it because it's not about language perspective. It's bad language design, for such a high level language. Inclusion wrapped with execution is unsafe and has an easy fix for most language interpreters. Don't allow execution during a declaration. If you want to do metaprogramming, there are other ways that don't break the paradigm (rewriting files before inclusion, chaining programs, etc).
Perl still holds a special place in my heart. It's so forgiving that it's perfect for banging out something that gets the job done in a couple minutes but while I most often use it for something quick and dirty you can still put in some time and planning to write something complex and maintainable too.
I'm not sure there are major projects being written in perl today, but it's the language I still use for automation of day to day admin stuff and for pretty much all of my log/mail parsing needs.
Nope, for reasons I've explained up thread. In the meantime I suggest this code:
from __future__ import bracesComparing whitespace issues to brace-matching issues is a strawman. Braces are visible in 99% of editors.
The benefits are enjoyed every single read of the code while the drawback is a potential issue (being very generous when I say) once every six months. Honestly happens about once every five years to me.
The whole can't manage my environment and don't understand how to manage the python thing at all has my sympathy. Python is messy at that level.
With the organizational 'control' sysadmin deprecated by devops and containers/automation becoming the new norm these types of complaints have to be dealt with by people who have no idea of what they are doing in generalized context.
> My code for Python 3.5 won't work with the Python 3.7 installation unless I intentionally port it to 3.7.
Probably not. While I can't guarantee there are zero breaking changes, it is highly unlikely to encounter any from 3.5 to 3.7. Another numerical pair might have been a better example.
The split between 2/3 was an issue, widely known, and in the past. Personally I'm glad some big problems in 2 were fixed, though there was some pain.
> When Perl5 came out, a lot of people just switched to a different programming language that was more stable.
Nope, Perl 5 was incredibly popular. It was 6 and the two decade wait where folks jumped ship.
> And I pity anyone who miscounts spaces and accidentally puts in three spaces instead of four somewhere -- this can take hours to debug and track down.
Nope, it tells you the first line where the indent is wrong.
> imports: then search every file in every directory
Nope, just type import ... If you want to know which file it found, print(module).
....
Others have found the other issues. Most of these are nitpicks from someone who doesn't fully understand or agree with the tradeoffs chosen.
IMHO, the biggest design flaw left in Python is that it is always dynamic and that's what makes it so slow.
Why too dynamic? Well, changing types of variables isn't used much in practice. So, to pay a time cost for it on every single line is unnecessary. If types were fixed by default but let you opt in to dynamic when needed, we'd have the best of both worlds. Speedy most of the time, dynamic and slow when actually useful.
Basically whole infrastructures like Cython have been built to correct this.
it seems the performance problems in python can (and are) being fixed. the big downsides i see are the 2to3 mess (which seems like it will be solved with time), weak concurrency support in the default implementation and the lack of the ability to do much real static analysis.
makes for great interactive glue though!
Python has its warts, but so do most languages that are almost 30 years old.
These so called package manager can't manage packages at all. I'd like to call it package downloader. They just download everything from language interpreter to library to a single directory and call it job done. No easy way to remove the package. The interpreter and library's version is forever fixed. Theoretically, it can upgrade the version but the user mostly don't.
I really hate their ecosystem.
This is scoped to the project in npm. In Ruby, you can specify the path (common to "vendor" your gems). You can use version managers (like rvm or nvm) to scope your libs to versions or even named projects (thinking of rvm's "gemsets")
> The interpreter and library's version is forever fixed. Theoretically, it can upgrade the version but the user mostly don't.
I can't speak for all ecosystems, but on Ruby projects I've been a part of, keeping gems updated is pretty common. Github even includes monitoring for vulnerabilities to encourage this.
Having been part of ecosystems that don't have package management (for example, ColdFusion) even the worst package managers are a major improvement. (Though I do think the isolation of python's virtualenvs is a better approach than most systems)
"pip uninstall" has been around as long as pip has.
Theoretically, it can upgrade the version but the user mostly don't.
In practice, people deploy to a virtualenv that gets recreated on each deployment, and tend to treat a virtualenv as an ephemeral thing.
> https://stackoverflow.com/questions/41573587/what-is-the-dif... What a mess.
Came here to post something similar, but a commenter on the blog post itself got to it first. Virtual environments never made sense to me.
A while ago I had a broken python ship with my Ubuntu desktop. Pip decided they were going to deprecate behaviour, and nobody updated or tested on Ubuntu.
Also it is slow and ugly. Just write it in Golang or something.
As for ugly, I disagree and find Python some of the easiest code to read. "Slow" depends on the application. For 90% of the apps out there, you're waiting on something else (network IO, DB, etc.) and it's fine.
I like the library `invoke` for scripting A LOT, I just prefer to write my larger software in other languages.
Using indents for blocks just seemed unintuitive and error prone to me. But I dismissed it because all the programming languages I've worked with have had curly braces so maybe the reason for my discomfort was that it was unfamiliar.
Braces are most definitely not whitespace, even if they serve the same function as whitespace in Python.
if (a)
somestatement;
someotherstatement;
athirdstatement;
Which is of course horribly misleading unless you're careful because the second statement isn't actually conditional on a. Python forces the indent to match the semantics.More generally, yes, many people use auto-indenting and it works in Python in most editors. It's possible that you're using one where it doesn't work correctly but that doesn't mean that's true for everyone else.
What I said was that in Python's case, if I indent some code block wrong, I can't just fix the indentation using auto indenting in python, because the indentation is the semantics. There would be "nothing" to fix and the code would just run wrong. This problem comes up always when I copy paste python code. Of course I should notice that it's indented wrongly but that doesn't always happen. (And please don't advise me to never copy paste code. Life doesn't work that way.)
In my experience that last part is somewhat optimistic for the implication that someone would notice it immediately.
> if I indent some code block wrong, I can't just fix the indentation using auto indenting in python, because the indentation is the semantics. There would be "nothing" to fix and the code would just run wrong.
This is true in some cases — most of the time you'll get an indentation error instead of it running incorrectly — but also why many editors have an auto-indent option for pasted code and autoformatters covert things like code which is consistently indented to match your project's indentation size.
As for pasting, if you're using an IDE that ought to be taken care of for you. I use a vim and the sequence for indenting or de-indenting the text I just pasted is now second nature.
For the first 5 or so years of my career I used Algol syntax (and a bit of Lisp) exclusively. But having been trading off between C, C++, and Python since then I've come to prefer Python's syntax, though not its lack of static types.
It sounds great in theory, but it leads to problems in practice. If you copy and paste code from any language with C style syntax, it's trivial to fix the formatting based on where the braces are, and your editor can help. With python, testing a 10 line example program from a forum post can turn out to be quite difficult. Not to mention, if a tab character ends up anywhere in your program, you now have a syntax error that's completely invisible to the eye. Lastly, it makes it difficult to have clean syntax for multi-line statements.
It'd be nice if something else would carry the torch for nice AI library wrapper language...that alone is the only thing that can tempt me touch it.
- lack of proper typing - it pretends to have a type system but hardly enforces it anywhere at least with any of the common practices used by the community. 'quacks like a duck' is a completely asinine excuse for not properly using types and interfaces.
- lack of true multithreading (aka, GIL): if you can't use all the cores on your computer you drastically limit the scope of applications that can be implemented. No, I don't want to restructure my application into a distributed multiprocessing system just to access the basic capabilities of the CPU in my computer thanks.
- lack of true cross-platform. partly because of the previous point half of Python's ecosystem is written in C and needs to be compiled for (if not on) the target platform. Python tries to pass as a language with Java-like bytecode but the reality is it doesn't even come close to that. We regularly try to install Python tools and find that they can't run because some obscure shared library isn't available on our system, etc.
Mostly, I dislike Python because it seems to be such a trap for new programmers who learn programming in it and then think they should never have to learn anything else. Trying to coach people on my team who started with Python to use or learn anything else is extraordinarily difficult. I remember when Java was like that and it was ugly then - it's just as bad if not worse with Python.
Python passes method arguments by value, pushing them on the stack like every other language I've used. Because everything is an object in python the passed _scalar_ values are references (addresses), the objects the references refer to are on the heap. I sometimes think literally everyone should have to use C for six months, most of this stuff would stop being debated endlessly.
3.fizzbuzz # returns "fizz"
5.fizzbuzz # returns "buzz"
7.fizzbuzz # returns 7
15.fizzbuzz # returns "fizzbuzz"
as is the case with Ruby or Smalltalk. I think that the author's complaints center around Python behaving like C sometimes and Smalltalk other times.In particular, some operations happen according to the semantics of types and others happen according to the semantics of objects. At the lowest levels of Python, object oriented programming isn't available to do things like adding methods to Integer.
>>> i = 123
>>> i.bit_length()
7
>>> def f(self):
... return self
...
>>> (3).f
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute 'f'You can't call `f` as a method of any object, because in Python (unlike Ruby) the top-level namespace is unrelated to the namespaces of any classes.
Python integers are definitely objects:
(3).real # returns 3
(3).imag # returns 0
Python's lexer/parser made different choices than Ruby's, so the parens are needed here.+ Java doesn't promise "everything is an object." It has Base Types.
+ Python doesn't make a semantic promise that "everything is an object." Code for
(3).fizzbuzz => "fizz"
probably needs written in C because built-in types are closed and object literals use the built-in types. "3" cannot be forced to use a subclass of Integer.At the bottom, '3' is defined in terms of Types (i.e. "Built-in Types") not as an Object.
+ Ruby's semantic promise that "everything is an object" means:
class Fixnum
def fizzbuzz()
if self % 15 == 0
"fizzbuzz"
end
if self % 3 == 0
"fizz"
end
if self % 5 == 0
"buzz"
end
return self
end
end
And "3" behaves just like any other object. The "everything is an object" promise means that behavior as an object is a cross-cutting concern (or an aspect).Java calls them "primitive types", btw.
"Everything is an object" does not necessarily imply "all objects are always infinitely monkeypatchable at all times". It also doesn't necessarily imply "you can change how the parser interprets literals".
You're also going to be really mad when you learn about __init_subclass__ and the fact that Python lets you write a class that can't be subclassed!
__init_subclass__ is a perfect example. Not at the level of what __init_subclass__ does. But that instead of implementing private methods, there's are rules around double underscore methods and double underscore methods have different behavior (name mangling). They don't actually make the method private, the actual name is not the name in the source, and the name is not anonomized.
I can see a rationale for making some methods private. I can see a rationale for making no methods private. Python doesn't make methods private and then deliberately makes it hard to reap the benefits of this design decision. Instead of implementing the design intent of private methods, what gets implemented are impediments to utilizing the absence of private methods.
Mangling of double-underscore names is explicitly documented as existing "to avoid name clashes of names with names defined by subclasses". The use case there is a parent class defines a method called, say, "foo". Other methods of the parent call self.foo(). Then a subclass overrides foo() but changes the signature (say, by adding a new non-optional argument), and doesn't override the other methods that call self.foo(). Without name mangling or something like it, this breaks. If the parent class either names the method "__foo" or aliases a copy of the parent class' implementation to "__foo", and calls self.__foo(), name mangling ensures those calls will find the right (as in, compatible) implementation of the method based on where the call came from.
That's it. That's the one and only use case for invoking name mangling in Python, and it's a thing that generally you shouldn't be doing anyway.
Name mangling also isn't invoked for leading + trailing double underscore -- a "__foo__()" method would not get mangled.
The libraries available in Python, for everything touching data / scripting are just so powerful and well thought, that for many small projects, using Python is a no-brainer. The best thing is that those libraries are actually incredibly fast, while Python is supposedly slow.
Since so many Linux distros used Python 2 for system scripting, you still have to have a Python 2 sitting around on your system somewhere.
Then people came along with "python3" and (yikes!) "pip3", but now you might have python 3.4, 3.5, 3.6 and 3.7. The transition was pretty painful because of the Unicode thing.
It does make it painful, though, to give people instructions on how to use Python-based software products.
I am converging towards the solution, however, of installing Python straight from the python web site with the appropriate version, hiding that Python somewhere where people won't mess with it, then working out of a venv.
Even though I have trouble with the implementation, I like the idea behind pypoetry. If it were me though, I'd put in a real SMT dependency solver because you don't need to download a whole wheel to get the dependencies, you just have to look at the ZIP directory at the tail of the file and you then grab the metadata file and leave the rest.
When kivy officially supports asyncio and they make it a little easier to install cross platform (e.g. brew was down for me over the weekend) that will be nice.
Pyenv[1] is your friend.
pyenv install 3.6.4
1. https://github.com/pyenv/pyenv blah = 1
if foo:
balh = 2
print blah
...doesn't let me know I've made a typo the way the equivalent Perl would: my $blah = 1;
if ($foo)
$balh = 2;
print $blah;
Which outputs the following even without "use strict vars": Name "main::balh" used only once: possible typo
And if you turn that on (like all Perl programmers have, in all their programs, for decades): Global symbol "$balh" requires explicit package nameDon't think it would hard to implement a warning, without need for strict, as python already spits various warnings when given non-optimal code.
However, what would be the alternative to significant indentation? I really don't think C-style syntax would work for Python...
[1] https://www.artima.com/weblogs/viewpost.jsp?thread=147358
That is problem with every single programming language out there.
- Python array are not called arrays because they are not array in C/C++ sense. It's like saying in C++ lists are called vector and not lists/ArrayList etc.
- Python library naming is inconsistent like PyGame, Numpy etc. Most of these mentioned libraries are third party libraries. How can a language enforce naming convention or documentation of a library?
- If you pick all softwares and libraries for all languages, you can't guess uses for most of them just by name. What are WireShark, TimeMachine, ReactJS, Boost, Armadillo etc?
One of the reasons Python is popular is because it's easy to do a lot of things. Some python choices don't make sense technically, but they were made to make python as easy as possible. Performance was never the first criteria of Python (or ruby). My personal peeve is with mandatory indentation. But again, that's the idiom the language has adopted.
How so? With java you can get your distro version from the package manager or just download a tarball and setup your PATH. Maybe install maven. GCC and build deps is as simple as a xxx install build-essential/build-base and you have a C compiler for C89/C99/C11.
- The GIL (global interpreter lock), which can in some cases reduce your multicore CPU to a single core CPU.
- No multiline lambdas. You can't write a quick anonymous closure and pass it to another function, unless you manage to fit the whole thing in one line.
its really not giving language any usage benefits when you cannot use ternary expressions, ! as not, !=, increment operators.
Also, PEP8 is very very presriptive, you are disallowed to do violate any rule, even when if you need to to make the code better. (I feel shunned by the community for not liking 80 chars line hard breaks)
Python3 migration was not planned well - the lack of back and forward compatibility gave users a legit reason to stay with python 2 forever. I've seen some conference talks where end users/projects being shamed for not jumping on python3.
GIL in python is something that was talked about 5 years ago and will still be talked in 5 years. It limits the ability of python to reap the benefits of better hardware.
This may be minor, but I really missing ruby rich set of collection methods: take_while, group_by, sample. Yet I can see a point in extracting that to a external library.
Of course, all this does not mean python is not good. Builtin pip/venv, adrequate unicode in python3, some of fantastic libraries(numpy/scipy, bokeh) making it indispensable.
It's also a huge source of misattributed problems — I've seen more cases where a complaint about the GIL was really “my algorithm depended on a single locked data structure” or “I was calling a C library which isn't thread-safe” than where the GIL was actually the limiting factor. That's not to say that there aren't real problems for people who want to run pure-Python computational code (not e.g. libraries like Numpy or Pillow) but it also seems to be popular as the bogeyman to blame when someone doesn't want to run a profiler.
> This may be minor, but I really missing ruby rich set of collection methods: take_while, group_by, sample. Yet I can see a point in extracting that to a external library.
See https://docs.python.org/3/library/itertools.html and https://docs.python.org/3/library/random.html for the sample function. I believe the difference is mostly that the Ruby methods are on Array but the Python ones are seen as processors for iterables so they're in a separate part of the standard library.
GIL sucks conceptually but I don't think it's massively limiting in terms of future hardware. E. G. I just discovered you can throw a bit of python into colab and do ML on Google provided K80 GPUs for free.
Two completely different things I know but to me that is a better indication of future proofness in a way.
In this case the emperor has no visible scope delimiters.
I've seen several bugs in C++ and Java where colleagues have indented the code for the correct control flow, but misplaced the curly braces, resulting in incorrect flow. Granted, the two bugs that come to mind first are dangling-else problems that were fixed by inserting the optional braces.
I think that you and I both prefer auto-formatters to force indenting to match flow control. However, there's an argument to be made for actually enforcing it at the language level rather than at the tooling level.
$ python2.7 -m virtualenv .venv or $ python3.6 -m venv .venv
$ source .venv/bin/activate
// dev + test here
$ deactiveate
> This worked great until I started on a second project that needed Python 3.6. Two concurrent projects with two different versions of Python -- no, that wasn't confusing.
Note that Java also comes in various version of the compiler and jvm, and its common to have several versions on the same system. They are backward compatible but forward compatibility issues exist: your main server app runs on java7 but your batch process is running java9. you might not want to make java9 the default on the system without formally upgrading the server app.
* Syntax/spaces : you get used to it. Hey some people write code in perl :)
* Includes: In principle, this works somewhat similar in Java: You use imports and the imported package hierarchy can nest quite deep so you need to look.
> The import function also allows users to rename the imported code. They basically define a custom namespace..
This can be a good thing, it helps you prevent name clashes. C++ also lets you do this, its called Namespace aliases.
namespace fbz = foo::bar::baz; std::cout << fbz::qux << '\n';
* Nomenclature:
> In every other language, arrays are called 'arrays'. In Python, they are called 'lists'.
Thats because it is a list. Nodes are dynamically allocated and appended to the list. You can expect similar algorithmic complexity for the operations.
Python also has arrays if you want the better efficiency: https://docs.python.org/3/library/array.html
* Pass By Object Reference: same as java
author must be running an unusual OS.
Go ahead, get your jollies and rewrite it in C.
But will anyone else be able to read it and maintain it? That's the core issue.
A dictionary is an interchangeable word with hashmap in most situations. The string 'quirks' actually look to be pretty helpful.
Gripes I do actually agree with: the schism in versions, mutation from pass by ref (although this isn't different from JS, Java or any other OO lang). In my opinion you can add OOP in general to the list of gripes against python.
I complained about the whitespace thing back in 2002 before I really loved Python too.
And trying to make out poorly named projects as a Python issue? Please.
I think I could make a comment on every one of this authors 'gripes' but I'll just do one for now.
Does this author think a minor version update should immediately EOL the previous version (3.5,3.6)? And obviously this author never heard that Python 2.7 is EOL 2020 [1]. But you know just reading this author's problems that if Python 2.7 had been EOL right after 3 released, or if a minor version upgrade immediately EOLs the previous minor versions (seriously, how is this a complaint?) he'd be posting about that instead of these 'which version do I choose' issues.
I just honestly can't believe a developer would complain about maintaining code for 'too long'.
More likely the author thinks minor version updates should be backward compatible, like they are in virtually every other language? In most languages you'd be able to uninstall 3.5 when you installed 3.6, because you'd be confident that all your 3.5 code would keep working in 3.6.
> I was advised by one teammate that I needed to configure my environment so that everything uses the Python 3.5 base. This worked great until I started on a second project that needed Python 3.6.
Might be an issue specific to that company of course.
>However, Python installs in separate installations. My code for Python 3.5 won't work with the Python 3.7 installation unless I intentionally port it to 3.7. Enough Linux developers have decided that porting isn't worth the effort, so Ubuntu installs with both Python2 and Python3 -- because they are needed by different core functions.
The way he just segways from talking about 3.5 to 3.7 seems really unnatural, I think he's talking more about from python 2 to 3, since that's what the second sentence is about. All code from 3.5 should work 3.7, unless they were using some undefined behavior of some sorts.
A quick google reveals python 3.6 is fully backwards compatible with 3.5, and 3.7 has only 2 exceptions. The words async and await are now reserved. So even if he did have a problem with this, any ide, or even just a find replace in a text editor, would be able to refactor the 3.5 code to work.
Interesting; that certainly wasn't always the policy (e.g. I remember 2.4 -> 2.5 being a major, breaking upgrade) and maybe his employer never got the memo. If it's now the case that you can do fearless upgrades between minor versions then maybe that just needs to be better advertised.
The only thing that really annoys me about Python as a language (and it still is just an inconvenience and a matter of taste) is the file=module thing, I want to split a module into a number of files so I could define every class in a separate file without having to import every one from a separate module then, C# namespace model feels a way better. I would also love to see 1-st class support for type enforcing (i.e. if I define a "type hint" and a value fails to comply to it should raise a warning) and support for immutable variables (that can only be assigned once). Using 4 spaces per indentation level indeed sucks (I would prefer 2 spaces) but this is not a language problem and its seriousness is futlie.
This circle of "friends" sounds like a toxic environment where apologies are empty and contempt runs rampant.
Are they truly sorry for what they said, or are they sorry they said it to Kyle's face?
https://en.wikipedia.org/wiki/Perl#Early_versions
(P.S. yes yes perl 6 went kookoo crazytown, but that's not what he was talking about.)
I find it incredibly surprising that this person got a Computer Science PhD while confusing Python lists with arrays and not knowing that pass by reference semantics is by no means niche.
With python, not only their code is readable, but they also enjoy using it.
For more advanced programming however, it is a mess, and I understand the author. It limits your option so that there is basically only one way to do things (the opposite of Perl), it makes it annoying to write since even debug code needs to comply. But on the other hand, it gives you constructs that are very permissive. Pass by mutable reference is an example: there is no easy way to know what will happen to your arguments. You can completely modify just about anything so that even simple operations can do really weird stuff in the background. Flexibility is nice but it feels strange for a "clean" language to let you mess things up so deeply with very little safeguards.
Does he want the language to automatically deep copy objects every time they’re assigned?
1. Versions.
R gets updated quite frequently, and most users just update regularly. It is rare for there to be breaking changes. Packages often require the latest versions of R, so that is an issue.
2. Installation.
Well on linux distros R often has the same problem, you install R with the package manager and it isn’t clear which version it is. Some additional config is required to make sure you get the latest version. This is explained on the R core website. On windows and Mac, this isn’t an issue.
3. Syntax.
If you don’t like indents then R is your friend. The syntax is pretty flexible compared to python. For data science this is a plus in my book.
4. Includes.
In R, for larger projects the issue is dealt with by turning your code into a package, which is quite neat and tidy.
7. Pass by object reference.
R functions default to pass by value. You can still mess around with global state if you want to complicate your life.
I will say, though, that I don't mind Python calling things lists and dicts. Those are indeed common terms (both are used in Erlang/Elixir), and reflect the fact that there are multiple possible implementations (lists might be arrays, linked, double-linked, etc., and dicts might be hash tables, keyword lists, etc.).
And in C everything goes directly into your main namespace, the equivalent of 'form foo import *'. And even with single letter renames you can just quickly check at the top of the file for what the rename is, 'gg<c-o>' in vim and probably similarly easy in most people's editors. And really importing common libraries as single letters should be by convention in a codebase.
I really think that Python's import story is one of my favorite things about the language and it's something where I'm often comparing other languages to Python and finding that they're coming up short.
Are you import'ing a Module or a Package? If it is a package then maybe there's a magic file called __init__.py in a directory that might have an __all__ = [...] array or might just import some other modules but not contain an __all__ or it might import some names from some other modules. Or maybe there's a namespace package somewhere which has some behavior when there are directories that contain .py files but don't have an __init__.py (I honestly don't know the rules -- but I do know they can't be explained to me in a small number of words and that makes me insane!) ... And by the way -- every instance of 'import' used throughout your program is additionally beholden in its behavior to a global runtime magic state magic called the PYTHONPATH (that often has to get munged from the shell environment prior to invoking a python program ...) which is a list of path's that will be consulted/augmented differently depending on whether its a (module/package/namespace) import search to ultimately decide which set of files will be inspected ...
As a half-assed python developer (who is firmly in the 'I hate python camp') I honestly have very little idea how you are 'supposed' to use these mechanism when defining a package. I recently tried to figure out actual best practices so I could publish a small package -- I relied on lots of boilerplate copying from what seemed like knowledgable sources. I think I got something vaguely workable (https://github.com/breathe/NotebookScripter) but there's a lot of line noise all throughout the filesystem layout ... What is it about my Project name that python demands I should repeat it in the filesystem over and over and over again?
I believe this is actually the common pattern for laying out a small library ...:
Project/setup.py Project/Project/__init__.py Project/Project/SomethingToDoWithProject.py
- Special-snowflake, verbose ternary syntax (expr if cond else expr)
- (De)evolution of language features over time, such as format strings (from "%s" % val to "{}".format(val), and now to format strings f"{val"
- Poor design decisions all around, and stubborn in its refusal to admit wrong, such as the sudden but long overdue addition of assignment expressions
- Lack of control flow like switch statements, leading to data-structure abuse e.g. via dictionaries
The recent addition of assignment expressions provoked much discussion and much effort into its proposals, but, outside of Python's echo chamber, it's a language feature that most other languages have, and that should have just been included from the beginning. Instead, decades after the fact, the language is tacking on the syntax and pretending it's some genius new feature.
"PyPI (Python Packaging Index): said aloud by using its full name, or by spelling out the last two letters, as in "Py-P-I". Alternatives are to abbreviate as the "Packaging Index", or to use the colloquial nickname "the Cheese Shop" (which refers to the name of a Monty Python's Flying Circus sketch). Referring to the packaging index aloud as "Py-Py" is strongly discouraged, as "PyPy" is the name of a popular alternative Python interpreter."
See, for example, https://github.com/pypa/python-packaging-user-guide/issues/8...
Yeah, sure, you can complain about how strict it might be, and complain about how it can cause bugs when something is mis-indented.
But it's not like brace-languages are perfect either.
How about people who mix tabs & spaces within a single line because of lack of discipline or maybe a mistake from refactoring code? Imagine that nightmare of rereading that based on what editor you use.
Missing braces can sometimes be annoying and difficult to find, and placing the brace in the wrong place can result in bad code. Isn't that equivalent to a lingering whitespace in Python?
Yeah sure, you might say "Once you code enough, that usually doesn't happen" But Pythonistas can say the same thing with their indentation.
It's just a different way of programming. Get over it.
I've got to call out his point 7 in particular; it's so wrong it kind of makes me wonder what he was trying to say. He's calling out Python for not following a convention that doesn't exist, nobody else follows, couldn't even work, and which you wouldn't even want. What in the world?
Every time I set up an ubuntu machine for using at work, I end up compiling a few packages from source, a myriad of packages that are installed from app images or curl | bash installed binaries (like docker, ugh), and more PPA's than I care to keep track of and clean out. And now there's flatpack, snapcraft... packaging on linux is a mess currently.
Honestly, on workstations the rolling release model is the only one that makes sense.
But for python, with tools like pyenv, virtualenv, or even just using LXC/docker/vagrant/whatever, versions in the 3.5-3.7 range are a non-issue.
I agree the quotes thing sucks, though. I wish there was more enforced discipline on that.
I'd love to see a deep dive from an expert on why Python sucks, because I truly hate Python, but this isn't it.
Anyway, this all seems like a lot of nit-picking to me. Yes, these "issues" are real, they just don't amount to much. And basically every other language has an equally long list of similar nits you could pick.
If the author had called this "Reasons Python Sometimes Annoys Me" then I could get behind that. But none of this, IMO, reaches of the bar of establishing that the language "sucks".
Talking about how there's very little new software written in Perl now is definitely using post-hoc reasoning. There's probably a language named after a British comedy troupe that is somewhat responsible.
I was looking how to get first element of an array or get nil if the array is empty.
Python library doesn't provide this kind of functionality.
https://stackoverflow.com/questions/363944/python-idiom-to-r... offers multiple ugly ways of doing it.
For Ruby, we use `.first`.
In Ruby, the standard library is richer. A lot of libraries are "official" because Rails uses it.
> "If I have the choice between using some pre-existing Python code or rewriting it in C, I'd rather rewrite it in C."
I should have stopped reading there, because this shows just how set the author is in his ways. Both languages have a place, and if I'm writing a web scraper, you can bet your ass it's not going to be in C.
But it doesn't. In Python only modules, classes and functions make a new scope. A block doesn't. (And that's because it "forces" you to write small self-contained functions.)
It's funny that he also links to a documentation page that clearly doesn't talk about scope at all.
Personal reflection: I rarely rant publicly about something, but when I do I make sure that I'm not saying something stupid that a quick google search can refute.
Another problem is packages. Authors don't really care about fixing dependency versions (had trouble with celery, quart and redis so far) we have much less trouble with node in this respect.
plus sides are: vs code has been amazing. pipenv is almost good enough. Community is great.
Curiously, I never see the indent scope complaint about, say, Haskell. It's always just Python. I've wondered for a long time why that is. There's the obvious one, that Python is just far more popular, but I'd expect experienced people to have used or at least seen another language that does it.
Not that I was expecting depth from a site called "hackerfactor" by a guy who wrote something like "open source sucks", but seriously, this middle school level "sass" (and about a popular practice at that) shouldn't be near tech writing.
Should have stopped reading there. On that note, this has way too many upvotes for such a misinformed, badly written, sophomoric piece.
Lists and arrays are fundamentally different beasts, though. Shouldn’t most coders understand that?
Go: Something you see ends up as a compiler error.
Oft heard is the bellyaching cries of the proponents that try to conflate these two things, but they are not the same thing. One results in bugs, the other can be bypassed. Most languages opt for the second. (With Java for instance, most shops have a checkstyle gate in their build process).
That being said, if your alternative is C or PHP suck it up and stick with Python. There is no situation in which PHP is the appropriate tool for the job, and as simple as C is and as much as I love it you're much more likely to introduce weird bugs and create broken software if you're using C. Python at least has some basic memory safety, even if its tooling and versioning is all horribly broken. As a user of software (and also a devloper): please stop writing things in C and related languages, if I keep seeing segfaults in popular products with huge QA teams behind them, I'm going to go insane.
While clunky as a language, PHP's "environment" is better if you are doing web apps. Easier to install, including a test setup, more web-oriented libraries and functions, and a built-in HTML templating language for "view" pages (per MVC).
If your stack is designed right, you'll be spending most your time plugging in parameters to API's such that the language itself won't make a whole lot of difference. You should be mostly gluing libraries, not writing an OS from scratch. If I were doing the second, then Python would be the better choice.
I just thought it's a natural consequence of a dynamic languge. Defining a class IS running code - you can even decide to define or not to define something based on a runtime random roll.
i just made that up
Yes, these are all valid points, and I laughed. Holding up javascript (npm anyone?) as some kind of counter example that proves python is bad may have held some un-intentional humor as well. Don't get me wrong, I love js but it seems to me like if you took these same 8 points and made them about javascript it would be 100 times more damning.
Heck, I am even willing to count this great article itself as a reason to love the python community even more. Someone who hates python so much must do so from a place of extreme love.
Making a language more Englishy, does not make that language more readable.
I still have to learn what the hell the syntax is trying to express except the English makes it more verbose.
My dislikes:
- no pipe operator - no type checker (something Flow would do) - over dependence on whitespace syntax
virtualenv is your friend.
pyimport pygame
Name two things wrong with what I wrote above.
Gosh python must be a pretty good programming language.
Pypy is pronounced pie-pie
PyPI is pronounced Pie Pee-Eye
Isn't it?
seriously? the community norm is:
import numpy as np
and because its a norm, nobody is confused. But the author certainly is.
the whole list of complaints basically exposes his basic lack of familiarity with the language.
But I still love Python XD
1. Versions: Definitely an issue with Python, the v2 to v3 split was a huge mess and is Python's biggest issue IMO.
2. Installation: Pros and cons, what I find is that Python tends to be easy to set up, but that it does it automagically for you via pip, which means if you run into trouble it is harder than doing it manually
3. Syntax: Python's syntax is wonderful and I find these arguments mostly flawed. The space vs tabs wars are the exception and a valid criticism that the language doesn't force one or the other. As someone on the tab side, I don't run into his issue at all of accidentally putting 3 spaces instead of 4 in, but then I do run the issue of going against the language's grain so to speak.
On the other hand, his issue about deep nesting is a problem in any programming language - unless he is not indenting properly when he wants to, which is my guess. If that is the case, that will cause severe issues when anyone else tries to read it! Debug code without indents also sounds like a bad idea to me. And even if you aren't a fan of Python's whitespace, I find that there are other features, such as using words instead of symbols (and, or, not instead of &&, ||, !) that definitely make Python one of the most readable languages out there.
4. Includes: I'm mixed on this one. On the one hand, I don't understand the first half of what he is saying, I have to go track down includes in any language. In Python at least I only have to deal with one include, whereas in C and C++ I have to go track down two, the header include and the actual library. This mostly seems to be complaining for the sake of complaining.
The last bit I personally agree with though. I once worked on a project that would analyze other people's Python code. At first Python seemed to do this extremely well as it can take any Python code and change it into its abstract syntax tree for further analysis. However, I soon found that in order to do this, it would have to execute any global code while it was reading it! I'm definitely not a fan of how Python technically runs anything it imports.
5. Nomenclature: Definitely unfair criticisms here. First, Python's lists aren't arrays but are lists! Arrays are blocks of unchanging memory, whereas lists are a structure that under the hood points to an array, but that array can be deleted and reallocated to resize it. I admit, I've never liked Python's word for dictionaries, but I don't like hash either, I think map is the better term, but it's still just a single name of a concept, not a huge deal. His arguments about the names of libraries are stupid, they are third party libraries that Python doesn't even control! And as someone who is very picky about function and variable names, a library name hardly ever matters, especially as Python allows you to rename imported modules.
6. Quirks: I don't even understand what his complaint is here. Does needing to triple quote multi-line strings really bother him that much? The binary and raw syntax might be a bit confusing, but I'll take that any day over C++'s L"" for wide strings and _T() for strings that may be wide or not and are determined at compile time. The string vs unicode is a bit confusing, I'll give him that, but I think the entire concept is confusing in any language.
7. Pass by object reference: I actually completely agree with him here, though I don't know how Python would fix this. I think I can explain it better than he does though. The issue is that if you create a list "list1" and then set "list2 = list1", and then append the element "3" to list2, list 1 changes as well even though list1 was never directly changed. This was one of the biggest things I struggled with in Python until I learned C++ better which taught me how Python was working under the hood. Python makes you think that it doesn't have pointers, but the truth is that everything in Python is a pointer, which I found confusing relative to how everything else in Python was beginner friendly.
8. Local names: I don't really understand his complaint here, don't shadow names.
A number of the people with federal felony convictions, some having already gotten federal prison sentences to go with them, for their role in supporting Trump's dishonesty might quibble with the “doesn’t matter” characterization.
"Where's Commodore today? It died out as users abandoned the platform."
No. Commodore died because Irving Gould told Tramiel to pack his bags and go when Tramiel told him it wasn't okay to use company's assets like the jet as personal property. Once Tramiel was fired, the company was plagued by chaos and mismanagement, but it had nothing to do with how bad or good Commodore's computers were. It's the 21st century and people still write fresh software and design modern hardware for Commodore computers, so they've never been abandoned.
> Commodore created one of the first home computers (long before the IBM PC or Apple).
Commodore, Apple and Radio Shack all released home computers in 1977. Six months is hardly “long before”.
> But the Commodore PET wasn't compatible with the subsequent Commodore CBM computer.
He must mean the CBM-II, because the original Commodore CBM machines were just rebranded PETs.
When I went down to reason 8 I was already utterly disappointed and felt like my time was wasted on a ramblings of 13yrs old that wrote "hello world" in 2-3 different scrypt languages and already thinks of himself an expert and a _hacker_ (that's even in the website title).
Too bad it is a senior citizen that some people might have actually listened to and think he know what's he is saying.
My reasoning (please give yours below proper heading)- I'm deadly afraid of refactoring or even accidental indentation change.
I always forget at least one of these. You didn't have any of these goodies in good old perl (sob, sob)
(wow, this one got flagged pretty quickly, wonder why)
What should be the correct behavior when accessing a key that doesn't exists? If you want a specific default value when a key doesn't exist you can use the get() method.
Also ':' at the end of each line.
What do you mean? You don't need ':' at the end of each line, only at the start of a block (basically anywhere you'd use a '{' in other languages).
Undefined behaviour, i.e. the JIT generates code to 3D print a clay golem on your desktop that sets your village ablaze.
I like 'self'. There's a lot to complain about magical 'this' of every other language - especially in Javascript. Specifying self makes it really clear if it's a member method or a standalone function.
Python really likes the idea of 'seek forgiveness, not permission', aka. abusing try/except. But it's kind of nice:
# seek permission
if 'key' in map:
fn(map['key'])
else:
whatever()
# seek forgiveness
try:
fn(map['key'])
except KeyError:
whatever()
Just pray that 'fn()' doesn't also throw a KeyError.- The hideous __method__ and _private conventions
- A friend of mine was complaining about the implicit string concatenation: ["foo" "bar", "baz"] whoops forgot a comma and now there's a very hard to find bug
- pyc and pyo files littering the filesystem after running (yes, only a minor nuisance)
- import anywhere, the ugly __name__ = "main" hack
- GIL, reference counting GC, abysmal performance in general
- Dynamic typing, which is probably my most major complaint but I realize opinions differ
C and many other languages had that before Python, and it’s very useful to be able to split a single string over many lines in your source code. Python very wisely also implemented this feature.
> - pyc and pyo files littering the filesystem after running
Fixed in Python 3.
dictionary.get(...) is specifically for avoiding the exception, you should just get None if the key doesn't exist. Unless I'm misunderstanding you and your complaint lies elsewhere.
Don't get me wrong, there are plenty of things that are stupid/annoying with python, this comment however reads more like "python is bad because I can't write perl in it". I think that's why people are flagging it.
I didn't downvote you, but you present complaints without support or putting forward alternatives other than everyone should ditch Python in favor of Perl. 2 of your 3 complaints come across as shallow syntactic bikeshedding presented without support.
It would be interesting to read your take on the engineering problems involved, the design tradeoffs involved, and why you think Perl's decisions are more appropriate than Python's decisions, even for Python's main use cases.
Python rubbed me the wrong way when I first encountered it, then I really loved it for a few years. Now, I feel it's pretty good at what it does, but I find other languages more interesting.
Maybe you have some good reasoning behind your gripes, but you don't provide any evidence that you've thought much about them or put any effort into understanding the design decisions.
For your first complaint, I do agree that the explicit self parameter vs just making it a keyword is hard to justify in retrospect, but is it really raising your blood pressure, causing you to lose sleep, etc? Your use of hyperbole doesn't help your case.
I presume that making all method calls explicit is to keep scoping sane in the case where a subclass defines a method that happens to accidentally conflict with the name of a function called in some superclass's method. An alternative would be to use static name resolution to resolve the ambiguity, but then you'd have to be statically invoking metaclasses to figure out which methods were in scope, or you'd have to get rid of metaclasses and generally get rid of a bunch of the effort Python goes through to be very late-binding and generally dynamic in behavior.
For your second complaint, if you don't want the dict to throw, use the get method instead of the brackets operator.
For your third complaint, I'm not sure exactly what you're complaining about. Clearly not every line literally ends with a colon, and you're again making poor use of hyperbole. Guido has commented in at least one interview that most of the colon's aren't necessary to disambiguate the grammar, but they really do help beginners in particular read the code.
Showing more effort toward educating and/or convincing people would help avoid the downvotes.
self - the reason is that I forget to type it and causes me to go back and fix the method signature or method call (there are usually lots of them in the code, so lots of opportunities to forget self - hence lots of instances where it can be omitted so that it must be fixed, at least for me; the remark is a gripe about ergonomics of syntax)
The alternative: do a different keyword for method definitions other than def; (well, still you need something to differentiate between method invocation and global function call in an untyped language)
: at the end of function definition or a block - same thing.
KeyError if dict entry not found; most other languages I know would return None, so that's counter intuitive to me (I know about get, but [ ] is the default syntax for accessing dictionaries in most languages) I know it will cause an undefined method call to bomb, which is a good thing in my book; but I don't like it for data structure access - I also don't like exception handling in particular and prefer explicit checking of results (again matter of taste and habit + frequent cause to go back and fix it)
I think python is making several tradeoffs in favor of making the resulting script more robust (like going for exception handling) but it makes the experience of writing said script a bit more tricky.
$ cat > foo.pl <<EOF
use strict; use warnings;
{ package Foo;
sub new { my $class = shift; bless {}, $class }
sub method {
my $self = shift;
$self->{"hash"} = { "thng1" => shift };
my $key = $self->{"hash"}->{"thing1"};
$self->increment($key);
}
sub increment { $_[1] + 1 }
}
my $count = Foo->new()->method(33);
print "count: $count\n";
EOF
$ perl foo.pl && echo "Program succeeded"
Use of uninitialized value $_[1] in addition (+) at foo.pl line 12.
count: 1
Program succeeded
The debugger is telling us about an uninitialized variable in Foo::increment(), but the bug is really a missing dict key in Foo::method() due to a typo. We never checked if it existed, Perl didn't care either way, and the program didn't even fail. In code that's more than a page long, good luck spotting that...