3 ms·
I am not going to dispute the value of compile-time type checking. And I admit to only having had time to skim the link at this point but it looks like a great
by dbcurtis 7y ago
I am not going to dispute the value of compile-time type checking. And I admit to only having had time to skim the link at this point but it looks like a great read so I bookmarked it for later. That said...
Python can be as strongly typed as you like it to be. But of course, with Python, instead of insisting on an incoming parameter being of a particular type, the thing to do is Pythonically ask nicely if it can be the type that you want. If you need parameter x to be a float, call float(x), which will raise TypeError or give you a float. And of course, it gives the implementor of the class the option to implement __float__().
With a small amount of effort, you can design all of your __init__() constructors to do that style of validation. Yes, it requires some elbow grease. But you will not be able to pass bad stuff to my __init__ constructors if I don't want you to. And if you want some parameter y to be an instance of Foo, just call Foo(y) and if it is already a Foo, you get the Foo, and if it can be made a Foo, you get a Foo, and if it can't, you get TypeError.
So of course you actually need to exercise the code also, which is where the hypothesis package comes in.
Anyway, I think one of the reasons Python is popular is that it is very consistent about types. Haskel and Rust are also very consistent about types -- most other languages, not so much. Give me one end of the spectrum or the other. The murky middle is suboptimal.
- joshuamorton 7y agoThis doesn't work as well as you'd hope. It requires among other things, a unique and toilsome coding style, constructor overloading (which isn't a first class citizen of python), and you only get late runtime validation (which means, for example, you can easily miss type validation on only one side of a conditional). Something like pytype detects such errors without any code modifications.
- dbcurtis 7y agoMost of what you say I agree with. But it is actually very easy to construct a __init__ that handles a very flexible parameter list. That style is well represented in the standard library. And I find that writing a beefy constructor simplifies parameter validation in methods. But certainly, it doesn’t remove the need for good test coverage. Now, if you want to argue for less testing, that is a different pitch.
- joshuamorton 7y agoPossible, yes. Easy and straightforward, no. Casework and duck-type checks over args and *kwargs are not easy to maintain going forward, especially for a non-expert in Python. Factory methods are usually better, and I believe what the more modern parts of the standard library have tried to use, but they don't work as well for this specific case.
- musingsole 7y ago> Casework and duck-type checks over args and *kwargs Once you start doing this in python -- beyond any low complexity usage -- you're better off capturing your arguments in their own object and passing this to the function as a single argument. This object can then enforce whatever you want, and your function can now be a class method. And then you can build a factory for this object that accepts a few args, maybe some keyword parameters. But, once that gets reasonably complex, just rinse and repeat. It's objects, methods and factories all the way down. And unlimited billable hours.
- Too 7y agoYou can get compile time validation for this type of construct using typing.Union. But once you have type validation i don't really see the use case anymore. Common example of how silly this is is functions that have a special case for handling lists of length 1 by allowing the object to be passed in without being part of a list, eg Union[List[Foo], Foo]. Now you need extra code to handle that special case in your function and the type annotation becomes more verbose. Much easier to just enforce List[Foo] as the only way to call your function and let the caller call you function as do_stuff([x]) if they only have a single item x. The IDE will complain instantly if you try to call it as do_stuff(x) so there is no risk to accidentally call it like that.
- pilif 7y ago> which will raise TypeError At runtime. In production. A compiler would catch the problem at build time, never causing an error in production.
- kalleboo 7y ago> A compiler would catch the problem at build time And a nice IDE/code editor would catch the problem as you're typing it
- pilif 7y agoNot in a dynamic language. Finding all possible inputs to your method that’s calling float() on its Arguments in a dynamic language is akin to solving the halting problem. Also, error messages in an IDE can be ignored. Compiler errors can’t.
- dbcurtis 7y agoMaybe you missed this part: > So of course you actually need to exercise the code also, which is where the hypothesis package comes in. These are tests you should be doing anyway. Type checking does not excuse poor test coverage.