4 ms·
I just got bit tonight by Python's duck typing. I was trying to use PIL to draw rectangles for a super simple script to visualize results from another component
by mynameisash 5y ago
I just got bit tonight by Python's duck typing. I was trying to use PIL to draw rectangles for a super simple script to visualize results from another component. I found an example and tweaked accordingly:
w, h = 220, 190
shape = [(40, 40), (w - 10, h - 10)]
# ...
img.rectangle(shape)
When my results came out totally wrong, I spent 45 minutes trying to figure out if my code that built the shapes was off or if my drawing was off. What I had assumed were rectangle dimensions in the form of `(width, height)` were actually bounding box coordinates `(x2, y2)`. Both of those are tuples of ints, but semantically, they're very different.
I'm growing increasingly frustrated with Python, and in my frustration, I feel like this nonsense could be wholly avoided by stronger typing. Were I to use Rust or C#, two languages with which I have much more experience, I have to imagine the library would prevent this loss of productivity. Simply define a Point struct and it's perfectly clear what is being passed in.
Edit: I shouldn't have said duck typing but rather the lack of strong/compile checks. Apologies for my sloppy thinking.
- pgorczak 5y agoYou don’t need strong typing but good naming to avoid these issues. While a type system kind of requires everything to be named, it really doesn’t matter to the type checker whether these names are semantically correct or have any semantic meaning at all.
- framecowbird 5y agoThere's nothing stopping you doing that in Python. If it's important to you to define a point struct, do that, add a type annotation, and add mypy to your compile. When i used to work with C++ i would spend hours debugging compile issues that were just the wrong type (templates are especially bad!) that really had no impact on the code, it was just a waste of my time. What I like about python is that the static typing is there if you want it, but you don't have to.
- still_grokking 5y agoTo my knowledge Python doesn't have any sound type system. Mypy doesn't provide that. So, no you can't have (currently) proper static typing in Python. It's more like in TypeScript where it's a best effort thing. (At least you get notified when the typer gives up. That's much better than nothing of course!) C++ templates aren't a good example either. Templates are a Turning complete language so they can't provide guaranties in all cases and aren't as such type safe at all. Better examples for type systems and their usefulness would be languages form the ML family (also including Haskell and similar in this case).
- CraigJPerry 5y ago>> Python doesn't have any sound type system Neither do popular statically typed languages like Scala, Java, C#, … - sound in the context of PL theory means the type checker will only accept a correctly typed program. Which is not true for any of these languages i mentioned - you can get the compiler to accept an incorrectly typed program. >> you can't have (currently) proper static typing in Python That’s an even bigger group of languages in that bucket, typing is undecidable in “favourite” languages like Rust, ocaml, c++, f#
- still_grokking 5y agoScala's base caluculus is proven sound! The implemented language as such has minor soundness holes, correct. But you need to resort to "murky" parts (like combinations of null, top and bottom types and sub-type relationships between them) to exploit this. In practice I've never run into any case of this soundness holes. You need to explicitly construct a pathological case. (Just avoid null and you should be safe in any case afik). This can't be said about things like MyPy or TS. There you run into soundness holes on a daily basis. OCaml and F# are undecidable? Also because of sub-typing? And what's the problem with Rust? It doesn't have sub-typing. The problems with sub-typing in general could go away in the future when algebraic sub-typing catches on.
- mynameisash 5y agoI have several problems with this: * I have had the installation of mypy fail on multiple machines and crash (‽) several machines * mypy is a separate component; yes, it's very well supported, but it's not included with python3 by default * It's not required by packages to function, which means that packages can and do simply neglect to add typings. The package I used in my example doesn't provide any such typing information, so me using mypy wouldn't help * Besides mypy, the package in question lacks appropriate documentation (eg, the mouseover of the function I was calling provided nothing in the IDE to give a hint). The method signature defines the shape parameter as being named "xy". Only when I went to the web site did I find that it's supposed to be either List[int, int, int, int] or List[Tuple[int, int], Tuple[int, int]] and that the second pair of values is a point and not dimensions. Were I doing this in another language, the type annotation would likely provide some details. This is my problem with Python -- yes, you can use typing hints if you want, but nowhere is it required. This leads to a world in which I spend 45 minutes painfully debugging this kind of thing only after my code is written. In my experience, this type of bug very rarely exists in stricter, compiled languages.
- petters 5y agoSure that is annoying, but I'm not sure it has anything to do with duck typing?
- nlitened 5y agoI don't think this example has anything to do with "stronger typing". A Tuple<Tuple<int, int>, Tuple<int, int>> would have passed the type check just fine the same way, without revealing your error.
- tirpen 5y ago> I don't think this example has anything to do with "stronger typing". I disagree. There are definitely languages where you could easily differentiate between a "length-int" and a "coordinate-int" in the type system, while still representing it as only an int in memory at runtime. I belive both Haskell and Ada can do this for instance.
- dragonwriter 5y ago> There are definitely languages where you could easily differentiate between a "length-int" and a "coordinate-int" in the type system Python, via the shared portion of the type system enforced by the major type checkers, is one of those languages. The fact that a particular library doesn't choose to do this doesn't mean the type system doesn't fully support it.
- formerly_proven 5y agoIf you have a Rect type that'll likely take four arguments and you can still make the same mistake of confusing the Rect(x, y, w, h) signature with Rect(x1, y1, x2, y2). This of course could not happen if Rect were defined as Rect(Point origin, Size size) but that might get a tad annoying for callers.
- Shish2k 5y ago> Edit: I shouldn't have said duck typing but rather the lack of strong/compile checks Every compiled language I can think of would also be totally happy for you to pass (int width, int height) to a function designed to handle (int x2, int y2) - what language are you thinking of that would make this a compile error? Some languages do allow you to create your own custom types which mean you can spot the difference between two types of int and avoid mixing them up… but python is one of those languages.
- dragonwriter 5y agoIts a library design rather than a language design thing. You could make a separate class Size and Point that both take an (int, int) constructor, and even have BoundingBox whose constructor takes (Point, Point), and the function could take a BoundingBox instead of a list of tuples. Of course, you can do that in Python, just as well.
- chriswarbo 5y agoSounds like you want 'newtypes', e.g. XCoord = NewType("XCoord", int) YCoord = NewType("YCoord", int) Point = NewType("Point", Tuple[XCoord, YCoord]) These are just new names for existing types (in this case the existing types `int` and `Tuple[int, int]`); they make no difference to the way the code actually runs (unlike, say, introducing a new class). However, checkers like Mypy will not let us mix up these newtypes with their underlying types. For example, if a function requires a `Point`, Mypy will complain if we try to call it with a `Tuple[int, int]`. Newtypes are useful for keeping semantically-distinct types separate, even if they just-so-happen to use the same underlying implementation. In other words, they let us selectively avoid duck-typing if it might cause problems. Of course, this is only useful if the code we're calling actually exposes such types!