Why Python Is Not My Favorite Language (2016)
zenhack.net
zenhack.net
WRT private class members, I can understand why this might be frustrating for library writers. But it's just so damned useful to be able to reach into a library and get functionality the library's writer didn't think I'd need, that I'll personally never consider this to be bad.
Multi-line lambdas would certainly be nice, but most of it can be handled with a scope-level function. It adds, on average, one line of boilerplate. As for syntax, no need to use Ruby's syntax, just incorporate parenthesis (a well established continuation construct within Python):
with(open("foo.txt"), lambda f: (
print(f)
print("I am a teapot")
))
The sinatra example doesn't really make the writer's argument about decorators to me; since the Flask example exposes a bit more functionality for only two lines of code - I now have a distinct app object, and can create more, or explicitly access attributes of the app itself. I can even add authentication to that function with just two lines: auth = flask.ext.httpauth.HTTPBasicAuth()
@app.route("/")
@auth.login_required
def handle_route():
return "I am a tea pot"
No, Python isn't Ruby; it doesn't support the same degree of metaprogramming. That's good, IMO. Metaprogramming is the source of exponentially more technical debt (and bugs) than is reasonable, frankly.As for the lack of a type system, yeah, that is definitely Python's biggest weakness for large programs. A lot of it is resolved with the type hints in Python3, but a lot of it is just Python itself. Love it or hate it, that's how Python is, was, and will be.
As for the example error - there is a typo. Of course it's going to throw a traceback. The error message is even more explicit than I expected, frankly. It pointed out the missing function name, which would make the typo rather easy to find.
I was ready to accept this as a matter of opinion, but then with this :
> Metaprogramming is the source of exponentially more technical debt (and bugs) than is reasonable, frankly.
Once you've jumped the shark and play with introspection and internals, are you really going to make the point that metaprogramming (or almost anything else, for that matter) should be banned ?
As a fan of the concept of the "Catfish" developer (even though I'm a "corporate drone"), absolutely.
Or, if not banned, kept to an absolute minimum. It's like the preprocessor in C. You can do anything and everything in it... but should you?
You can be exceedingly clever with metaprogramming, but clever code is exceedingly hard to read and maintain. Give me boring, explicit code any day of the week.
Metaprogramming also cannot necessarily be "hard to read and mantain". Take a look at macros in Common Lisp. They are almost the same as a normal function. Yet they do metaprogramming.
> Give me boring, explicit code any day of the week.
With macro metaprogramming you can eliminate boilerplate code / unnecessary copy-paste repetition of code, and this does improve maintainability of the code.
At the same time, replacing 10 repetitions of the same big boilerplate code (similar code, written with slight variations each of the 10 times) with 10 very simple (one line each) calls to a macro, improves readability: because you will easily read what is the difference between those 10 calls each.
I'm not trying to dunk on your editor, but people have had this problem solved since the early 90s. If your programmer's editor doesn't have some affordances for your language, then it's not a programmer's editor at all, it's a text editor.
In many cases with common macros I'd add decls for Cursive Clojure + Idea to let me autocomplete introduced bindings. For example, I helped my editor understand unique structure, e.g.,
(defauthedendpoint get-frobnaz [request user]
...re<tab>quest)I guess that without the right environment ifdef stuff falls out, but I dunno what to tell you there other than making builds contingent on the environment is a sketchy practice.
#include a.h
#include b.h
#include c.h
MACRO(varname)
welp.Seems, um, like a pretty reasonable idea to me if you care about C-style macros (I sure don't.)
Most modern editors are able to horizontally scroll and thus handle long lines in source code. That doesn't make a 500-character-long line of code acceptable by any stretch.
Either you want your editor aware of your language's semantics if you don't. I rather do, because it saves a ton of time and basic integrations are usually quite straightforward in 2017.
> Or perhaps better documentation on your program's part and/or better education/familiarization on my part is warranted.
Barring my opinions on this, I don't get why this would exclude editor semantics.
> Most modern editors are able to horizontally scroll and thus handle long lines in source code. That doesn't make a 500-character-long line of code acceptable by any stretch.
Okay, but this is a false equivalence. You're telling me not to use metaprogramming tools because they make it hard for a specific editor feature to be used in conjunction with them in the most difficult case for any system (introducing new run-time-dependent lexical bindings into a scoped block and handling that at compile time).
But unlike code visibility, in most good solutions exist to work with these systems (within reason).
There is no such problem with Common Lisp macros.
C programmers gluing together symbols from pieces so that MACRO(PREFIX) expands to some PREFIX_var or whatever are often not complete idiots; they are desperately doing whatever they can to simplify what they are doing in the best way that is supported by the portable language.
That said, my (admittedly few) days spent debugging bad macros were pretty damned bad. Having to inspect the results of the macro expansion and figure out what went wrong is not my idea of a good time. And that doesn't even account for reader macros.
It takes extra time and effort to make macros that are suitable for broader use (people other than the macro writer).
But ultimately, bugs in software happen, and bugs in macros can be a pain in the butt to debug. The extra steps to go in and expand the macro, and then translate the fix in that expansion into a fix for the macro itself gets complicated, fast.
It's no different from any other "input -> process -> output" computing situation where the output is wrong, the right output is obvious, and you have work back into the process to make that output come out.
You shouldn't be fixing macro output. If the macro is wrong, the first step is to find the simplest macro invocation which reproduces the problem. Use a dummy argument for anything irrelevant, and use an atom for any argument that allows one. For any argument position which accepts a list of items, see if the problem reproduces with an empty list, or a list of just one. If you can see the problem in (mymac (a b) c d), you have it made. Hey look, b is being wrapped in an extra list, and d should be quoted."
Boring, explicit code which depends directly on the unspecified, unsupported, implementation details of the libraries you're using ... ?
Lisps, C Preprocessor (within certain limits), and so forth are a bit different, since you can expand the macros without having to run the code itself.
Does that indicate a strength of Python or a weakness? A complete value judgement there. In either case, metaprogramming in Python (and many other languages, even those with support for metaprogramming resolution in their IDEs) is still simply harder to understand and debug.
So, while occasionally under specified, they are quite well supported and stable.
I do miss out on the fun of sorting out the requests dependency chain with every release of my code, but I'm mostly OK with that.
It's almost like the qualification is that the reader can't even imagine writing the abstraction and therefore they can ignore it.
Third-party resources are where it's at for taking advantage of things that require skills you don't have. I haven't written enough frameworks and abstract libraries to expertly debug, test, and write my own.
Indeed. You're more likely to be able to use and debug it if you wrote it yourself rather than relying on the weirdom of the crowd from SO.
> I haven't written enough frameworks and abstract libraries to expertly debug, test, and write my own.
I suspect you'd do just fine if you tried, assuming you don't try and displace django.
By the same token, if someone implements TensorFlow in a different language, there is little room for complaint assuming the same functional abstraction (and minimization of intellectual overhead) is present in that language's implementation.
Hacking something with private library parts doesn't involve any more "introspection" and "internals" than reading the library's code and finding useful loot to steal; metaprogramming is a completely different and orthogonal way to compromise code maintainability.
"Banned", no. Just that's not simply this big trove of goodies that it's often cracked up to be. And in particular, it isn't strictly speaking necessary or essential for a language to be successful -- or even among the top 5 considerations that make a language successful or not.
class PricePredictionCalculator():
def __init__(self, price_prediction_calculator_settings: 'PricePredictionCalculatorSettings'):
self.x = price_prediction_calculator_settings.x
self.y = price_prediction_calculator_settings.y
def calculate_something(self) -> int:
return self.x + self.y
class PricePredictionCalculatorSettings():
def __init__(self, x: int, y: int):
self.x = x
self.y = y
It was actually kind of nice. It allowed rapid prototyping without tons of boilerplate, and then tacking on the boilerplate at the end. Mind you, I was a data scientist, not a full-time engineer. There was also a lot of room for stylistic differences and varying opinions. class PricePredictor:
class Settings:
def __init__(self, x: int, y: int):
self.x = x
self.y = y
def __init__(self, settings: Settings):
self.x = settings.x
...I'm asking because I'm usually torn between short names that assume the reader can understand them and long overly descriptive names that (in theory) require less implicit understanding.
Is the name price_prediction_calculator_settings too long? If we call it settings I can see an issue come up if we need to pass in another type of settings object calculator_format_settings or something like that.
There's no IDE support for "plain" dictionaries, they resulted in code duplication and broken encapsulation since you can't add any logic to a dictionary, and they put unnecessary cognitive load on the developer by forcing them to keep track of which fields go in which dictionary.
I saw it as a form of design-by-contract and self-documenting code. The app I worked on had a ton of these mappings being passed around between different subsystems and it was becoming nightmarish to deal with. With this style, if you were on Team A and needed to plug your work into Team B's FooBar class, all you had to do was look at the FooBarInputData class to see exactly what the FooBar class needs.
The name ``settings`` is too generic. There's probably a better way, but to demonstrate that we'd need a real example and not hypothetical.
The other general issue is why have the redundancy? There's only one settings object here. If you need to pass something different in later, refactor! Remember the principle of You Ain't Gonna Need It.
Anyone who says his behavior hasn't been troublesome in #scala at times over the last 5 years isn't paying attention.
I still remember my first run in with him where he demanded I take a "test" to let him "grade" me. In the middle of a larger conversation about a sbt bug.
At this point, The Zen of Python has been absorbed into many late-gen programming languages; it's easy to see a heavy Pythonic influence on Swift and Go, for example, and some languages, like Nim, even draw the parallel as a promotional thing.
Sadly, the technical underpinnings of the Python runtime itself have not kept up, and it leaves people asking why they shouldn't just enjoy the same advantages they'd get with Python through a newer language with a modern implementation, providing better performance and package/dependency resolution. As much as it pains me to say it, even cutting-edge ECMAScript code can be made to look pretty Pythonic these days.
Python will always hold a special place in my heart, but I'm not sure that a Python implementation is the obvious choice for a dynamic application anymore.
def print_each_twice(xs):
map(lambda x: print(x) # whoops, impossible
You can define a local function: def print_each_twice(xs):
def print_twice(x):
print(x)
print(x)
map(print_twice, xs)
The former is more intuitive to write. The latter reduces nesting, makes you name the function, and improves readability IMO. It's currently easy to scan blocks of Python and see each indentation level starting with one of a few keywords (class, def, for, with). Adding multi line lambdas would make the ultra simple structure harder to quickly parse.I don't want this though. This doesn't do what multi-line lambdas do. It can never do what lambdas do. It's not a lambda function if it's named and fully pre-computed.
> Adding multi line lambdas would make the ultra simple structure harder to quickly parse.
This is just about the least compelling argument I've ever heard. "The parser writers are not going to have fun."
It's an extremely contrived example, but from the code I posted, it should just be:
for x in xs:
print(x)
print(x)
Much easier to read because there's only one way to write it. Hence GVR shunning map/reduce/filter.Lambdas are everywhere BUT Python now.
Javascript is even pushing forward with more sophisticated constructs to help them build custom monadic constructs, and shipping a super useful monadic construct in syntax. C# has been doing that for years. Java will soon as well.
The rest of the industry has moved on from this conceit and you can't really argue that it's made environments like Javascript overwhelming to newcomers.
If Python 3 could just get over this conceit, its maintainers could start solving major Python problems really quickly. E.g., Python's janky iteration primitives could be replaced in an afternoon or three with equivalent but cleaner and clearer options.
It doesn't really matter, except that the browser's dominance as client-side platform is the cause of JavaScript's current ascendancy, rather than programming language design decisions of any sort. This is a refutation of the claim that you made saying "the last 5 years of software development and the remarkable growth of Javascript" make a compelling case that van Rossum is wrong about multiline anonymous functions in Python. Classic post hoc ergo propter hoc fallacy.
If you're going to stick by the argument that somehow the support of anonymous functions is responsible for JavaScript's recent successes with respect to Python in terms of adoption and pace of development, then you'll have to explain why Lisp didn't take the world by storm when they were introduced nearly sixty years ago.
Or, of course, admit that whether or not a particular programming language conforms to your personal preferences has relatively little bearing on the success of that programming language in the world at large.
>>> def splatter(t):
... def f(): print(t)
... return f
...
>>> beep=splatter("beep")
>>> boop=splatter("boop")
>>> beep()
beep
>>> boop()
boop
That's the problem with Python, everything is dynamic.Still, the existence of this sorta demands we ask, "So why can't we use it then?"
If I can simulate anonymous functions with locally scoped functions that then behave as lambdas then what hill is Python dying on here? The "we don't want to submit a patch to make our parser do this?" Hill?
It's definitely possible to do. So...
But your complaint isn't without merit here, a profusion of unique nameless verbs can get overbearing sometimes. It's often better than the alternatives (e.g., dependency injection vs lambdas in an environment) but Haskell, ocaml and lisps all seek to have a small but very robust set of combiner and base primitives to help make this kind of code avoid a complexity explosion similar to OO ontology explosions.
I can only really think of one instance where I've used this, and it was to work around an outright bug in the library (which was unmaintained, and I was using out of a certain amount of desperation).
FWIW, most of the complaints around private in my post would actually be solved just by having a separate namespace for "private," even if there was a back door. I'd be pretty willing to forgive that.
> Multi-line lambdas would certainly be nice, but most of it can be handled with a scope-level function.
In the absence of the with statement can you really imagine yourself writing this all the time:
def _with_body(f):
print(f)
print("I am a teapot")
with(open("foo.txt"), _with_body)
?Strikes me as pretty ugly, despite not actually adding much code.
> As for syntax, no need to use Ruby's syntax, just incorporate parenthesis (a well established continuation construct within Python):
Yeah, that's a bit nicer.
> As for the example error - there is a typo. Of course it's going to throw a traceback. The error message is even more explicit than I expected, frankly. It pointed out the missing function name, which would make the typo rather easy to find.
Yeah, that was kinda my point; I was comparing this to what happens if you try to "take duck typing to heart", where you don't get the error until you try to call request. The earlier you find out about mistakes the better.
Honestly, no. But not because of any measures of beauty, but because I just don't do callbacks all that often.
try:
f = open("foo.txt")
print(f)
print("I am a teapot")
finally:
try: f.close()
except: pass
is what I would be more likely to write (and did write, before context managers were a thing), if the "with" statement didn't exist. It's ugly as sin, but I can put it in front of anybody and they will understand what's going on. I can also tell at a glance that it is correct.I'll second that. I'll share my own experience to give some perspective. Older versions of the JDK had an LDAP library that for some reason I can't understand (actually pretty sure it was a mistake because it was changed in subsequent version) specified a File as the type instead of a Stream for its configuration file. There was no way for me to modify the configuration during run time without restarting the JVM. This wasn't acceptable to the software we were writing because it supported multi-tenancy.
What did I end up doing? I used introspection to grab the "private" configuration table (Vector or Hashmap?) and wroten an API around it to allow modification on the fly.
Lesson here is that "private" things aren't really so private and Python style discretionary "private" variables would have required a lot less work.
Also, preach on duck typing it's a python myth that is absurd. If it quacks like a duck means I had to figure out what it was.
This language is slowly destroying my soul, one painful unit test at a time.
Maybe you meant to say TMP?
TMP, on the other hand, is an entirely separate language within C++ that allows you to create monstrous, arcane code that's incredibly hard to debug or understand. Some of the STL uses TMP. But it sounds like custom TMP is the thing you don't like.
Even languages with labeled effects allow for this sort of construction effect. If they can properly sequence and isolate it, they allow mutation as well!
If you dislike call-by-name, why is Python's practice of insisting all non-trivial functions be called by name is not distasteful to you?
No, I was trying to isolate the important part. "@f def a: return b" would be the Python syntax, and I think it's misleading as sugar for, hmm, you'd have to write it as "a = f(lambda: b)" (hmm, I'd forgotten how different defining a function "natively" or as a lambda value are in Python).
You seem to be using a different definition of call-by-name to the one I'm used to. What bothers me is that a decorator can make a function behave completely differently (e.g. its body might never even be executed) but the syntax doesn't look like it can make that big a change to the function.
I guess in Python land, blueprint's entire purpose is to selectively fire your handlers.
Litterally.
You can take any code with decorators and replace them with this syntax. If you have arguments you must call the factory first, that's the only exception.
def make_money():
blockchain_pyramid_scheme()
into: @with_jazz_hands
def make_money():
blockchain_pyramid_scheme()
or equivalently: def make_money(*args, **kwargs):
with_jazz_hands(make_money_impl, *args, **kwargs)
def make_money_impl():
blockchain_pyramid_scheme()
Both ways wrap with_jazz_hands around make_money without altering the call sites of make_money.Please, please learn about what higher order functions are in a more general setting before invoking them.
This "please, please learn" bullshit comes off as so smug and patronizing, did you realize? And when you're misunderstanding something it's even worse.
For example, I used a decorator the other day that I called `@logperf` that I can pin to any function, which will `logger.info` the approximate time it takes to run the whole function. It doesn't mutate what the function does, it just adds a side effect.
Literally, please do not do this shit. Your function should be modular and able to be useable with or without being decorated.
So we're two! I'm trying out Rust for a side project now and so far it's fun. Lots of compile-time checks, non-crippled lambdas, and you can use itertools too!
I don't quite understand the author's complaint about this specific case of decorators. An initial read makes me wonder why he doesn't use dynamic routes, which is where the decorator / function-name pattern really starts to shine.
At the end of the day, Python is just an abstraction. It isn't destroying your soul, or doing anything else to it.
OTOH, your conscious decision to stick it out in a job that makes you work with a language that you find "absurd", and where people do things you "absolutely loathe" but have to put up with, anyway? That's what's destroying your soul, man.
Preach it. Also, you don't have to worry that a method whose signature is `public int DoWork(string input)` actually expects char[] as its input and returns a long, the way you do when the types are only documented through comments.
Having a required type system for this huge part of the community would be a great let down.
Those criticism come from people comming from a strong dev background. They completly ignore the rest of the world.
That's why having the current OPTIONAL type system is so good. Because you can use it and benefit from it, and many do, but you don't screw half of your community.
Python strength is versatility. It's not the best at anything. But it's very good at most things.
That's why if you know it, you can solve most problems decently. That's what makes it fantastic.
I'm still waiting for people doing data anaylsis in Go, sysadmin in JS, 3D scripting in Haskell, batch testing in erlang or geography in lisp.
Because Python... well it can. And AI. And Web. And embeded systems. And IoT. And... And...
They have 30x 200 lines longs scripts and couldn't care less about code quality. They just want the result.
Instead of the compiler telling me exactly what something is, what I can do with it, and if it makes sense to pass it to some function, I have to figure all of that out myself, greatly slowing me down.
Edit: It occurs to me I have yet to hear an answer to my question of how types hinder anything, especially if they are inferred. The only exception is the trivial requirement of sometimes having to cast between numeric types.
Do you realize how insane that sounds? Static typing makes programs harder to debug? Harder to maintain??
On the contrary, static typing helps debugging and maintenance: changes that break invariants are more likely to be caught by the type system.
This speak of tradeoff sounds wise on the surface, but this is hardly a tradeoff at all. For many people (including me), a good static type system makes prototyping and maintenance easier.
> Yeah, you [DarkKomunalec] basically [use static typing to] make initial implementation easier at the expense of maintenance and debugging
It was not clear that "Yeah" was an approval (and not a dismissal), and it was not obvious that "you" was a general "you" (and not a personal "you" directed at DarkKomunalec).
Nevertheless, you were still talking about a tradeoff, and I personally see none: in my experience, dynamic typing makes initial implementations harder (or longer), because so many errors are caught too late. Static type systems have a much tighter feedback loop.
However, it isn't free. Type theory is a kind of math that most people have very little exposure to, so there's going to be a lot of work in order to start becoming proficient.
Additionally, there's more than one type of type theory. Are you using a system F like system? Are you going to have sub-typing? Is it going to be structural sub-typing? Maybe you want row polymorphism. Is there going to be a kind system? What about higher order poly kinds? Dependent typing? Intensional or extensional?
Additionally, there's more than one type of implementation of these type systems. Ocaml functors ... is it a generative or applicative functor? Haskell ... are you using gadts, functional dependencies, or type families?
In the end I think that type systems will eventually be able to get you a completely typed experience that feels exactly like a completely dynamic experience, but with compile and design time checks that always make you feel good about the experience. However, I don't think we are quite there yet and I don't think you can expect everyone to be able to take the time to get sufficiently familiar with an arbitrary type system in order to be productive with it.
Sorry to be pedantic, but you probably mean static type systems, not strong type systems.
See: "What to know before debating type systems" - http://blogs.perl.org/users/ovid/2010/08/what-to-know-before...
A normal person doesn't care. He just wants, and expects, 2+2.5 to yield 4.5. He doesn't want to use a cast, or write the 2 as 2.0, or use some sort of baroque type conversion procedure, or anything like that.
This answer is not Python-specific, of course, but it's a good example of the overhead that gets introduced when a language becomes too type-happy.
"You can only push off the complexity for so long if you want to do things that aren't trivial."
There are a lot of things that aren't "trivial" that nonetheless don't require a totalitarian type system.
You are living in your bubble. The bubble of people who knows what they are doing.
Get out, you'll be surprise how much amateurish the world is.
Yet it runs.
The common response is, "But then I have to learn something new." But this is the curse of technology, and pretty much inevitable. Some learning needs to occur because tech changes so fast.
But you don't, necessarily. Dealing with type wankery takes time. And no, it has nothing to do with "learning something new". Languages that tout "type safety" have been around since at least Pascal (47 years old)... arguably even before that, with some of the Algol variants.
Yet they've never made it to mainstream acceptance. It's not even about hype -- Pascal was pushed HARD, by just about every university. Where is it now? Turbo Pascal had a small degree of success, but that's only because it chucked a lot of real Pascal's rigidity out the window.
If so, I counter: the last 3 years have been a series of breakthroughs both in terms of technology and social acceptance of typed programming. TypeScript is the rapidly growing language, Haskell's never been more boring to use, Scala's edging out Clojure even though it has very fragmented community leadership. C++ has adopted a lot of powerful new features and you're seeing functional programmers speaking at C++ conferences because the folks there can use it. Java has more capable and composable lambdas than Python.
Systems not using these techniques are plagued by security flaws, while those that are work on stabilizing performance under various workloads.
It's never been a better time to be using a strong, static type system..
I would make it "Pascal was never popular", but yes.
"If so, I counter: the last 3 years have been a series of breakthroughs both in terms of technology and social acceptance of typed programming."
This isn't the first rodeo for many of us. "Compile-type static type checking will solve all of our problems" is an idea that's come around repeatedly. Outside of a few niche applications, it never works, or even catches.
As for the supposed booming popularity of TypesScript... dude, TypeScript doesn't even make the top 30 on GitHub. It's less popular than assembly language and Visual Basic.
Then why... why bring it up? Should I discount all of dynamic typing because Io and Pike never took off? C++ did stick around, Oak became Java. APL is still in active use.
> This isn't the first rodeo for many of us. "Compile-type static type checking will solve all of our problems" is an idea that's come around repeatedly. Outside of a few niche applications, it never works, or even catches.
"It never works" is a pretty bold claim given that the majority of code you interact with on a daily basis has SOME sort of type system. I'd say C++ is better endowed than most in this dimension.
> As for the supposed booming popularity of TypesScript... dude, TypeScript doesn't even make the top 30 on GitHub. It's less popular than assembly language and Visual Basic.
My dude it would be extremely suspicious if it did. Instead, look at the growth stats: https://octoverse.github.com/. It's the fastest growing language that has non-trivial presence on github (and of course, that's the correct way to phrase it, a new language appearing can appear to have quadruple-digit growth percentile).
This seems profoundly disingenuous. Is that your intent?
As for "the case", Java does reduce the # of NPEs you get by not letting you deref invalid keys on objects, and it makes it easier to handle objects in the abstract.
But moving on to your claim in this post, nobody ever said "compile-time checks eliminate errors altogether." What they do do is reduce errors and completely eliminate certain classes of errors. They also make maintenance and debugging much easier because they define clear expectations for the interfaces of their arguments and return values. The length of stack traces is a completely orthogonal concern.
It's pretty rare to have measurements that are accurate to more than a few decimal places.
Having your share of holiday costs come out as NaN is fiddlier than getting an exception at the point where you actually divided by zero.
The "type safe" guys like to pretend that their approach can catch all that stuff at compile time. It does catch a certain class of error, but at the cost of making the code take much longer to write. That doesn't work in a world where your competitor is iterating five times while you're still building the first one. Excellent way to get your milkshake drunk, that.
Sure, the point is that using integer arithmetic for integer calculations gets you better error reporting that saves you time when tracking odwn other bugs.
> The "type safe" guys like to pretend that their approach can catch all that stuff at compile time. It does catch a certain class of error, but at the cost of making the code take much longer to write. That doesn't work in a world where your competitor is iterating five times while you're still building the first one.
My experience is that I can iterate a lot faster if the compiler's able to help me catch errors faster. It doesn't slow down writing the code; almost anything I'd write in say Python translates directly into the same Scala. (I guess the language pushes me into defining my data structures a little more explicitly, but that's something I'd want to do in Python anyway if only for documentation reasons).
So does anyone who's done math. They also know 3/5ths is different. It's not unreasonable to ask for addition to be defined in a reasonable way though.
> This answer is not Python-specific, of course, but it's a good example of the overhead that gets introduced when a language becomes too type-happy.
Besdies OCaml, who actually does this for general programming? I can't think of many examples at all.
P.S., "A normal person doesn't care. He just wants". Stop this. The community here might give you a pass for being tedious and correct. Being tedious and incorrect is pretty much unforgivable.
Hmmm... I could have had an undergrad math degree in addition to the CS degree if I'd stuck around one more semester, but decided to head off for CS grad school instead. And yeah, I understand cardinality, and why there are more real numbers than integers (and could even write the proof for you from memory).
I also completely understand that 2.5 in a computer isn't actually represented as a non-integral real number, or anything like it. The computers we have now can't represent arbitrary real numbers (quantum computers can, I think, but I haven't studied those in any great degree). At one time I even wrote some non-trivial asm programs that ran on the 80x86 FPU, but I'd have to do a fair amount of review before doing that again.
So yeah, I'd say I've both "done some math" and have a good handle on how integers and floats are represented in a computer.
That still doesn't mean I want to have to put in a freakin' type cast when I add 2 and 2.5 on a pocket calculator. Nor does anyone else.
Or is this about Pascal again? Did ocaml bite you and you still have a mark? I'm trying to give you an opportunity to suggest this isn't a straw man. My most charitable hypothesis is that you really don't know much about modern static and strong typing techniques.
Everyone's numeric tower accounts for this and does sensible (if not optimal) type conversions. The best of the bunch give you fine grained control on what happens when. That something must happen is inescapable.
Types are great for large projects, but tend to add verbosity and development time for small scripts (thus why there are so few strongly typed scripting languages). SML/ocaml show that there is a nice middle ground where most types are inferred, so you can keep your types without too much work. Unfortunately, they've been around for decades with little usage in the profession.
My first programming job was writing map reports (choropleth maps) using AutoLisp.
In fact Lisp can be much simpler than Python, it is just like using a RPN calculator if you don't want to dare into macros and other advanced stuff.
The question is more about "what kind of problem do you want to have and what price are you willing to pay to solve them ?". After that, choose the tech that matches this wish.
For me the iteration speed, the ease of adding new members and the flexibility are key.
But what I discovered is that most people just code bad Python and say they have problems, just like most people write bad C/java/whatever and blame the language.
You are supposed to writte Python in a certain way, like any language, to scale.
Nowaday, ruby is dying in his RoR and Japan niches so the question is moot.
For small, local needs to pass around data, a plain dict or tuple usually suffices. If you need stricter contracts, you define good classes and interfaces.
There's a need for discipline in your coding style, but I've frankly not found this to be an issue with moderately competent coders who understand this. Yes, you can shoot yourself in the foot with this, but if anything those bugs are usually obvious and solved in the first pass of testing.
Yeah, but they're often solved after the code goes into production and doesn't work, rather than before the code is ever committed.
> Having a required type system for this huge part of the community would be a great let down.
... Why? These days types generate much more code than they cost in modern FP languages. E.g., https://github.com/haskell-servant/example-servant-minimal/b...
> That's why if you know it, you can solve most problems decently. That's what makes it fantastic.
Most of the things you're talking about are available in other languages. And quite frankly, if pure versatility and available open sourced code is your argument? Then why aren't you using Javascript and nodejs?
Name a general domain that I can't find at least one or more well-maintained projects supported on NPM. I dare you.
> And embeded systems.
Who.. who is doing IoT and Esys in Python outside of toymakers?
Aren't most IoT things basically toys anyway?
one is named
> Maybe, not sure how that is relevant.
Man, it was your dare!
Please tell me what pieces you need and I'll do my best to make good on my claim. At a minimum, tensorflow, nltk and spark bindings exist. And in fact, the popular notebook software packages are reaching out to other runtimes already.
That NPM has churn is an example of how massive that ecosystem is compared to PIP, which is comparatively microscopic and is much more dependent on specific corporate actors to continue investing.
Javascript is a terrible to do data analysis, given its inferior numeric data types.
I'm still amazed that ya'll put up with Python's trashy numeric tower, coming from other contexts.
More info on this in the pep: https://www.python.org/dev/peps/pep-0484/
Worse, I didn't have the docs -- just the header file. That one cost me a fair amount of head scratching before I figured it out.
I've written Python for several years, and as a result the way I reason about problems have been heavily influenced by it.
I'm fluent in Ruby as well, but it takes mental effort for me to conform to its "flow", for lack of a better term. My time in Ruby has made me a better Python programmer, too - I understand more fully the idea of a DSL and how it should work. Ruby is great for writing DSLs and writing concise code that's guided by the language of the domain in which you're working.
I'm competent in Clojure, which if nothing else has given me a distinct distaste for impure functions - if a Python function takes an array, it should not modify that array in place unless it's very clear from the name that's it's going to do so, and a function should not both modify its input and return it.
At the end of the day, Python is still the language that I reach for whenever I'm writing pretty much any personal project. It's clean, easy to read, and the overall feel of the language guides me to write maintainable code.
For instance, `1 + "hello"` will throw a `TypeError` at runtime because the `+` operator is not supported for use with `str` and `int` types. In a statically typed language, the code would not run or compile unless a `+` operator has been defined that takes a `str` type as it's first argument and an `int` type as it's second argument.
I guess you could say that a `TypeError` at runtime isn't really "type checking" in a sense. I guess it would be more like "type enforcement".
No, you're referring to type-related errors at runtime. That's not checking. Nothing is checked. Code breaks and may recover, but it has no idea what the types of the arguments to the offending expression were, only that it didn't work with that bit of code.
This is not type checking.
> I guess you could say that a `TypeError` at runtime isn't really "type checking" in a sense. I guess it would be more like "type enforcement".
All it does is say, "This code cannot execute with this implicit prior state." It has nothing to do with types except in the most tangential way.
1 + 'hello' resulting in an Exception would be a product of dynamic type checking.
A language would not be able to explicitly throw a type related error unless it had some information regarding the type of the data.
Most resources show little confusion on this, I think you are wrong. https://stackoverflow.com/questions/1347691/static-vs-dynami...
Incorrect. It might be the product of a run time type check. It is not inherently so. You ca't even be sure you actually had a type error when you get a TypeError.
But even then, this is using "type check" in the most vacuous and equivocal way possible. It's not the concept most people are referring to.
> A language would not be able to explicitly throw a type related error unless it had some information regarding the type of the data.
You (and this resource to some extent) are confusing strong vs weak typing with static vs dynamic typing. A + method might have a type check embedded (esp python since + is magical), but it's actually quite rare of that a.foo(b) is anything other than an assertion that the object A's vtable-analogue has an entry.
This is sort of dynamic typing, in the sense that I can think of formalisms that model this (named extensible row types come to mind), but this is a profoundly useless defintion of "type."
I'm not sure why we'd tolerate redefining type checking to a worthless concept and then using that divergent definition to imply that it's unhelpful. It's 2017, functional languages are fully baked. Powerful static type systems exist even for the Javascript environment and they are taking over that ecosystem rapidly.
There are many more important problems with Python: packaging, no way to know which exception you are gonna get, no nice async web framework and so on.
But this? You have those in any languages.
virtualenv helped a lot when that came out but it still doesn't save you from shared system libraries and dependencies
> At one point a simple letsencrypt refresh ended up re-installing some critical python component eventually resulting in a full re-install of a server.
The letsencrypt developers explicitly recommend using a virtualenv to run the certbot script.
The best part of virtualenvs is that you can almost use them like little containers. For instance, let's say you have a script running a web interface and another script that performs data calculations both on the same server.
Your web application can have its own virtualenv and be listening for proxied requests from an nginx instance. The data processing script can be running in it's own virtualenv as well and listening on a local unix socket for incoming data to process. The data processing script is 100% independent of the web application script and vice-versa. They each have their own interpreter and dependencies. Hell, you could even run your data processing script in Python 2 and your web application in Python 3 if you needed to.
Yes, but virtualenv is not always available and updating a certificate should not require a large amount of software to be installed on the sly on a machine, it should just upgrade the certificate and be done with it.
``` pip install virtualenv
virtualenv -p python3 venv ```
If you have a heavily locked-down server or something, talk to the administrator.
> updating a certificate should not require a large amount of software to be installed on the sly on a machine, it should just upgrade the certificate and be done with it.
I respectfully disagree. First, updating a certificate can be done by hand without the use of any software. The point of letsencrypt is to automate the process. Automation requires software. If letsencrypt were written in C you would still need to ensure that the executable was compiled for your architecture and that you have the correct header files available in the correct locations.
I'm also not sure what you mean by "on the sly" here either. If we assume that you mean that the letsencrypt package automatically creates a virtualenv, how is this any different from postgres installing libxml2 as a dependency for example?
> First, updating a certificate can be done by hand without the use of any software.
Yes, I'm aware of that.
> The point of letsencrypt is to automate the process. Automation requires software.
Exactly. So, how difficult can it be to upgrade a certificate that was already there, nothing on that machine needed 'upgrading' over and beyond the certificate, especially not without doing so in an irreversible way. All the software required to do the upgrade was in place because it worked 90 days before then.
You'll have to explain. Any problems that arise would be problems that would arise with installing any package from any packaging system. I fail to see your point. Errors and bugs are always possible in any situation. This isn't really an argument against vurtualenvs, it's an argument against software in general.
> Exactly. So, how difficult can it be to upgrade a certificate that was already there, nothing on that machine needed 'upgrading' over and beyond the certificate, especially not without doing so in an irreversible way. All the software required to do the upgrade was in place because it worked 90 days before then.
When dealing with security measures such as SSL, it's extremely important that all packages involved in the process are secure and up to date, Therefore, it makes sense to me that an SSL library would want to ensure that all of it's dependencies have the latest bugfixes and security patches.
- no clean way to freeze / update
- no good way to distribute a standalone executable
- binary packaging is still a mess
- big libs like GUI framewok are still terrible depend on
- compilation solutions like the awesome nuitka requires a lot of manual work
- too many things to know
For a beginer it's hell. I haven't been a beginer in 10 years, but I train people in Python for a living, and I know it's a pain point.
- pip and setuptools maintainers break everything quite often*
- basically no mobile support what so ever for packaging
(* quite often you say, skeptically? Well, they completely screwed it up twice this year already. That's twice more than any other package manager I use)
I'm on Win10, and pyinstaller made it easy to create an .exe for Windows, but I could find no way on Earth to assemble an executable for Mac.
As it happened I just asked a tech-savvy colleague on a Mac to use pyinstaller on their machine, and it worked, but still - I'm really impressed with Python generally, but this seems like a surprising and considerable oversight.
I just thought it was interesting quite how hard it turned out to be! As someone who's usually on the games/VR side of things, I may have taken some of the magic that Unity, for example, uses to compile to all sorts of platforms for granted.
Other operating systems apart from Mac OS should be much easier to run in a VM, and where they're not, I'd blame that more on the creators of the OS's themselves rather than on Python.
Are you a beginner in general maybe? Because that's either arcane, damn difficult to setup or nigh impossible in tons of other languages too -- even ones that actually do produce executables to work (which Python by default does not).
(And of course you can always just create the executable on a vm -- no need to buy a computer running the other OS).
But my work has, until this year, rarely involved compiling software for other people to use. So that may be the noobness you're detecting.
Whereas, from my experience, it's usually a pain in the ass to set up.
But hey, you can use VMs to hit a lot of these targets.
Java much?
In Python, you are encouraged to follow Easier-to-Ask-Permission-than-Forgiveness and catch specific exceptions. Doing either / both requires you actually know what exceptions to catch so you don't hide bugs. The problem is that the docs rarely mention the expected exceptions. You have to resort to discovering them experimentally and hope that that exception was an intended part of the API contract and not an implementation detail.
The Python exception hierarchy is not as good as I'd like. It should be harder to match NameError, AttributeError, etc because there's rarely a sane way to handle those.
People often write code expecting to "handle" exceptions but usually end up masking design defects. Most of the time you can only sanely handle a handful of exceptions way back at the base of the stack. And the handling at that level is usually "log" and/or "limited backoff/retry mechanism". Most of the code you're writing shouldn't do much with exceptions other than perhaps add context and re-throw.
Even if you're not required by the compiler to handle an Exception, knowing which ones are possible to be thrown in a part of the code is still handy.
The same way Optionals and Enums in language like Rust force you to handle all cases.
In the past, I've created scripts that have not needed packaging. Packaging is what is making me fall out of love with Python, especially as I'm learning Rust and seeing how great it can be.
There are many different metadata files with slightly overlapping information without a clear place that documents all of them.
I'd love to see poet take off.
Tornado? http://www.tornadoweb.org/
There is nothing half as good as Django in the async world.
You should check out aiohttp: https://github.com/aio-libs/aiohttp
I've built a couple of tester projects with it in Python 3.5. It's actually quite pleasant.
For example, I slowly go insane dealing with the dumpster full of clownshoes that is python iteration. It's so dumb. I know it's a relatively small thing and only a few small tweaks would fix it, but after about 100 lines or so where I have to deal with it I'm ready to rage-eat my office chair.
- nothing similar to a build system that manages the external libraries
- no type checking, it means: no autocomplete in the IDE, runtime errors due to a value not expected
I don't mind to use python for small things, but for medium-big project I don't know how people can handle it.
UPDATE: I didn't see the python stubs, that made my day! lot of thanks for it!
self.ivar = 1 # type: int
self.ivar = [1]
""" :type: list[int] """
def fun(arg1, arg2): # type: (int, str) -> str
def fun(arg):
"""
:param int or str arg:
:rtype: str or None
"""
I like the comment version (e.g. `# type: int`) better (plus its officially defined in PEP 484), but sadly they tend not to work as well, sometimes due to not being able to `import typing`, and sometimes for other less clear reasons.What? That makes no sense! I think you should take another look at MyPy.
PyCharm has some autocompletion. But it's for some reason way behind what I'm used from Rubymine (fair comparison) and light-years from let's say IntelliJ (a little unfair comparison).
I am used to IntelliJ with java/kotlin. So the autocomplete and type checking is far away, but I think it is because the language itself.
Completely agree on type checking point though.
Maybe I suck at Python.
It's a great general rule for any programming language.
I like to use Java and Go for things that require more structure and type checking.
I like to use Scala for functional programming.
I suppose we all have our preferences, but I'm glad the author uses the word "favorite" because there are great cases to be made for every technology out there.
I totally see the appeal of Kotlin. For me, my day to day job is JS and TypeScript makes that a top shelf experience. It seems like the one constant in my work has always been JS and SQL for almost everything I've ever touched. Though in my dichotomy, I never actually need or use Rust. I would like to find an excuse to write a library in Rust.
I don't either. What are you talking about?
Python is great for what it does. I use it a fair amount. Its fast enough for me.
I admire the hutzpah to use it everywhere, but...But when people try to number crunch, and add c extensions and hey arduino could use an interpreted language! why don't we have it optimize itself (pypy) and so on.
When I learned it I thought I would reach programming bliss, as its reputation is so strong. But after I learned it I thought "its nice, but am I missing something?"
If someone hands you a gun and tells you "Don't shoot yourself in the foot", and you proceed to aim down and pull the trigger, it is not the fault of the person that handed you the gun that you shot yourself in the foot.
Lack of encapsulation is a feature. It allows greater flexibility. But flexibility requires responsibility.
I've never understood the desire for multi-line lambdas. At that point, what makes a lambda different from a function? Just seems like a $1,000 term for a $5 concept.
And decorators are wonderful things. The ability to alter functions in a single line is beautiful.
TypeErrors as described by the author are just sloppy programming. If a function is expecting a string, why are you passing it something that's not a string?
secondly and less importantly he notes that (with some weird naming practice though) _Foo__bar might again look like something you could define. here i'm stretching my python knowledge so i could be wrong (for example i don't know if hinheritance affects the extended name...)
Anyhow i think the first one was the main point: "accidental and hard to debug modification of private field"
• One either likes, doesn't care about or doesn't like encapsulation. I fall in the second camp so... meh.
• One either likes, doesn't care about or doesn't like functional programming. I fall in the second camp so... meh.
• One either likes, doesn't care about or doesn't like static typing. I fall in the second camp (although I admit I do have a preference for dynamic typing) so... meh.
On that third point: Whenever anyone gets into the "but muh type checks at compile time!" I just think, always, that one should write unit tests for everything anyway, no matter the language. The only difference between static and dynamic languages in that regard becomes, then, that the type in static languages is checked twice: in the test and at compile time. So I was waiting for this person to mention tests anywhere...
> Often in dynamic languages, even with a decent test suite, extensive changes can be really difficult.
And he did... with a non-point. Extensive changes are (not merely "can be") really difficult, in any language, no matter the type system.
Frankly, most of this article could be summed up as "I, really, really want Python to be Java", and the half-assed attempt at critiquing java at the end deters that notion very little. I, for one, like Python specially because it is very much not Java and I'm glad it'll stay that way.
> But I’ll be looking elsewhere if I want to write robust software.
Why do people who write this kind of articles always include sophomoric comments like this? Python has been around for longer than Java (or Go, which seems to be the other language he wants python to be, but I like Go so that's fine) and a lot of very robust stuff has been written with it.
If you are lucky enough to have unit tests, they are full of holes and/or outdated. I've seen it happen on critical software. The kind where a crash can literally kill people, and where appropriate testing is mandated by law.
Relying on unit tests for anything is madness. They are nice to have, but they are just another layer in a defense in depth strategy, not something you can count on. Static checks (including typing) is another layer, just like code reviews, functional tests, etc...
And yeah, you can write very robust code in any language, but Python doesn't help much.
How can an unit test be outdated? Honest question because, from what I've seen, if whatever it was testing doesn't work anymore (or if something that wasn't supposed to work starts working) then the test fails, fulfilling its purpose.
In other words: I don't see how I would write a test that wasn't self-updating, by alerting that it needs to be changed.
> Relying on unit tests for anything is madness. They are nice to have, but they are just another layer in a defense in depth strategy, not something you can count on. Static checks (including typing) is another layer, just like code reviews, functional tests, etc...
Not sure what you tried to say here. "Relying on unit tests is madness" I can understand (if not agree with), but then you went out to compare it with other practices. Is relying in those practices madness too? If so, what's the point of anything?
> And yeah, you can write very robust code in any language, but Python doesn't help much.
Is there a language that does? I could turn this around and say that you can write truly horrible code in any language, and [insert whatever language you like here] doesn't prevent it much.
"Favorite" is a focal word of the title, i would be surprised if the content was not about opinions.
> most of this article could be summed up as "I, really, really want Python to be Java"
to anybody its tastes, the author was disappointed by (the expevtations of) python and was complaining
> > But I’ll be looking elsewhere if I want to write robust software.
again a personal statement which takes nothing away from others people ability to write robust software.
If nothing else how i personally interpretated the article was a complain about unmatched expextations and personal pain points.
class Foo:
def __init__(self, a: int):
self.a = a
class Bar(Foo):
def __init__(self, a: int):
self.a = a
The idea of "encapsulation," seems to be a synonym for information hiding, a principle touted by Betrand Meyer[0]. Except that languages which try to provide facilities for this principle have bolted it on in the wrong way and we as an industry have come to accept some other feature ("public/final/private" keyword languages) to mean we have, "encapsulation," in our language.Information hiding is orthogonal to class member accessors and even object-oriented programming as we understand it today. In Haskell it's much easier to take advantage of information hiding without requiring such extra verbiage in our declarations.
This is just a long way to say that this isn't Python's fault. That Python doesn't have keywords or anything to control access to class members is a feature and not a flaw. I haven't read any introductory Python material that tries to misrepresent or hide this fact.
[0] https://sophia.javeriana.edu.co/~cbustaca/docencia/POO-2016-...
update: That is, Python has ways to achieve the effect but it's not a part of the language grammar.
Actually, there's an interesting talk from Uncle Bob saying exactly that: https://www.youtube.com/watch?v=TMuno5RZNeE&t=2325
The idea is that public/private/protected is an attempt to restore some of the "perfect" encapsulation we had with C; and that we lost this encapsulation with OO langages.
There's not much comparison with encapsulation because Julia isn't exactly object-oriented in the sense that objects have methods, instead methods have types that are deployed with multiple-dispatch.
Julia provides first-class support for anonymous functions:
function (x)
x^2 + 2x - 1
end
Julia also provides full macros for metaprogramming.And you also get a type-annotation system and type inference. Newer releases also add support for return types.
One cool example is the JSON for modern C++. You can write stuff like this:
json j = ... // read json
my_struct s = j;
and the compiler will check (recursively) that there is a complete my_struct deserializer. At runtime, it presumably avoids virtual method invocations as a bonus.
I get that some people want a GC, but python feels lower level than C++ to me these days.
Yes I know that C++11 has a lot of cool things, but I find hard to believe it's higher level than Python. Even for simple things, C++ code tends to be several times bigger than the Python equivalent. Yes C++ might be faster, but when you need agility, to build a prototype or a script really quickly Python is many times better than C++ (in agility)
Despite all of my various grievances with Go (on various levels), I strongly believe that interfaces were one of the things they did quite well.
I'm still making my mind up on whether Rust traits are an improvement in this department (though to be fair they are solving a harder problem because Rust has generics), but the fact that you can describe and use an interface without having to update every implementation is incredibly powerful.
Learn to write constructors well, and typing takes care of itself.
Explicit, make-it-this-gosh-darn-it typing is for performance acceleration: numpy, cython. Otherwise you should be using constructors that raise the appropriate ValueError exceptions. Embrace the duck. Otherwise, if you want to write Java, just go use Java to start with.
Multi statement lambdas are forbidden for a reason. Decorators are not after thoughts to fix lambdas.
If you like nodejs and consider it beautiful, thats your favorite color.
with(obtain_resource(), make_function_to_process_resource())
if `make_function_to_process_resource` raises, we leak the resource. You can, of course, pass a function as the first argument to fix this… with(lambda: obtain_resource(), make_function_to_process_resource())
and we're good again, but I think this is why the syntax is nice. It's sugar.The other problem would be lexical scoping:
with(lambda: open('foo'), |fobj|:
data = fobj.read() # data is bound to the wrong scope!
)
Assuming we're ignoring Python 2, we can work around that, data = None
with(lambda: open('foo'), |fobj|:
nonlocal data
data = fobj.read() # data is bound to the wrong scope!
)
The sugar gets sweeter.(I suppose we could also copy how Ruby does scoping/binding, since we're copying syntax. I also think this is why a let keyword is nice.)
I don't think the above arguments invalidate his general argument for a better multi-line lambda syntax, just the argument for with-as-a-function. (Though he was using that to support multi-line lambdas as a potential use-case, and I don't think that's really the case.) I'm just not as bothered by having to def a function, in practice…
There's a pull request for mypy protocols which moves further toward structural subtyping: https://github.com/python/mypy/pull/3132
More importantly, we have 0 evidence that using private/protected in software is better than not. People like to use them because, as the author notes later in the Type System comments:
"Refactoring. If I’ve got a type system and I’m faced with needing to clean things up, I can put stuff behind an interface and follow the type errors to find everything that needs updating. Often in dynamic languages, even with a decent test suite, extensive changes can be really difficult."
You shouldn't have a workflow of fix failures blindly and private variables contribute to that style of over-reliance on language features instead of actual attention to detail.
That being said, the world isn't perfect and it's common to take this approach if you have a lot of strong language safety features and are under time pressure (who isn't). In my experience, this situation means things are being rushed and/or the developer is burned out to give up such fine grained control over the code.
Google/Dropbox/Instagram/Facebook/Mozilla use cases are not most people use cases.
(Plus they do use Python anyway)
I would go further. I think encapsulation (data hiding) is a concept that is slowly being obsoleted by functional programming style, having "naked" (structurally visible to all programs) immutable data types. One of the promises of encapsulation is breaking global state into smaller pieces, but the approach where you avoid state entirely is superior to that.
Namespacing is related to modular programming, which is still useful even in functional programs (it makes sense to group related data and functions together for organizational purposes). Interfaces serve a different purpose yet, to do a dispatch based on type.
Fortunately, Python has namedtuples right? Holy shit is it such a pain in the ass to use. The way to declare namedtuples are some of the ugliest code I've seen anywhere, not just in Python.
Point = namedtuple('Point', ['x', 'y'])
Taking a list of strings to define attributes? Having to write the class name twice? Oh and as an extra fuck you to good coding style, a namedtuple instance has underscored methods that are intentionally meant for public use - https://docs.python.org/3/library/collections.html#collectio...Fortunately 3.5 introduced the NamedTuple class that sort of fixed everything right? Of course I'm talking about `typing.NamedTuple` class, not `collections.named_tuple` - clearly two different things and definitely not confusing at all. Well, here's how you use that:
Employee = typing.NamedTuple('Employee', [('name', str), ('id', int)])
Just shoot me now.3.6 fixed that, but I'm too tired to care anymore. I feel so frustrated just thinking about some of Python's recent daft design decisions. Hugely disappointing considering Python used to be rightfully lauded for having good style.
What I meant was that the author complains that Python does encapsulation badly, because it doesn't have private/public. But that itself is, IMHO, an obsolete view which ignores functional programming.
Automatic refactoring is essential if you want to avoid adding layers and layers of development sediment to your codebase, imho.
If the project is large enough who knows every place some class is used? Even if you do it in what you claim is the "right" way, the compiler provides a sanity check on your work.
I think his point, and it's a valid one, is that Python has practically re-invented private members with special underscore handling anyway -- just more half-assed.
We may have zero evidence that private/protected makes software better but programmers, even in languages with no native support for it, seem to out of their way to do it anyway. Perhaps there is something to it afterall.
It was added more as a kind of "stop gap" for people who complained about the lack of private/protected attributes. That's why it is indeed half-assed. :) And that's also why hardly anybody uses it, especially the double underscore thing.
If Python developers were truly serious about everything being public there would be no underscored members.
Convention is something that doesn't exist in the language. You cannot mark properties as private or protected in Python so the convention is that underscore means private or protected. There are no getters or setters in Java so the convention is to use a method with a get/set prefix.
I would argue that such a common programming practice (enforced or not) should not be left to convention.
i could probably think hard and remember the examples, as i realize i just said "feels" here, but i think that that's actually important. as i am writing code in f# or racket, i feel at ease. these languages are consistent and relatively simple in their experiences. how you feel when using a tool matters a lot in addition to technical talking points.
python feels like it ignores language design principles on purpose and not for a lot of good reason. but unfortunately, it's used everywhere. it's especially bad in scientific contexts where scientists don't know better and are lured in because of its decent selection of available packages.
Packages are the motivation for using the language. No other language has both the array of scientific and math-related libraries and a robust non-math-related ecosystem. I know that there would be enormous demand for alternatives but none exist so far.
However, UI development is complex in any language, and Python's bindings for toolkits are comparatively much better than most other languages in similar domains.
Otherwise in term of number crunching matlab is (expecially on the feels side) a lot better than python
While I get what the decorator complaint was about, the example was just weird... My first follow up question would be: so how do I route 2 paths to that function?
@route ('/')
@route ('other')
def ...
seems like a trivial answer. Now, how do I do that with that block-taking function? Maybe I can pass in an array of paths, maybe not...The reason is this: It's really, really hard to design a good type system after the fact. There are some pretty compelling theoretical reasons for this (Rice's theorem) and also a fair amount of history of mediocre results.
With OCaml/Haskell, not only do you get strong, static types, but the compiler can figure them out on its own. There's just no way that can happen for python.
I've seen more compelling gradual/optional type systems. Last I checked, MyPy still had structural types as a "someday" thing. This puts a huge percentage of python code in the position of basically needing to be rewritten to be usable with MyPy.
TypeScript has interfaces, and generally looks better put together to me.
Racket takes an interesting approach, where whole modules are either typed or untyped, and it's pretty strict about enforcing things at the border:
* If you import something from an untyped module into a typed module, you have to specify the type.
* If you call a typed function from an untyped module, the type is checked dynamically.
So you get much better guarantees.
Erlang's dialyzer takes an interesting approach -- rather than being a conservative type system (where if a program type checks it is definitely type-correct), it is optimistic, so it will only bother you if it's sure it's found a problem. This lets you graft it on to existing programs without having to do major refactorings of parts that don't fit. But you lose a lot of the assurance that way. Still, my impression is it sees wider adoption (as a percentage of Erlang users) than any of the conservative gradual type systems I've seen.
I haven't actually tried any of those beyond tens of minutes of playing around though.
There's something depressing about code that is littered with double underscores.
Django's ORM can get fugly! Lines like this are quite common:
tr_rowid_debtor__de_listed_date__lte=to_date
See https://stackoverflow.com/questions/21319832/what-do-double-...
That said, Python itself is a decent language.
Detailed reply:
- The author complains: "Most obviously, there’s no such thing as ‘private’. The convention is to prefix things with an underscore, as a way of saying to the user, “don’t touch.”"
Yes, underscore is just a convention. But the author ignores that a good Python IDE, like PyCharm, will automatically and explicitly warn you whenever you're accessing an 'underscored' member from outside.
- "Python has lambdas, but they’re seriously hamstrung: They can only be a single expression. No statements."
The author ignores that functions are pretty much first class in Python. Thus, if you want a multi-line 'lambda', just define a function and pass it around. Yes, they will not be anonymous, but functions can be defined at any inner block, as well, so if you do it that way, you will have no name conflict problems. Very easy to do.
- The with statement. "If you had multi-statement lambdas in the language, you could do something like:"
I guess the author has not used Python enough, since the example he shows can be done in Python. As I mentioned, you can pass a function as an argument pretty easily.
- "Decorators, on the other hand, are just a band-aid over the lack of multi-statement lambdas."
The author hasn't got a clue of what is the usefulness of decorators.
- Again, complaining about decorator use in Flask:
from flask import Flask
app = Flask(__name__)
@app.route('/'):
def index():
return 'Hello, World!'
The author complains that he needs to define a name for index(), while this isn't needed in Ruby (under Sinatra).Well, the author should use the Flask framework a little bit more before complaining, because the reason one needs to define a name for the index() function is to be able to... refer to this function later in many occasions!! For example, if i want to get the actual URL that is routed to index() i can use:
url_for('index')
In this way, you can separate the routes from the view controllers, and thus, you can change the route paths without having to break many parts of the code.- The rest of the article, the author complains that Python "does not have a Type System". However, the author conveniently ignores many things:
a. Python allows to call functions using keyword arguments, which strongly helps preventing bugs since the order of parameters is not going to affect the code and you clearly know what input parameters are you assigning values too.
b. You can always insert assert() statements to have a form of type checking. A good IDE, like PyCharm, will then warn you whenever you are calling a function with wrong types, and in runtime the type error will be catched as well.
c. If you want static typing at 100%, why are you using Python? Wrong choice.
d. There is a specific documentation style that, if you're using PyCharm or another decent IDE, will show you the documentation for every parameter as you are writing your function call. If, having this feature, the coder still makes mistakes calling a function with wrong parameter types, then the coder is too stupid to use Python.
e. Python is strongly typed, so a wrong input type to a function will cause a runtime error pretty soon, anyways. And, in my opinion, the bugs caused by type mismatches, in a strongly typed languaged, are trivial to solve. The bugs that worry me are the difficult to solve ones, the ones caused by wrong understanding of the problem, etc. Those bugs will never be prevented by a type system.
The author is not concerned with "hard stuff". He is concerned with language (syntax, etc) criticism, which is a different thing.
And, no, he doesn't want to "program Java-style within Python" -- that's an easy way to dismiss any kind of language criticism.
The author wants to do Java in Python. He should better use Java! Java has all that, it is significantly faster than Python, it has even more libraries, it is easy to learn, it has great IDEs and a lot of codebase and support. Java 8 even has a sort of anonymous function support.
My point is -- if you use Python then you need to take advantage of its differences, instead of wanting to type code exactly as in other language. Kind of makes me remind of the clever one-liner on "Real Programmers Don't use Pascal":
FORTRAN programmers can write FORTRAN code in any language.
Mind you, Java is my least favorite programming language, but if I wanted to make use of all those features, i'd be choosing Java or C#, easily, no-contest there.None of which (and not even all together) were first, exclusively, or specially in Java.
And none of which are what make (or made, circa J2EE/EJB/XML craze) Java painful.
>My point is -- if you use Python then you need to take advantage of its differences, instead of wanting to type code exactly as in other language.
Sure, but those differences have to be different for good reason, not just because "well, that's what Python offers".
Else one is justified to criticize them.
I have some fond memories of hacking around with it, about 10 years ago - although for some context, I was coming out of the wastelands of Visual Basic, pre-modern C++, and AWT/Swing era Java. I'm not sure there's anything easier for quick and dirty 2D game development than the PyGame SDL bindings were.
But I'd cringe at the thought of building anything serious with it today. It feels too loose and sloppy, like trying to build a castle made of sand, although I might have some Stockholm syndrome from years of more static, more strongly typed languages. But I have to do enough grepping around through the code base now to make sure tweaking something isn't going to break some snippet of JS code on the front-end now; I really don't need to be worried about that on the back-end too.
That's the whole point.
> you may as well just reach for TypeScript instead of Python.
The only reason I do JS is because it's the only browser native language we got. I'm never gona use this aweful tech anywhere else if I can.
It is? I don't think so. My whole point was that they're useless from a performance perspective.
>The only reason I do JS is because it's the only browser native language we got. I'm never gona use this aweful tech anywhere else if I can.
That's why TypeScript exists. It's better than older versions of ECMAScript and Python, put together.
from castles import CastleWithMoat
c = CastleWithMoat('eyy')
It's just not meant to replace C++, but I think it beats php, perl, R, and lots of things in readability and package coverage. It lets me do GIS, number crunching, and web serving all in one system. Equivalent functionality in Python is a fraction of lines of complexity implementing from scratch in "strongly typed serious code", for a huge swath of problems.I'd recommend Julia (http://julialang.org) for anyone looking at new projects where Python is an option. Similar syntax, though saner code structure (e.g. no tabs/spaces issues), compiled, strongly typed. All around an excellent language.
++Lack of Consistency in Project Structure:++
* The Django way of "several applications are in your application" creates many folders, each with either a models/ folder or models.py.
++Django ORM:++
* Writing a model which generates a migration, prefixes table names based on the "sub-application" name in my application is just awful. The default names for these tables is just another layer of cognitive load to bear.
* Implicit relationships between tables, vs allowing me to specify has_one/has_many/belongs_to/has_many_through/etc relationships.
* I miss FactoryGirl. Specifically I miss FactoryGirl's feature of allowing me to declare associations between my factories, then creating upstream objects in the proper order. Without this feature, I get a lot of repeated 'setup' code in my unit tests, which makes my tests brittle, and lowers their value.
++Django Testing:++
* Writing unit tests without the describe/context/it style feels like I just took a bus back to 1998. Combined with the lack of FactoryGirl, I see test suites that barely test the "happy path", let alone any "sad path" cases.
* Due to the brittle boilerplate setup code in unit tests, minor changes to the code tend to break tests badly, further slowing down the pace of development.
* The alternative to constantly fixing broken tests is to have fewer tests (or no tests) which is even worse IMO, since only your customers will test code code.
* The Rails way of writing a migration, making factories, making model specs, then controller specs (and finally features for end-user acceptance tests) seems to have gone completely off into an unknown future from the perspective of Django, who was left behind, frozen in time at the end of Bill Clinton's last term in office.
Recently discovered these two projects, thought I'd mention them:
* Model Mommy (https://github.com/vandersonmota/model_mommy)
* Factory Boy (https://github.com/FactoryBoy/factory_boy)
Thanks for sharing!
>Python has lambdas, but they’re seriously hamstrung: They can only be a single expression. No statements. Furthermore, Python has a bunch of built-in language features that are totally obviated by adding a decent syntax for multi-statement lambdas.
This guy doesn't understand functional programming and why the lambda exists as a single expression.
Either way I agree it's better to have multiline lambdas but I disagree with how he related it to Guido making functional programming painful... If lambdas had multiline statements then the lambda would not be following the functional paradigm. In functional programming EVERY function is a single expression, including the main itself. In fact, in functional programming, your Entire program is just a single expression.
This is a technicality. There's basically always a mechanism for chaining two expressions and junking the result of the first. In OCaml, the semicolon operator does this. In Scheme (and most lisps) you can just put multiple expressions in the body of the function, and the return value will be the last one. And there's `begin` if you want a block that's not a function. In Haskell, the purity makes this actually not make sense in most contexts, but you've got `(>>)` for monads, and the do-notation which sugars it into something that looks basically like a statement.
Some version of that in python would be an acceptable alternative.
But there's also stuff like moving reduce to a separate package.
Python's whitespace, on the other hand, only ever becomes not-a-big-deal. I'm not a fan on conceptual grounds, but I rarely run into issues with it (emacs keeps me good here). But I'm pretty sure Python's whitespace offers no enlightenment for my nominal troubles.
The biggest issue I once had with semantic white space was that demanded convoluted rules and imposed itself into the syntax like in the single line lambdas case... But then Haskell has none of those problems.
I thought Heartbleed was just a over-read of data and had nothing to do with parentheses and indentation.
I also always thought I disliked white space indentation and then I used it with coffeescript. Apparently I just don't like Python.
I have ruby for having "end". It's just taste.
I like Python because it forces people to write well indented code. I worked with too many pigs.
It's like driving on the autobahn without guardrails. You may only need them once or twice in your life and yet, you're happy the guardrails are there. Just in case.
Allowing both tabs and spaces was simply a mistake but it is too late to turn the clock back on that now.
http://wiki.c2.com/?PythonWhiteSpaceDiscussion
In general, significant whitespace isn't fantastic but at least now we have widespread version control which helps in case of accidental deletion/insertion of a space or a tab or a newline.
So Python has ... nothing. I program a lot in Python but this is one headache I could have done without. It also always makes me read a little bit further than I have to when looking at some block of code because you never know that something else might follow until you drop that indentation level back down. So now you have to count indentation levels.
And in many languages, those are optional in certain contexts (as someone else mentioned: see heartbleed).
if (a == b)
do_this()
and_always_dothis()
...Who exactly considered it a bad idea and why?
This is my major pain point with Python. No code blocks as arguments and single line lambdas are the second one.
And those underscore looks so 80s, but that's when van Rossum started designing Python. Same thing for the self argument in method definition: it's the pointer to the struct holding the function pointers and instance variables in object oriented C (without ++).
On the other side
object = ClassName()
is more convenient than object = ClassName.new # Ruby
object = new ClassName(); // JavaHaving to edit a bit a code you copy paste from an badly formatted source (which you should do anyway for safety) is a small price to pay to avoid all the dirty coders in the world to write blocks with a showel.
I know that I should select the region and do C-c > to move it one level to the right, but it's not convenient. In any other language the editor indents it with one keystroke (actually it's 3 keystrokes: C-M-\ because it's emacs...) That's why TAB, ARROW DOWN is faster, when it doesn't add bugs.
I have never encountered either of these problems, pasing issues are trivially solvable ("+p), and who uses tabs?
I also often run in to code that mixes tabs and spaces, not just in Python, but it many other languages as well, and it is super annoying when it happens.
That said, before I learned Python, I absolutely hated the idea of meaningful, forced whitespace. But once I actually learned Python, it turned out it wasn't a big deal, as I tended to indent that way anyway. But it is annoying on occasion when working with other people's code, for the reasons mentioned.
It's not like this is an accident, it's by design. Python pretty much waits until <<right now>> to bind things to names. It's part of the reason that it's "slow" and relatively challenging to make "fast." But OTOH you can do really interesting powerful stuff. "But I don't do that stuff"? Ok, maybe indeed Python's not for you.
As a simple fix for this problem, vim users can use syntastic, atom users have a/several python plugins to check syntax, I'm sure VSCode and emacs have analogous plugins. Yes, their power is limited to only some kinds of checks, so TypeErrors can slip through.