> Ok, so “hate” is a strong word, but hopefully this click-baits enough folks into reading this article with a clearer, more rational and unbiased demeanor than what the title suggests.
Self-aware clickbait is the worst kind of clickbait.
People can very well do it because they like fancy titles, or leaving things unspecified, or puns, or whatever, and not to drive visitors to their website.
In fact, before the web, authors have long used fancy titles for articles that you had to buy the magazine or newspaper to even read, so they had no advertising-like value to attract unsuspecting readers.
If the title had been "Python [is] a very problematic programming language for building reliable, high-performance distributed systems.", we'd all have just gone "duh" and moved on.
Yeah, but if that's the case then just have the title be clickbait and leave it. Having the title be clickbait and acknowledging that in the first sentence and trying to justify it as somehow improving the reception of the content is just kind of insulting.
"Look at me using the word hate misleadingly, hate is such a strong word". Is it though? This is like the title-writing version of /r/madlads. :-)
> Python is considered a weakly-typed or dynamically-typed programming language.
This isn't right. Weak/strong typing is on a different axis than static/dynamic typing.
Python is strongly typed. There's no implicit conversion among types (which is one marker of weak-typedness). For proof, consider:
>>> 1 + '3'
[...]
TypeError: unsupported operand type(s) for +: 'int' and 'str'
Strongly typed means that the types are protective, in a way. You put a value in a box labeled X and that's it — you can only treat it as an X from now on.
In a weakly-typed language, if your function expects an X and you give it a Y, well, it'll do its best to make due.
Many people think that because Python is dynamically typed (i.e. you can pass anything anywhere without compile-time restrictions), that it must also be weakly typed. This is simply not the case. If I write a function which expects an integer and I give it a string, I'm going to have a bad time despite the fact that the code "compiles" and only fails at runtime.
---
Edit: I've read further, and the author actually uses their lack of knowledge on this subject to portray the Python community as being willfully misleading in their promotion of the language:
> Python markets itself as a dynamically-typed programming language, similar to Perl, Ruby, or JavaScript. The word “dynamically typed” has a positive connotation, more so than “weakly typed.” Conversely, the word “strongly typed” sounds more positive than saying “statically typed.” Diction matters, because proponents of different camps will select a word-choice that reflects their bias of one programming language over another. Python is both weakly-typed and dynamically typed, which contrasts itself to languages like Java, Scala, C/C++, and Go which are strongly-, statically-typed.
C is statically typed, but it is also weakly typed. You can treat any value as any type if you so wish, either by explicitly casting or by removing checks for implicit casts during compilation.
I'm going to finish reading this article, but... I don't think I respect this author very much, based on what I've seen so far.
---
Edit 2: While the author's points would be valid by themselves (GIL, build toolchain, dynamic typing), the over-abundance of negative rhetoric shows that the author was merely interested in writing a smear article of Python, essentially. I think it's a pretty weak article overall, and would have been aided by a more fair comparison with notes about why some of these design decisions were made (or at least a less-slanted writing style).
It's also in the Wikipedia page about python that it is not a "weakly typed" language. It's literally in the resume box in the corner : "Typing discipline : Duck, dynamic, strong".
Edit: I was going to mention the author's confusion, but OP did it in their edit.
Edit 2: The author is "Staff Software Engineer at Tesla" ? And he doesn't know C pointers ?
It's worse than I thought. If you see my edit, I found that the author used their complete ignorance of the (strong/weak)/(static/dynamic) distinction to invent a claim of willful misdirection on the part of the Python community. Not only did they not do their homework on the topic, but they tried to portray people in a negative light to further their narrative. I find that incredibly dishonest.
While true that there is no single official definition, it does not give the author license to portray the community in such a negative light.
As an aside, I work in PL research (as an assistant) and have never talked to a researcher who disagreed with the assessment I gave above. I'm not saying you're wrong (because you aren't), but rather that there is at least enough of a distinction that the author shouldn't have conflated them to the extent that they did.
To me, the core points seem like table stakes knowledge that one should have before jumping into development with ANY language, not just Python. Every language is a tool that is good at some things and not good at others. Cue the standard if all you have is a hammer, every problem looks like a nail.
A few core things:
The GIL problem is frustrating. I generally just try to avoid writing threaded code and instead execute calls asynchronously or run parallel instances and work with Celery or something similar. For web applications, this tends to work well enough for me personally but this is definitely a valid issue.
Python2 vs Python3 is sort of a done deal at this point, in my opinion. I was a big holdout on moving to 3 for a while but, for the past couple of years, I haven't had more than 1 or 2 cases where I had serious issues. I don't have to deal with legacy codebases though.
On a larger note, I would say that this all points to the fact that it is easy to do things the "wrong way" in Python. The accessibility of the language is great, but it is not without its issues if you go in expecting it to be a fenced-in playground with batteries included (to mix metaphors).
My issue in favor of Python is the clarity and brevity I cannot find in other programming languages. For example, doing a web service with Flask is a relaxed task with minimum code that after months continues to be clear and without a lot of context needed. NodeJS/express? My stomach cannot pass callbacks nor promises (I am aging, sure).
In terms of progress I love what .NET has achieved and found that libs like Nancy [1] are good to explore.
Disclaimer: I am not working as a professional programmer anymore so I look for fun and "move faster" tools.
The "GIL" problem is so over exaggerated for most problems. A significant amount of threaded code is IO bound and in most of those cases the GIL gets out of the way. Checkout the cpython code around the calls to things like select, poll, epoll. If you are needing to do high performance number crunching across multiple cores, then yeah it's a little more work, but it's gotten easier and easier to write some C libraries for that or leverage something like numpy.
I would agree it's over-exaggerated. In my day to day work, the GIL has rarely, if ever, been the performance bottleneck. It usually traces back to something outside of Python (e.g. a slow DB query or a slow API call) or to something that has been implemented in python in an inefficient way (e.g. writing something O(n^2) when if you read your algos 101 book or even just searched stack overflow you could find an O(n) solution).
I also think that the whole "drop down to C argument" is sometimes viewed as a cop-out when criticizing python, but I personally believe that being proficient at high-level and low-level languages, knowing their strengths and weaknesses, and transitioning between them when it makes sense is stronger than just using purely high OR low-level. It's sort of like having multiple gears on a car: you need all of them, and they are all useful and most opportune in different scenarios.
> Python is viewed as a ubiquitous programming language; however, its design limits its potential as a reliable and high performance systems language. Unfortunately, not every developer is aware of its limitations.
Hammer is viewed as a ubiquitous [sic] tool; however, its design limits its potential as a reliable and high performance drill. Unfortunately, not every carpenter is aware of its limitations.
> "Ubiquitous" is spelled correctly. Why the "[sic]"?
For what it's worth, '[sic]' doesn't necessarily mean "that was spelled incorrectly", only, literally, "thus": i.e., "that's the way it was in the original; don't blame me." As such, it can be used to indicate misspellings or 'misconceptualisations' ("I know that it's the wrong word, but it's the one that was there"), as nmyk (https://news.ycombinator.com/item?id=17484468) indicates.
To expand on this excellent description, when JadeNB claims that “sic” literally mean “thus”, that is because “sic” is the Latin adverb meaning “thus”. Some people think that “sic” an initialism for “spelling is correct”.
I love Python, and use it often. It's huge in many industries due to the ease in which people from various backgrounds can learn it. However, I agree whole heartedly with the article. Python does not scale, performs poorly and is unreliable at run time.
I think this is true for many second generation languages, not just Python. Perl, Ruby and JavaScript are really no different. None of these languages were designed to scale, be cloud oriented and perform close to C. Thus, many developers complain of 'hitting the wall' and try other languages because their tools were not intended nor designed to do these things.
I also think Go (and languages like it) which were designed to scale, use the cloud, be safe and fast (performance close to C) are the solution and the future. Here's a quote from Ian Lance Taylor, "Go was deliberately built from the start to support large scale programs implemented by hundreds or thousands of different programmers. Those kinds of programs are written at Google, and Go was designed to be used to write programs at Google."
This starts off with a misunderstanding of what "strongly typed" means. Python is strongly, but dynamically typed.
Static typing means you know the type of something at compile time. Dynamic typing means you don't. The upside to dynamic typing is that the compiler doesn't have to prove much about your program; the downside is that it can't really prove much of interest either. Strong vs weak typing is about how much your language is willing to fudge types in order to make an operation succeed without errors -- be it the intended result or not. Both static and strong typing tend to make errors be more obvious sooner. Both dynamic and weak typing tend to require less boilerplate to make your program do things.
Python doesn't know the types of things in advance (dynamically typed), but it is very picky about which types are allowed to interact (strongly typed). Even in cases where the answer is obvious, like str.join, you still have to make everything strings: "".join([1, 2, 3]) will fail.
To demonstrate why these are orthogonal, consider the other cases.
Haskell is strongly and statically typed. A program that adds a string and a number together will be rejected at compile time because the compiler can prove it's invalid.
C is weakly and statically typed. A program that combines a string and a number may be accepted by the compiler, who interprets the number as, I dunno, a wchar_t or something. Another example of weak typing is how C allows (heck, encourages) pointer arithmetic and untagged unions/structs.
JavaScript is weakly and dynamically typed. A program that adds a string and a number together will, at runtime, convert the number to a string and then concatenate the two.
The rest of the points are silly rehashes of arguments as old as Emacs v vim, but sure, let's go there too. We've done empirical research to see if this matters. Charitably, the results are "inconclusive"[0].
> The first glaring issue is that I have no guarantee that is_valid() returns a bool type.
You can name functions ridiculous, unhelpful and misleading things in any language. The very fact that you know to expect a bool undermines the point: clearly, despite not having static types, a human managed to make a pretty reasonable assumption about what a piece of code was about to do. Just because the compiler can check that is_valid returns a string doesn't mean that hat's magically a useful thing to do.
> The fact that I cannot, in the absence of perfect test coverage, verify the syntactical correctness of someone’s code is a big red flag.
Syntax errors are caught by the compiler. Semantic errors aren't caught by the best type systems. At best, type systems are a way to encode a bit of the semantics so that the compiler can prove things about it.
> To improve static type checking in Python, developers made libraries like jedi, pytype, mypy, and toasted-marshmallow, but they are often not used in most projects because they do not belong to the standard toolchain
I can not imagine the author actually used jedi and believes this. That's... not how jedi works. I also don't know what to do with the argument of "oh, you have an optional static type system but people don't use it because it's annoying, this is why we should use a mandatory static type system" other than shrug.
I like types! I think there are great arguments for types. I like the idea of being able to have a type system prove that certain bugs can't happen. This adds nothing but FUD and no new insights.
> “In Python a good amount of errors are discovered at runtime"
In my experience this is roughly equally true with statically typed languages as it is with Python. The types of errors the compiler helps find before running tests are just not important. They are not types of errors that matter much. And if compiler errors are cryptic enough, it can be a huge huge pain that you can’t just run the code on a toy input to see what’s going on. The whole pitch about how static typing and compilation will save you time by forcing more correctness prior to testing is just a hogwash myth in practice. It happens occasionally, but not nearly enough to justify all the stuff advocating it, and the benefits of being able to debug according to desired behavior and just not care about strict safety in edge cases you can guarantee you won’t hit are really tangible. You cannot easily dismiss these benefits.
Python objects are duck typed, and c/c++ structs are strongly typed. Duck typing is closer to weak typing than strong typing. Tagged unions are dangerous in C/C++, but python’s object type is strictly weaker than a union.
In fact, C and C++ are both strongly typed, but they are both memory unsafe.
However, C++ provides enough static typing to make it easy to write memory safe (but single threaded) programs.
Also, tools like valgrind get you 99% of the benefit of memory safety, which is good enough for debugging. C++ lets you override the allocator, and build in inexpensive (fast enough for production) checks for most use after frees, memory consumption profilers / leak detection/ etc.
I’d argue that python is memory unsafe in practice, since we get segfaults from third party dependencies in python about as often as we see them in c/c++.
Crucially, C and C++ provide enough tools to get static type safety on the error handling path.
Errors like ‘assert “” is not None’ and missing field exceptions in python error handling code are frequent, but unheard of in C/C++.
If you use std::string, adding a in integer does something well-defined (rtfm). Adding an int to a char widens the result to the int type (foiling attempts to use it like a char without a downcast, if you use -Wall -Werror). Adding an into to a char* does pointer arithmetic, which can be used safely, but there are better string / buffer types you can implement in an afternoon, so do that instead.
> In fact, C and C++ are both strongly typed, but they are both memory unsafe.
You are refuting my point but provide no evidence and implicitly redefine several terms in a way contrary to common usage.
Is there a definition you can pen down? Because C sure isn't strongly typed in the Liskov, Jackson or modern usage senses of the term.
> Also, tools like valgrind get you 99% of the benefit of memory safety, which is good enough for debugging. C++ lets you override the allocator, and build in inexpensive (fast enough for production) checks for most use after frees, memory consumption profilers / leak detection/ etc.
Would you say that de facto C/C++ has a memory safety problem? If it does, why?
(Also, not germane to my point.)
> I’d argue that python is memory unsafe in practice, since we get segfaults from third party dependencies in python about as often as we see them in c/c++.
Do you have extraordinary evidence to go with this extraordinary claim, or are you just talking about your own anecdata?
(Also, not germane to my point.)
> If you use std::string, adding a in integer does something well-defined (rtfm).
My argument was not that it is poorly defined. Weak typing doesn't mean UB.
I’d argue that type errors (not including memory safety errors) should not occur at runtime in a strongly typed language, or at least that a “stronger typed” language would admit fewer classes of those errors in practice.
I’d argue that de facto, modern C/C++ does not have a memory safety issue. Operating systems like OpenBSD and many other secure, hardened network daemons provide existence proofs that are not available for languages like python.
In contrast, I’d argue that python has severe type safety issues.
I’m not sure why evidence that “strongly typed” python in practice suffers from all sorts of typing errors that “weakly typed” C/C++ avoids is not relevant to your point.
I also don’t understand why you dislike the string examples, which explains the (strongly typed) semantics of the operators you mentioned in your comment.
By “strongly typed” do you mean that all operators return the (single type) of their parameters?
I don’t know of any languages that enforce such a thing.
Perhaps you mean “strongly typed languages only include operators that are to my taste”? That’s surely not a useful definition.
OK, Wikipedia it is. Wikipedia lists C’s typing discipline as
“Static, weak, manifest, nominal“, and Python’s as “
Duck, dynamic, strong since version 3.5: Gradual[4]”.
> I’m not sure why evidence that “strongly typed” python in practice suffers from all sorts of typing errors that “weakly typed” C/C++ avoids is not relevant to your point.
> I also don’t understand why you dislike the string examples, which explains the (strongly typed) semantics of the operators you mentioned in your comment.
I have no idea what evidence you’re referring to, or what examples I supposedly dislike.
There are examples of string operators two comments up. Those are the examples I’m referring to.
To any readers that made it this far, and are hoping for something precise, here is a minimally useful definition of “strongly typed”. I’ve never heard of a broader definition of “strongly typed”:
The language’s type system must be embedable in a lattice, and at runtime, it is guaranteed that functions aren’t passing the top element around and then using it without an explicit downcast.
C/C++ meet this definition, since you need to cast void* before dereferncing it, and void isn’t a valid type for a variable. Also, the only union types are defined at compile time.
Python does not meet this definition because functions can have multiple return types due to control flow, which means you can create call sites that return things like lub(integer, string), and then compose them until you have something of type “top”, and there’s no way to statically guard against usages of such a type.
This isn't a very good definition. C++ doesn't have runtime types other than void* (python does, on the other hand) (java is the same way).
In C++, the only thing passed around at runtime is void* . The pointer is then used as a concrete type without any explicit downcast.
Any conversation about strong typing should not involve the word "static". Nothing about strong typing is "static". It is entirely a runtime concept. Functions potentially having multiple return types is entirely an issue of static typing. Not strong typing.
This is obvious because you can annootate python code with the type information. This makes it strongly typed, but not statically typed, even though it makes all unions compile time defined.
In other words, if I take python with type annotations (which is statically typed), and remove the annotations, you claim this would make the language weakly typed, but this makes no sense. There would be no difference between strength and dynamicness.
A better definition might be that a language is weakly typed of it allows transformation from type a to b, where a and b are unrelated (ie neither inherits from the other), without a call to a constructor. Python does not meet this definition, the only way to cast is via a constructor call.
C and c++ do not, void* allows arbitrary transforms. Java will throw a classcastexception, but can only do so at runtime, bit will do so at the bad cast.
C and c++ will keep chugging until the miscast object is misused. That's weak typing.
> Strong and weak typing are poorly defined terms.
I work as a research assistant in a PL research lab and have talked with many PL researchers who have been in the field for a long time. They all use the terminology consistently, and it all lines up with what lvh has been saying.
I agree that the terms are poorly defined, because there's not a single definition which can be used as a criterion for evaluation. But that Python is more strongly typed than C seems to be a common sentiment, at least among people that I explicitly asked about it. Much C code practically relies on the weakness of the type system to function, whereas Python cannot be deceived in the same way.
> I’m not sure why evidence that “strongly typed” python in practice suffers from all sorts of typing errors that “weakly typed” C/C++ avoids is not relevant to your point.
Typing errors are evidence of a strong typing system...
> I also don’t understand why you dislike the string examples
Your string example relied on the weak typing of C to work. The conversions are implicit, aren't they? This is a hallmark of a weakly-typed language.
The author complains about CPU-bound performance and then mentions PyPy but claims it's ecosystem is barren enough that the point stands. This claim is presented without evidence.
In practice it doesn't really show up: the cases where code is actually CPython-specific usually imply all the work is being done in highly optimized C bits. Hence, those programs, despite being run on CPython, aren't CPU-bound.
Finally: while this FUD is really pernicious because it used to be hard/impossible to get scientific code like numpy to run on PyPy, that hasn't been the case in a long time. Right now you can just pip install numpy and it'll just work.
It will bite you when you don't expect it. When i work, we had a dev write a IO trace playback engine in Python to do some IO performance testing. We then deployed it on various platforms and got very strange results - eventually I noticed when TOPing the node, the CPU was pegged at 100%, so this wasted about 2 months time of about ~4 developers
Wasn't worth taking the time to figure out, since none of us were expert level in python and we only needed a one-off profiling tool. I rewrote it in Julia, which killed the problem.
Given that it was about 150 lines of code and I did a fairly straightforward rewrite in a performant language in about a day, and the performance was acceptable, yes.
The typing, encapsulation, and packaging issues are features of the language which have benefits and tradeoffs. The author of the article seems to balk at them because they aren't what he is used to. Re: typing in particular, gradual typing is something that's slowly becoming viable, and I think it's great.
The python 2 vs 3 issue isn't really an issue if you're starting with the language. There was a time where lots of big libraries were only supported by python 2, but that's definitely not the case any more.
OTOH the build system is definitely a common pain point, and the GIL forces you to use other services (e.g. redis + multiprocessing) to achieve horizontal scaling which is unfortunate. And of course python's crappy performance is something anyone looking at languages should be aware of.
However, what is the cost of addressing them? Slower development (core lang and ecosystem). Less use in niche fields. Less flexibility to adapt to new challenges.
Python is a rusty toolbox that's been put together over 27 years.
You know why it's still around? Because people can use it to do the things they want to do.
Fixing all the warts would give us the language we need now... in about 15 years. Which would by then be useless.
> However, Python does not have a real privacy model.
> This is unsettling if you’re coming from a Java or C/C++ world and are expecting access modifiers
It's unsettling to a lot of people, but so what? I still don't see a solid use case for where member access (not "privacy") is a good idea. Python is one of the only languages to get access modifiers right (convention), imo and I don't even like Python.
You could implement some "privacy" with a lambda closure (which gets increasingly complex), but that's one more technique that lambdas are ill-suited for.
The author makes a lot of strong statements based on their opinions. I wrote a bit about their dynamic/weak typing gaffe in another top-level comment.
> lambda closure
Just FYI (not intended to be mean or anything), in formal PL theory this phrasing doesn't make sense. A closure is the pairing of an anonymous function (a "lambda") with an environment. Python's "lambda" is really just a lambda --- it is not a closure. This is revealed in the common pitfall where you try to create a list comprehension using a lambda to generate interior values.
The idea is to create a lambda that's sole purpose is to act as a closure for member access restriction. I'm not sure how that could be misinterpreted, but here's your prize.
I guess my point was that you should have just said "closure" instead of "lambda closure", since that phrasing doesn't make any sense in a formal context. Lambdas and closures are different things entirely. I really wasn't trying to be patronizing or anything; I was just trying to offer information in case you found it interesting, but I guess I missed the mark there. My apologies.
Even in C/C++ privacy is silly, since at any point, any part of the program can look up any part of memory it has access to, and that would include privately defined members.
That's because package management is an extremely hard thing to do if you're only trying to control one part of a system and have it work magically in different environments. And so strangely enough programming language people writing their own toy-town package managers get it wrong again and again and again.
It's worth noting that autotools for building C/C++ was so broken that ESR declared that SCONS (written in python) was a much better way to build C/C++ code.
> There is no short answer as to what is fully going on here, but simply put, Python modules are packaged either as a Wheel or an Egg
This is a false dichotomy -- there are actually many different ways to package a Python module, including source distributions (sdists). These are just the two most well-known types of built distributions.
> The numpy module is a wheel, i.e. it is a source distribution that depends on a handful of system libraries — gfortran, blas, lapack, atlas — to compile properly.
This is not correct, a wheel is specifically not a source distribution, it is a built distribution. The dependency on platform-level libraries has nothing to do with what type of distribution it is.
NumPy publishes a lot of wheels for a given release (https://pypi.org/project/numpy/#files) each of which is platform-specific, since they are pre-built (pre-compiled) for a given platform.
In the event that a built distribution isn't available for your platform, that's when pip falls back on the source distribution, which requires the build step (hence the dependency on gcc or clang).
Totally agree with this person's assessment on types.
I took over a smallish half-completed web application written in python a couple of years ago. Nothing major - simple business-y analytics/data viewing app with some fancy charts and tables etc - perhaps a couple of thousand lines of code interfacing to backend database unique to our business. It had some tests, but nowhere near 100% coverage. I needed to finish it off and then get it into production.
Long-story short, trying to understand someone's half-completed python and make changes to this was a HUGE nightmare for precisely the reason the article outlines: python expects you to remember all the minutiae of all the code you are calling. If you cant remember, then you need to suck it up and pick your way through the code and mentally keep track of what is going on at every stack frame.
So instead of thinking about the actual problem you're trying to solve, python forces you keep track of all the little bullshit that you shouldn't need to care about. And even then, you cant be 100% sure you got it right until you exercise that code (either through 100% test coverage, or - shudder - at run-time at 3am on a Sunday when a user in Japan logs on).
So far from being productive and "easy to use", python for us was a nightmare of low-productivity and frustration in only a very small application being worked on by just 2 or 3 remote developers. I cant imagine the levels of pain and suffering for larger projects & teams!
The "solution" is apparently extensive & specially crafted comments that specifically explain the types used (that of course needs to be kept up to date) - I find this fairly odious (why not just use a typed language if you need to put the types in the comments?!)
Still, its not all bad. As a result of the pain of this application (which incidentally was damn slow in production due to the processing we had to do on the data in the python backend before we sent it to the browser), we decided to learn golang and use that for future work instead and have not looked back.
Golang has the same feeling of fast-paced "throw things at the screen" productivity, but is obviously typed (so you know about errors before your users do) and orders of magnitude faster.
.. sorry to hear about the frustration, but almost all of these are ordinary problems with application development. Maybe the original developer made a mess, but that is ordinary too.. if you have a short-list of hmm .. ways to solve typical problems, in a workflow you like, in a language and functions you like.. then you can bring those to the new efforts.. production technical engineering groups will sometimes just bid to remake the whole thing, using their own ways, for exactly these reasons.. but individual developers rarely have that option.. chin up!
> python expects you to remember all the minutiae of all the code you are calling. If you cant remember, then you need to suck it up and pick your way through the code and mentally keep track of what is going on at every stack frame.
I have seen python code written this way. It's not a joy to work on. This function returns this type of class or this other type of class based upon input, and the two are not interchangeable say, so you have to refactor a bunch of code if your type changes underneath you.
The typical pattern is a method should return something of one kind of class only or raise an exception. And then newer tools like PyCharm can help you out here by knowing what the functions are going to return.
lazy article. dude wishes python was java. also, it fails the snake-test: python is named for Monty Python's Flying Circus, not the snake. anyone who uses the snake is not clueful.
Oh yes, python packaging sucks. That may not be well understood. But the languages that handle distribution very well are just a few, that i know of (jvm based, .net based, go, possibly js with all the tools that exist) and there'll be some that i don't know. But for most languages, distribution of a software with dependencies is a great pain.
Hate those things all you'd like, but that won't stop you or others from using it and doing so regularly. None of these points are show stoppers for many scenarios. That's good enough. Python is the best second language for everything and first language at many things.
- The indentation based syntax: it can easily trip you up when moving big blocks of code around. You want to put this if else block somehwere else, hope you get a syntax error, because you can get sth. that seemingly works but does not do what it's supposed to do. Also sometimes finding where you are can be difficult.
- Dogmaticism: a big thing in the community. They'll wait 20+ years until they add string interpolation, a feature common in many similar languages. They'll wait 20+ years until they start to realise having expression equivalents of statements is useful.
- Breaking syntax: new syntax is added in what seems to be minor releases, so you either avoid new syntax to be compatible w/ all the 3.* interpreters, or you need to expressly avoid certain interpreter versions. And there's no equivalent of Perl's "use v5.something;" so you need to write code juggling multiple supported versions.
Still I think it is a nice language to have in your toolbox for the vastness of the stdlib and the available 3rd party packages.
> there is no question that type safety matters for building and maintaining large-scale distributed systems in the enterprise.
Some of the largest-scale enterprise distributed systems, as in, at the telecoms level, are written in erlang, which is dynamically typed, and highly reliable.
One thing not mentioned by anyone yet, that kinda annoys me is the fact that Python libraries use different naming conventions. Even though PEP 8 has existed for 17 years now, there are many examples where I see lower_case_with_underscores mixed with lowercase and camelCase in the function names.
I know that many libraries and Python standard lib itself predate this PEP but I don't understand why Python 3, which broke backward compatibility anyways, did not fix that at least in the standard library.
It aggravates me the abundance of scientific/mathematical packages that exist for Python, because of all of the language shortcomings that this article details.
No doubt your typical Math Ph.D. type doesn't want a language with a step learning curve to implement their expertise, so what to do?
Perhaps everyone could at least switch to Go,a language which (as I understand) provides for some basic static type safety, good performance, etc.
I spent six months working in Python a while back and I'll be intent to avoid it from now on.
His outburst is an emotional manifestation of the different sources of frustration I've had working with Python that I've tried to document.
A common theme I've seen in responses is that many claim that I've mistaken Python as a weakly-typed language countering that it is indeed a strongly-typed language -- obviously an argument of semantics. I'll define a "strongly-typed" language as one where it is trivial to identify the type signature and definition of any function or variable in a program. In Python, this is non-trivial.
Wow, I had the same error as Hotz's last night, it crashed the program after about 15 min of calculations, which then had to be rerun. I couldn't even blame myself, because that was someone else's code.
I have exactly the same reaction to C#, C, Forth, Pascal, Prolog, etc: regardless of the typing strength or compiler nitpickiness there will be scenarios where stuff doesn’t go as planned because I didn’t understand the problem domain sufficiently, and these errors serve to enhance my understanding of the same.
Regardless of language, x -> a + 1 is not going to work if a never has a value. In many cases handling “a doesn’t have a value” is the wrong way to address the problem, because the problem should be solved as “figure out why the data collection didn’t work, and propose a strategy for handling our dataset when either a value is missing or is provably wrong.”
As an example one might assume that a video feed has exactly as many frames per second as it says on the tin (eg: 24fps will have exactly 24 frames for each second of video) but in reality there will be missing frames due to data corruption, or even a mismatch in clock speeds meaning that over 100 seconds you have 2398 frames instead of 2400. How do you handle the missing two frames? The problem is there are no frames missing, it’s just that they were never there to start with, and frame 1237 of another source has no direct equivalent in the slightly-faster stream. You might alter the clock for processing, duplicate one or two frames, use interpolated frames, or otherwise process the various sources so that you get a consistent synthesised data stream with which to do further processing.
86 comments
[ 2.7 ms ] story [ 143 ms ] threadSelf-aware clickbait is the worst kind of clickbait.
In fact, before the web, authors have long used fancy titles for articles that you had to buy the magazine or newspaper to even read, so they had no advertising-like value to attract unsuspecting readers.
Were you self aware when using a double negative?
"Look at me using the word hate misleadingly, hate is such a strong word". Is it though? This is like the title-writing version of /r/madlads. :-)
This isn't right. Weak/strong typing is on a different axis than static/dynamic typing.
Python is strongly typed. There's no implicit conversion among types (which is one marker of weak-typedness). For proof, consider:
Strongly typed means that the types are protective, in a way. You put a value in a box labeled X and that's it — you can only treat it as an X from now on.In a weakly-typed language, if your function expects an X and you give it a Y, well, it'll do its best to make due.
Many people think that because Python is dynamically typed (i.e. you can pass anything anywhere without compile-time restrictions), that it must also be weakly typed. This is simply not the case. If I write a function which expects an integer and I give it a string, I'm going to have a bad time despite the fact that the code "compiles" and only fails at runtime.
---
Edit: I've read further, and the author actually uses their lack of knowledge on this subject to portray the Python community as being willfully misleading in their promotion of the language:
> Python markets itself as a dynamically-typed programming language, similar to Perl, Ruby, or JavaScript. The word “dynamically typed” has a positive connotation, more so than “weakly typed.” Conversely, the word “strongly typed” sounds more positive than saying “statically typed.” Diction matters, because proponents of different camps will select a word-choice that reflects their bias of one programming language over another. Python is both weakly-typed and dynamically typed, which contrasts itself to languages like Java, Scala, C/C++, and Go which are strongly-, statically-typed.
C is statically typed, but it is also weakly typed. You can treat any value as any type if you so wish, either by explicitly casting or by removing checks for implicit casts during compilation.
I'm going to finish reading this article, but... I don't think I respect this author very much, based on what I've seen so far.
---
Edit 2: While the author's points would be valid by themselves (GIL, build toolchain, dynamic typing), the over-abundance of negative rhetoric shows that the author was merely interested in writing a smear article of Python, essentially. I think it's a pretty weak article overall, and would have been aided by a more fair comparison with notes about why some of these design decisions were made (or at least a less-slanted writing style).
Edit: I was going to mention the author's confusion, but OP did it in their edit.
Edit 2: The author is "Staff Software Engineer at Tesla" ? And he doesn't know C pointers ?
As an aside, I work in PL research (as an assistant) and have never talked to a researcher who disagreed with the assessment I gave above. I'm not saying you're wrong (because you aren't), but rather that there is at least enough of a distinction that the author shouldn't have conflated them to the extent that they did.
A few core things:
The GIL problem is frustrating. I generally just try to avoid writing threaded code and instead execute calls asynchronously or run parallel instances and work with Celery or something similar. For web applications, this tends to work well enough for me personally but this is definitely a valid issue.
Python2 vs Python3 is sort of a done deal at this point, in my opinion. I was a big holdout on moving to 3 for a while but, for the past couple of years, I haven't had more than 1 or 2 cases where I had serious issues. I don't have to deal with legacy codebases though.
On a larger note, I would say that this all points to the fact that it is easy to do things the "wrong way" in Python. The accessibility of the language is great, but it is not without its issues if you go in expecting it to be a fenced-in playground with batteries included (to mix metaphors).
In terms of progress I love what .NET has achieved and found that libs like Nancy [1] are good to explore.
Disclaimer: I am not working as a professional programmer anymore so I look for fun and "move faster" tools.
[1] http://nancyfx.org
I also think that the whole "drop down to C argument" is sometimes viewed as a cop-out when criticizing python, but I personally believe that being proficient at high-level and low-level languages, knowing their strengths and weaknesses, and transitioning between them when it makes sense is stronger than just using purely high OR low-level. It's sort of like having multiple gears on a car: you need all of them, and they are all useful and most opportune in different scenarios.
Hammer is viewed as a ubiquitous [sic] tool; however, its design limits its potential as a reliable and high performance drill. Unfortunately, not every carpenter is aware of its limitations.
For what it's worth, '[sic]' doesn't necessarily mean "that was spelled incorrectly", only, literally, "thus": i.e., "that's the way it was in the original; don't blame me." As such, it can be used to indicate misspellings or 'misconceptualisations' ("I know that it's the wrong word, but it's the one that was there"), as nmyk (https://news.ycombinator.com/item?id=17484468) indicates.
I think this is true for many second generation languages, not just Python. Perl, Ruby and JavaScript are really no different. None of these languages were designed to scale, be cloud oriented and perform close to C. Thus, many developers complain of 'hitting the wall' and try other languages because their tools were not intended nor designed to do these things.
I also think Go (and languages like it) which were designed to scale, use the cloud, be safe and fast (performance close to C) are the solution and the future. Here's a quote from Ian Lance Taylor, "Go was deliberately built from the start to support large scale programs implemented by hundreds or thousands of different programmers. Those kinds of programs are written at Google, and Go was designed to be used to write programs at Google."
More from Ian on this topic here - https://www.quora.com/Will-the-Golang-code-become-unmaintain...
Static typing means you know the type of something at compile time. Dynamic typing means you don't. The upside to dynamic typing is that the compiler doesn't have to prove much about your program; the downside is that it can't really prove much of interest either. Strong vs weak typing is about how much your language is willing to fudge types in order to make an operation succeed without errors -- be it the intended result or not. Both static and strong typing tend to make errors be more obvious sooner. Both dynamic and weak typing tend to require less boilerplate to make your program do things.
Python doesn't know the types of things in advance (dynamically typed), but it is very picky about which types are allowed to interact (strongly typed). Even in cases where the answer is obvious, like str.join, you still have to make everything strings: "".join([1, 2, 3]) will fail.
To demonstrate why these are orthogonal, consider the other cases.
Haskell is strongly and statically typed. A program that adds a string and a number together will be rejected at compile time because the compiler can prove it's invalid.
C is weakly and statically typed. A program that combines a string and a number may be accepted by the compiler, who interprets the number as, I dunno, a wchar_t or something. Another example of weak typing is how C allows (heck, encourages) pointer arithmetic and untagged unions/structs.
JavaScript is weakly and dynamically typed. A program that adds a string and a number together will, at runtime, convert the number to a string and then concatenate the two.
"It’s virtually impossible to statically analyze"
and
"In Python a good amount of errors are discovered at runtime"
> The first glaring issue is that I have no guarantee that is_valid() returns a bool type.
You can name functions ridiculous, unhelpful and misleading things in any language. The very fact that you know to expect a bool undermines the point: clearly, despite not having static types, a human managed to make a pretty reasonable assumption about what a piece of code was about to do. Just because the compiler can check that is_valid returns a string doesn't mean that hat's magically a useful thing to do.
> The fact that I cannot, in the absence of perfect test coverage, verify the syntactical correctness of someone’s code is a big red flag.
Syntax errors are caught by the compiler. Semantic errors aren't caught by the best type systems. At best, type systems are a way to encode a bit of the semantics so that the compiler can prove things about it.
> To improve static type checking in Python, developers made libraries like jedi, pytype, mypy, and toasted-marshmallow, but they are often not used in most projects because they do not belong to the standard toolchain
I can not imagine the author actually used jedi and believes this. That's... not how jedi works. I also don't know what to do with the argument of "oh, you have an optional static type system but people don't use it because it's annoying, this is why we should use a mandatory static type system" other than shrug.
I like types! I think there are great arguments for types. I like the idea of being able to have a type system prove that certain bugs can't happen. This adds nothing but FUD and no new insights.
[0]: https://danluu.com/empirical-pl/
In my experience this is roughly equally true with statically typed languages as it is with Python. The types of errors the compiler helps find before running tests are just not important. They are not types of errors that matter much. And if compiler errors are cryptic enough, it can be a huge huge pain that you can’t just run the code on a toy input to see what’s going on. The whole pitch about how static typing and compilation will save you time by forcing more correctness prior to testing is just a hogwash myth in practice. It happens occasionally, but not nearly enough to justify all the stuff advocating it, and the benefits of being able to debug according to desired behavior and just not care about strict safety in edge cases you can guarantee you won’t hit are really tangible. You cannot easily dismiss these benefits.
In fact, C and C++ are both strongly typed, but they are both memory unsafe.
However, C++ provides enough static typing to make it easy to write memory safe (but single threaded) programs.
Also, tools like valgrind get you 99% of the benefit of memory safety, which is good enough for debugging. C++ lets you override the allocator, and build in inexpensive (fast enough for production) checks for most use after frees, memory consumption profilers / leak detection/ etc.
I’d argue that python is memory unsafe in practice, since we get segfaults from third party dependencies in python about as often as we see them in c/c++.
Crucially, C and C++ provide enough tools to get static type safety on the error handling path.
Errors like ‘assert “” is not None’ and missing field exceptions in python error handling code are frequent, but unheard of in C/C++.
If you use std::string, adding a in integer does something well-defined (rtfm). Adding an int to a char widens the result to the int type (foiling attempts to use it like a char without a downcast, if you use -Wall -Werror). Adding an into to a char* does pointer arithmetic, which can be used safely, but there are better string / buffer types you can implement in an afternoon, so do that instead.
You are refuting my point but provide no evidence and implicitly redefine several terms in a way contrary to common usage.
Is there a definition you can pen down? Because C sure isn't strongly typed in the Liskov, Jackson or modern usage senses of the term.
> Also, tools like valgrind get you 99% of the benefit of memory safety, which is good enough for debugging. C++ lets you override the allocator, and build in inexpensive (fast enough for production) checks for most use after frees, memory consumption profilers / leak detection/ etc.
Would you say that de facto C/C++ has a memory safety problem? If it does, why?
(Also, not germane to my point.)
> I’d argue that python is memory unsafe in practice, since we get segfaults from third party dependencies in python about as often as we see them in c/c++.
Do you have extraordinary evidence to go with this extraordinary claim, or are you just talking about your own anecdata?
(Also, not germane to my point.)
> If you use std::string, adding a in integer does something well-defined (rtfm).
My argument was not that it is poorly defined. Weak typing doesn't mean UB.
Strong and weak typing are poorly defined terms.
I’d argue that type errors (not including memory safety errors) should not occur at runtime in a strongly typed language, or at least that a “stronger typed” language would admit fewer classes of those errors in practice.
I’d argue that de facto, modern C/C++ does not have a memory safety issue. Operating systems like OpenBSD and many other secure, hardened network daemons provide existence proofs that are not available for languages like python.
In contrast, I’d argue that python has severe type safety issues.
I’m not sure why evidence that “strongly typed” python in practice suffers from all sorts of typing errors that “weakly typed” C/C++ avoids is not relevant to your point.
I also don’t understand why you dislike the string examples, which explains the (strongly typed) semantics of the operators you mentioned in your comment.
By “strongly typed” do you mean that all operators return the (single type) of their parameters?
I don’t know of any languages that enforce such a thing.
Perhaps you mean “strongly typed languages only include operators that are to my taste”? That’s surely not a useful definition.
> I’m not sure why evidence that “strongly typed” python in practice suffers from all sorts of typing errors that “weakly typed” C/C++ avoids is not relevant to your point. > I also don’t understand why you dislike the string examples, which explains the (strongly typed) semantics of the operators you mentioned in your comment.
I have no idea what evidence you’re referring to, or what examples I supposedly dislike.
To any readers that made it this far, and are hoping for something precise, here is a minimally useful definition of “strongly typed”. I’ve never heard of a broader definition of “strongly typed”:
The language’s type system must be embedable in a lattice, and at runtime, it is guaranteed that functions aren’t passing the top element around and then using it without an explicit downcast.
C/C++ meet this definition, since you need to cast void* before dereferncing it, and void isn’t a valid type for a variable. Also, the only union types are defined at compile time.
Python does not meet this definition because functions can have multiple return types due to control flow, which means you can create call sites that return things like lub(integer, string), and then compose them until you have something of type “top”, and there’s no way to statically guard against usages of such a type.
In C++, the only thing passed around at runtime is void* . The pointer is then used as a concrete type without any explicit downcast.
Any conversation about strong typing should not involve the word "static". Nothing about strong typing is "static". It is entirely a runtime concept. Functions potentially having multiple return types is entirely an issue of static typing. Not strong typing.
This is obvious because you can annootate python code with the type information. This makes it strongly typed, but not statically typed, even though it makes all unions compile time defined.
In other words, if I take python with type annotations (which is statically typed), and remove the annotations, you claim this would make the language weakly typed, but this makes no sense. There would be no difference between strength and dynamicness.
A better definition might be that a language is weakly typed of it allows transformation from type a to b, where a and b are unrelated (ie neither inherits from the other), without a call to a constructor. Python does not meet this definition, the only way to cast is via a constructor call.
C and c++ do not, void* allows arbitrary transforms. Java will throw a classcastexception, but can only do so at runtime, bit will do so at the bad cast.
C and c++ will keep chugging until the miscast object is misused. That's weak typing.
I work as a research assistant in a PL research lab and have talked with many PL researchers who have been in the field for a long time. They all use the terminology consistently, and it all lines up with what lvh has been saying.
I agree that the terms are poorly defined, because there's not a single definition which can be used as a criterion for evaluation. But that Python is more strongly typed than C seems to be a common sentiment, at least among people that I explicitly asked about it. Much C code practically relies on the weakness of the type system to function, whereas Python cannot be deceived in the same way.
> I’m not sure why evidence that “strongly typed” python in practice suffers from all sorts of typing errors that “weakly typed” C/C++ avoids is not relevant to your point.
Typing errors are evidence of a strong typing system...
> I also don’t understand why you dislike the string examples
Your string example relied on the weak typing of C to work. The conversions are implicit, aren't they? This is a hallmark of a weakly-typed language.
Most likely a char or a * wchar. Yeah, random pointers.
In practice it doesn't really show up: the cases where code is actually CPython-specific usually imply all the work is being done in highly optimized C bits. Hence, those programs, despite being run on CPython, aren't CPU-bound.
Finally: while this FUD is really pernicious because it used to be hard/impossible to get scientific code like numpy to run on PyPy, that hasn't been the case in a long time. Right now you can just pip install numpy and it'll just work.
The python 2 vs 3 issue isn't really an issue if you're starting with the language. There was a time where lots of big libraries were only supported by python 2, but that's definitely not the case any more.
OTOH the build system is definitely a common pain point, and the GIL forces you to use other services (e.g. redis + multiprocessing) to achieve horizontal scaling which is unfortunate. And of course python's crappy performance is something anyone looking at languages should be aware of.
However, what is the cost of addressing them? Slower development (core lang and ecosystem). Less use in niche fields. Less flexibility to adapt to new challenges.
Python is a rusty toolbox that's been put together over 27 years.
You know why it's still around? Because people can use it to do the things they want to do.
Fixing all the warts would give us the language we need now... in about 15 years. Which would by then be useless.
> This is unsettling if you’re coming from a Java or C/C++ world and are expecting access modifiers
It's unsettling to a lot of people, but so what? I still don't see a solid use case for where member access (not "privacy") is a good idea. Python is one of the only languages to get access modifiers right (convention), imo and I don't even like Python.
You could implement some "privacy" with a lambda closure (which gets increasingly complex), but that's one more technique that lambdas are ill-suited for.
> lambda closure
Just FYI (not intended to be mean or anything), in formal PL theory this phrasing doesn't make sense. A closure is the pairing of an anonymous function (a "lambda") with an environment. Python's "lambda" is really just a lambda --- it is not a closure. This is revealed in the common pitfall where you try to create a list comprehension using a lambda to generate interior values.
It's a clusterfuck of half-baked, non-standard, spaghetti code approaches.
The Zen of Python says "There should be one-- and preferably only one --obvious way to do it." Python package management makes that in to a joke.
This is a false dichotomy -- there are actually many different ways to package a Python module, including source distributions (sdists). These are just the two most well-known types of built distributions.
> The numpy module is a wheel, i.e. it is a source distribution that depends on a handful of system libraries — gfortran, blas, lapack, atlas — to compile properly.
This is not correct, a wheel is specifically not a source distribution, it is a built distribution. The dependency on platform-level libraries has nothing to do with what type of distribution it is.
NumPy publishes a lot of wheels for a given release (https://pypi.org/project/numpy/#files) each of which is platform-specific, since they are pre-built (pre-compiled) for a given platform.
In the event that a built distribution isn't available for your platform, that's when pip falls back on the source distribution, which requires the build step (hence the dependency on gcc or clang).
- large and inconsistent stdlib api,
- extremely wordy but somehow still vague documentation
- overwrought build/packaging system (although tooling has improved)
- endless runtime gotchya debugging in production
- almost all the baggage of OO boilerplate but none of the benefits of type-checking.
- half-baked functional paradigms
Although I would add the incredibly slow process start time to the list as well.
I took over a smallish half-completed web application written in python a couple of years ago. Nothing major - simple business-y analytics/data viewing app with some fancy charts and tables etc - perhaps a couple of thousand lines of code interfacing to backend database unique to our business. It had some tests, but nowhere near 100% coverage. I needed to finish it off and then get it into production.
Long-story short, trying to understand someone's half-completed python and make changes to this was a HUGE nightmare for precisely the reason the article outlines: python expects you to remember all the minutiae of all the code you are calling. If you cant remember, then you need to suck it up and pick your way through the code and mentally keep track of what is going on at every stack frame.
So instead of thinking about the actual problem you're trying to solve, python forces you keep track of all the little bullshit that you shouldn't need to care about. And even then, you cant be 100% sure you got it right until you exercise that code (either through 100% test coverage, or - shudder - at run-time at 3am on a Sunday when a user in Japan logs on).
So far from being productive and "easy to use", python for us was a nightmare of low-productivity and frustration in only a very small application being worked on by just 2 or 3 remote developers. I cant imagine the levels of pain and suffering for larger projects & teams!
The "solution" is apparently extensive & specially crafted comments that specifically explain the types used (that of course needs to be kept up to date) - I find this fairly odious (why not just use a typed language if you need to put the types in the comments?!)
I know now that Python is looking at type hints as part of the language (PEP 484 - https://www.python.org/dev/peps/pep-0484/) that should help a lot.
Still, its not all bad. As a result of the pain of this application (which incidentally was damn slow in production due to the processing we had to do on the data in the python backend before we sent it to the browser), we decided to learn golang and use that for future work instead and have not looked back.
Golang has the same feeling of fast-paced "throw things at the screen" productivity, but is obviously typed (so you know about errors before your users do) and orders of magnitude faster.
I have seen python code written this way. It's not a joy to work on. This function returns this type of class or this other type of class based upon input, and the two are not interchangeable say, so you have to refactor a bunch of code if your type changes underneath you.
The typical pattern is a method should return something of one kind of class only or raise an exception. And then newer tools like PyCharm can help you out here by knowing what the functions are going to return.
- The indentation based syntax: it can easily trip you up when moving big blocks of code around. You want to put this if else block somehwere else, hope you get a syntax error, because you can get sth. that seemingly works but does not do what it's supposed to do. Also sometimes finding where you are can be difficult.
- Dogmaticism: a big thing in the community. They'll wait 20+ years until they add string interpolation, a feature common in many similar languages. They'll wait 20+ years until they start to realise having expression equivalents of statements is useful.
- Breaking syntax: new syntax is added in what seems to be minor releases, so you either avoid new syntax to be compatible w/ all the 3.* interpreters, or you need to expressly avoid certain interpreter versions. And there's no equivalent of Perl's "use v5.something;" so you need to write code juggling multiple supported versions.
Still I think it is a nice language to have in your toolbox for the vastness of the stdlib and the available 3rd party packages.
Some of the largest-scale enterprise distributed systems, as in, at the telecoms level, are written in erlang, which is dynamically typed, and highly reliable.
All Erlang variables are immutable; it's not just a default.
I know that many libraries and Python standard lib itself predate this PEP but I don't understand why Python 3, which broke backward compatibility anyways, did not fix that at least in the standard library.
No doubt your typical Math Ph.D. type doesn't want a language with a step learning curve to implement their expertise, so what to do?
Perhaps everyone could at least switch to Go,a language which (as I understand) provides for some basic static type safety, good performance, etc.
I spent six months working in Python a while back and I'll be intent to avoid it from now on.
https://www.youtube.com/watch?v=7Hlb8YX2-W8&t=355s
His outburst is an emotional manifestation of the different sources of frustration I've had working with Python that I've tried to document.
A common theme I've seen in responses is that many claim that I've mistaken Python as a weakly-typed language countering that it is indeed a strongly-typed language -- obviously an argument of semantics. I'll define a "strongly-typed" language as one where it is trivial to identify the type signature and definition of any function or variable in a program. In Python, this is non-trivial.
FYI, this article is also cross-posted on:
https://news.ycombinator.com/item?id=16337435
https://news.ycombinator.com/item?id=16371338
https://news.ycombinator.com/item?id=16411765
Regardless of language, x -> a + 1 is not going to work if a never has a value. In many cases handling “a doesn’t have a value” is the wrong way to address the problem, because the problem should be solved as “figure out why the data collection didn’t work, and propose a strategy for handling our dataset when either a value is missing or is provably wrong.”
As an example one might assume that a video feed has exactly as many frames per second as it says on the tin (eg: 24fps will have exactly 24 frames for each second of video) but in reality there will be missing frames due to data corruption, or even a mismatch in clock speeds meaning that over 100 seconds you have 2398 frames instead of 2400. How do you handle the missing two frames? The problem is there are no frames missing, it’s just that they were never there to start with, and frame 1237 of another source has no direct equivalent in the slightly-faster stream. You might alter the clock for processing, duplicate one or two frames, use interpolated frames, or otherwise process the various sources so that you get a consistent synthesised data stream with which to do further processing.
Strong typing doesn’t help solve real problems.
If I had a dollar for every time a bug was introduced in Python code due to incorrect indentation during code refactoring, I would retire already.