Had I had MyPy back when I was working in data science, I would've saved countless hours and headaches.
No, the code in data science isn't bad because of the lack of typing. The code is "bad" mostly because those writing it are relatively fresh from starting to program. Also there is more pressure to make things possible, often just to run it once, and neglect repeatability or scaling to larger code bases. Different emphasis. That doesn't mean an experienced full stack developer would do Data Science better, because he might lack a lot of skills that matter more in that domain.
I'm not sure if that's true. Doesn't Pandas handle ETL and some anaysis? There is nothing inherent to ETL that makes it a hard problem with compiled languages.
In your opinion, what does Pandas do that's hard to do with compile-time languages?
This even starts with basic Numpy and handling tensor objects. It's not easy for a type checker to understand what operations you can do with what shape of tensor. Worse, most often you don't know (or want to know) some of the dimensions or even dimensionality of some of the objects. Then it is impossible to check all of this at compile time.
That doesn't sound like a Python problem.
Instead, it sounds like the natural consequence of numpy being designed in a way where their data types aren't organized into subtypes, and leave that as runtime properties. This is a natural reflection of numpy's take on vectors, matrices, and tensors, which in terms of types are just big arrays with runtime properties.
To put things in perspective, in C++, Eigen supports static dense vectors and matrices whose size is specified and known at compile-time. I'm sure Python doesn't impose addition static type constraints than C++.
Significant types in ETL-style applications typically come from outside (e.g. a certain CSV column in the input file contains a date in YYYYMMDD format, or maybe YYYYDDMM, figure it out, and don't forget time zones or your accounting will go wrong).
Then types are mostly complex but obvious and easily deducted (e.g. multiplying matrices of compatible shapes necessarily gives a matrix of a certain shape, why should the program say anything more detailed or lower-level than "do a matrix multiplication" or "do a tensor product"?); they are an often dynamic and unpredictable property of the data, not a useful abstraction.
What, mutate data with known structure? What exactly do you imagine is hard about this, let alone notoriously so?
A pandas dataframe types to something like Iterable[SomeKindOfProductType[int, str, str, ... (78 other columns)]]. The formal type of a dataframe in the middle of a transformation is... not very useful to know.
The number and types of the columns aren't necessarily known at compile time. Which leads to runtime errors. Even in a statically typed language, such dataframes are a kind of "dynamic typing escape hatch". As complexity of a software increases, such mechanismus of dynamic typing and throwing runtime errors creep up all over the place.
It's certainly very useful to know, even if that knowledge is indirect via the type of the resulting dataframe after all the transforms.
This resonates with my experience. I had the opportunity to work on a DS codebase written entirely in Scala with all the typing, parallelism, actor model, whatnot. Basically I joined the company because of this technical factor. It was fun until I figured out that DS was "typed IF-THEN-ELSE written by Java devs in Scala returning stuff the users complain about with high reliability within milliseconds". Now I am happy to be back to the single threaded untyped Python world. Still no bugs in production, because we validate all requests to death, have unit tests and integration tests running on real data not on mokups. Basically we follow the principle: if the integration test passes, our typing is just right, or at least good enough. Funnily all the typing errors we catch are caused by wrongly typed data, coming from the productive system written in a typed programming language... what a strange world.
I can see you are enjoying the life outside of a highly regulated industry. Having certain kinds of production data in tests (or feeding that to test environment) would be a major audit finding in any finance or healthcare company.
Makes for both a blessing and a curse.
...thereby destroying the production-like features for which you want it for testing, which you then need to recreate and reintroduce, so you might as well just synthesize test data in the first place, since that's what you end up doing anyway, in effect.
No, in actual practice you don't scrub the stuff you actually need to test.
No, I’ve spent ~20 years in healthcare, with this issue as a frequently recurring issue.
> No, in actual practice you don't scrub the stuff you actually need to test.
In actual practice, the stuff you really need to test often overlaps with the stuff minimally required to scrub to legally deanonymize the data. The most common scenario I’ve seen trying to do this is both creating most of the work of generating synthetic data and failing to legally deidentify the source data.
This is the largest difference. When there's no expectation of code lasting beyond a very short lifespan, why go through the effort to future proof things, improve maintainability, have better ergonomics, etc?
Sounds a lot like F# type providers to me.
https://thesharperdev.com/introduction-to-fsharp-type-provid...
And good luck teaching beginning scientists F#.
The output of most analytics is a report. For longer lived processes it seems easier to just hand off
Static typing is completely viable and I hear those arguments mostly from those who didn't use Python for a long time. The added productivity makes up for a little more debugging while the program is running.
I'd also posit that many Python codebases are somewhat less "untyped" than one might assume. Django for example does a lot in its ORM and Form classes to make sure the right stuff goes in the right slot.
The added productivity of non-typed Python is such a ridiculous myth to anyone who has to maintain significant Python code bases. Sure, it makes you more productive for one-off exercises but the moment you’re having someone else inspect your code the productivity gains vanish. This largely true of dynamic languages more generally except in niche caches (e.g., ORMs) where types are strongly implicit.
I say all this as a mathematician that is used to reading papers full of symbols packed with implicit information. The code I’ve read from gung-ho dynamic typing advocates is some of the most painful drivel I’ve ever had to wade through.
I can compare Python's productivity to other languages, and it beats all of them so far. Another lesson I learned from working with statically typed languages: The quality of the code is more dependent on who writes it than on the language or even the ecosystem. But people are productive much sooner in Python than they are in C# or F#, and then we can work on the foot-gunning.
Python's productivity isn't a myth. This is proven by its popularity and by successful projects across academia and industry alike.
There's also the argument that one is most productive in what they know. As someone who has spent far too long with Haskell and OCaml, Python's semantics and libraries often seem surprising and counterintuitive to me.
I know that Typescript helped me over Javascript. I don't get that benefit from Mypy yet, but the "pain" just isn't as great as it was with JS. And I've seen what inexperienced programmers do when a static typecheckers try to force or encourage them to do the right thing. It's not necessarily the right thing, just a lot more complicated than it needs to be.
No, I have worked at a variety of large institutions that have millions of lines of Python. I work with it often. The use of Python is more about it being accessible for non-developers, which is why they also teach it in schools; and the need for a standard. It is the new "Visual Basic". Again, it is not more productive in all situations.
There are a lot of "libraries" people write for their use only, who then put them on Github as a token of good will.
You as a user of someone else's library can either contribute to it or learn to use it. Otherwise, write your own equivalent.
And be judged for wasting time reinventing a wheel?
That is never a waste of time.
I took a random stab in pandas but they’ve adopted type annotations so that defeats the point. So i thought old-school code bases - zope sprang to mind, but that’s not a good example either because they use docstrings and it’s kind of obvious. So i tried gunicorn on a whim and they have even more attention spent on their docstrings.
I just want to sample this feeling of lost confusion in python so that i can figure out how I’d deal with it.
def _new_conn(self):
"""Establish a socket connection and set nodelay settings on it.
:return: New socket connection.
"""
...
try:
conn = connection.create_connection(
(self._dns_host, self.port), self.timeout, **extra_kw
)
...
return conn
(I've chopped it down a bit to emphasise the important bits)What's the type of "conn"? It certainly wasn't clear to me that it's a socket.
But just now I looked at upstream and I notice they've added type annotations, which do greatly improve things:
https://github.com/urllib3/urllib3/blob/e16beb210c03c6f5ce4e...
def _new_conn(self) -> socket.socket:
...
So a small victory for type annotations there.That docstring isn’t enough for the problem you had though, e.g. I wasn’t clear from that if they meant a socket.socket or maybe some special socket derivative type so i hit go to definition and create_connection is a short method - i can see it is indeed a socket.socket being created, configured and returned.
I’m guessing you’re not using any developer tooling, maybe a straight text editor i guess?
This kind of tooling has been around for a long time, i remember using rope and jedi in vim back around the late 2000’s i think. It’s gotten a LOT better since PyCharm appeared mid 2010’s i think - first i recall using it must have been around 2014-ish. These days, it’s even better. I would really strongly encourage adopting some kind of tooling that lets you navigate and has some basic refactoring - rename is usually 80%+ of the value there so it doesn’t need to be complex. A few ergonomics like having the ability to jump to test and call from repl with a keystroke - you’ll turbo charge your comfort and productivity.
That has to be our difference i think because it took one mouse move and one key chord to completely bottom out.
- Static type systems are an effective form of documentation
– Static type systems reduce the effort to fix type errors
– Static type systems may not be helpful in preventing semantic errors
It would have been awesome to have evidence for #3 but even just #1 & #2 could be useful, albeit to a much lesser extent. Could we quantify by how much?
It’d be important to quantify because if static types mean a developer can be done sooner, then they have spare time to spend improving the software. So do these differences with static types translate into benefits for the developer?
The author of that paper says no. He published this other paper just after that one: https://www.ics.uci.edu/~jajones/INF102-S18/readings/23_hane...
Other take home message is that the famous 10x programmer was found in both groups. The one with dynamic language was faster, just as the argument for dynamic typing goes.
The benefits of masks against spreading respiratory viruses are easily and objectively proven. Completely different thing.
I've also programmed extensively in Pascal, assembly, and C. I get it about types. Don't forget C has 8 different types of integers that have to be kept straight. ;-)
On the other hand, I realize that any of my programs is but a little nub of code sitting on top of large and expertly maintained libraries. I'm grateful for whatever the authors of those libraries are doing to keep their sanity, and am not surprised when I see type hints in those codes.
In my opinion there is no excuse not to.
Luckily IDEs can auto add the imports, but sometimes a slightly mistyped piece of code auto adds an import I need to delete, I love avoiding imports and circular imports in PHP. The other nice thing is avoiding random python behaviour differences in MacOS, Windows and Ubuntu. Code that works fine in CircleCI but not locally. Works on a Mac but not on windows etc… I didn’t experience that with PHP.
Not sure there are a huge number of long term PHP devs who switch to Python or the other way around to have a reasonable opinion.
Adding stronger typing, like type hints, could reverse that, of course.
The reason not to is performance and reliability. Typing helps with the latter, Python is just bad at the former.
Not to mention the notorious circular import problem, that is much more likely in a big codebase.
If I have a custom class, that class has to be passed as a type hint for functions. What If I use that class in another module, but have to import it inside this one that has a type-hint only?
Well, let me introduce you to their genious solution:
from _future_ import annotations
from foomod import FooClass
def myfun(foo: FooClass):
return foo.bar()
Wonderful, isn't it?Not once did I find the additional typing useful in my actual day-to-day work.
In my experience its the other way around. Most people who are a fan of dynamic typing have worked in systems with types. The world is largely coming around to the view that types make more consumable and safe software
If so, I would counter that adding types isn't particularly onerous and adds very little overhead to development time, and over time they'll tend to increase your productivity since you'll make less errors, IDEs can give you better hints, etc.
As to the percent of Python developers who use types / type checking, it seems to me that's been changing somewhat rapidly over the past few years. It's certainly greater than 1%.
Gradual typing is based on effort. Most people won't spend the effort. And they don't need to, most of the time, either.
Each new line or character is a line to maintain, there is a cost associated with that. Tests have costs but are extremely valuable, they are a no brainer to me, types are way less obvious.
I have spent a lot of time maintaining python types. I have no idea how many bugs it has prevented. I am not sure it is a large number.
I know there have been bugs in types, bugs in mypy and dependency changes which created work which only existed because types were in place. There have definitely been days worth of types maintain.
There are the extra type imports, extra time to run tests and integration tests etc.
It is not as straightforward as you might initially assume.
For me, python is mostly a calculator, and it seems faster to try a calculation and immediately see if it runs and/or looks correct, rather than try to implement type hints et al (though I still use type hints occasionally).
List[Tuple[Tuple[str, int, str], Tuple[int, float, float]]]
Oh, so I am supposed to make my own data structures now, just for typing?How about no?
What is not easy is type-hinting it. What if I want it to be both an list and an iterator? Well, I would have to use Union.
I'll leave it to you to write that Union "type".
Union[List[Tuple[Tuple[str, int, str], Tuple[int, float, float]]], Iterator[Tuple[Tuple[str, int, str], Tuple[int, float, float]]]]
/sStarting from 3.8 (I think), you can also directly use dict[T, U] and list[T] rather than having to import Dict and List.
This as opposed to a legitimate type system where complex types are classes themselves which don't need unraveling. There is no need to alias a type and there is no need to maintain separate complexities for the object and the type.
Type aliases are okay if they're not overly complex, having used them in Python and other languages to good effect, e.g. to avoid duplication or for clarity.
I'd suggest using type annotations where it's not overly onerous--which is probably most places--to get the benefits without much effort but perhaps avoid them or use simplified types where it's too complex.
I also agree that there are places where typing is unnecessary and doesn't add much value, such as in certain scripts.
Like many things in programming, it's all about finding the right trade-offs.
This is exactly the kind of situation where you're supposed to use a named tuple or a data class to make it clear what's what.
Named tuples are just immutable classes with slots. Unless you want to slow your code down considerably, it's best to avoid them.
Thing = List[Tuple[Tuple[str, int, str], Tuple[int, float, float]]]
def foo(t: Thing) -> Thing:
...It's literally a tuple containing two three-tuples inside a list.
Do a for loop over it, unpack it and you are done..