6 ms·
Here's some rational "hate" for Python then. I just returned to Python for the first time in a little while to collaborate on a side project and ran into a few
by CydeWeys 7y ago
Here's some rational "hate" for Python then.
I just returned to Python for the first time in a little while to collaborate on a side project and ran into a few tricky-to-debug errors that caused a fair bit of lost time. Know what the errors were?
In one case, I was iterating over the contents of what was supposed to be a list, but in some rare circumstances could instead be a string. Instead of throwing a type error, Python happily went along with it, and iterated over each character in the string individually. This threw a wrench into the works because the list being iterated over was patterns, and when you apply a single character as a pattern you of course match much more than you're expecting.
And another one, I typoed a variable name as groups_keys instead of group_keys (or vice-versa, I don't remember). Instead of just throwing an error, Python happily went along with it, used an uninitialized value, and then all the logic broke.
There's entire classes of errors you can cause yourself in Python that aren't possible in stronger, statically-typed languages. For a large project, I'd pick the old and boring Java over Python every time.
- winstonewert 7y ago> And another one, I typoed a variable name as groups_keys instead of group_keys (or vice-versa, I don't remember). Instead of just throwing an error, Python happily went along with it, used an uninitialized value, and then all the logic broke. Python doesn't have uninitialized values, it throws NameError when you try to access a variable that hasn't been set. So I don't see how this could have happened.
- CydeWeys 7y agoOther way around, the typo was in the name of the variable being set, so it defined a new one instead of modifying the existing one.
- deleted 7y ago[deleted]
- klodolph 7y ago> In one case, I was iterating over the contents of what was supposed to be a list, but in some rare circumstances could instead be a string. The creator of a well-known alternative to Python has a single-letter email address, and regularly receives email generated by Python scripts with this exact bug (which means instead of sending an email to "user", sends an email to "u", "s", "e", and "r"). So I’ve heard.
- drawnwren 7y agoIn my CS program, we learned Python as a convenient way to sketch a program. We also learned C++ for speed and OCAML for those functional feels. A programming language is a tool, Python has some great use cases mostly focused around ease-of-programming.
- _coveredInBees 7y agoYour 2nd error isn't possible in Python, so I'm not sure what you did there. Regarding the first, sure, it is a bug that was annoying to catch. But, having an `Iterable` interface in Python is also really neat and useful if used responsibly. If you're programming regularly in Python, you are accustomed to the tradeoffs that come with a dynamic programming language and no static types, and you can still avoid issues like the one above. Right off the top of my head, using type hints with a decent IDE or an assert statement would likely have caught the issue. I'm not saying that Python doesn't have issues (all languages do), but I don't see the error noted above as any sort of deal breaker. On the other hand, if you're only ever going to use Python like a strongly typed language without taking any advantage of its dynamic characteristics, then I can see why it would seem as a total downgrade compared to languages like Java.
- CydeWeys 7y agoI didn't explain the second one well. Here's some exact code. group_keys = ... if not isinstance(group_keys, list): groups_keys = [ group_keys ] So rather than listifying the non-list variable, it was creating a new variable. The cause of this bug is that Python doesn't distinguish between declaring new variables and overwriting existing ones.
- sreque 7y agoI still don't think you quite understand what's going on here. Python wouldn't create a new variable in this case. I It would re-assign the value represented by the variable you already assigned once. I agree that it would have been better if Python had explicit variable declarations (this is one of the few things I think Perl got right.). On the other hand, Ruby made this same mistake. If you wrote this code in Javascript you wouldn't get an error, but you would in fact have two different variables. For instance, this code runs for me using node 8: var fun = (bool) => { var x = 1; if (bool) { var x = 2; x += 1; } else { x += 1; } console.log("x=" + x); } fun(1); fun(0);
- CydeWeys 7y ago
- batbomb 7y agookay, so use type annotations and mypy --strict
- CydeWeys 7y agoIt's not my project; I'm just a collaborator. My experience has been that a very tiny minority of Python code out there is written in this style, so unless you're only starting projects from scratch, you can't benefit from it.
- batbomb 7y agoyou can gradually type (probably don't use --strict in that case). It might not be a ton of benefit if you aren't actually writing new code though. There's a good document on this: https://mypy.readthedocs.io/en/latest/existing_code.html https://mypy.readthedocs.io/en/latest/existing_code.html
- CydeWeys 7y agoAnd that'd be fine if everyone were on board with it and that were the general direction of the project, but I don't think that's true. I've never seen a strict, type-annotated Python project out there in the wild, and I've seen a decent amount of them. A random non-primary-contributor isn't going to have much luck stepping into an established project and getting everyone to go along with adding annotations to the whole thing. And if I were starting a project from scratch, rather than coercing the language to do something it wasn't really designed for, I'd just use a language that has first-class support for types directly in the compiler, like Java or Go.
- totalperspectiv 7y agoBut at that point why not get something for they time you spend adding types and just use a different language?
- pytester 7y ago>In one case, I was iterating over the contents of what was supposed to be a list, but in some rare circumstances could instead be a string. Instead of throwing a type error, Python happily went along with it, and iterated over each character in the string individually. I've been using python for about 13 years professionally and I wrote up a list of "things I wish python would fix but I think probably never will" and treating strings as iterable lists of characters was on there. I've seen this bug multiple times and the fix is relatively easy - just to make strings by default non-iterable and use "string.chars" (or something) if you really want to iterate through the chars. Nonetheless, I still love the language and wouldn't use anything else. >Instead of just throwing an error, Python happily went along with it, used an uninitialized value, and then all the logic broke. This one gets caught by linters - unfortunately 90% of most python linters spit out rule violations which aren't important which drowns out stuff like this in the noise.
- CydeWeys 7y agoIterating over characters in a string is something that's done very often in introductory CS classes, but very little in the real world. Python has support for string finding and regexes; why in the world would I be individually iterating over characters? Generally, when you see that, it's a code smell. So yeah, I totally agree with you, it'd be better if trying to iterate over a string were a flat-out error, and if you really want it, you should mean it. Though Python being dynamic still means that you'll only spot this error at runtime. As for linters, how do they know if your intent was to reassign the value of an existing variable, or to define a new one? The language has no way to indicate which of these is intended.
- bakery2k 7y ago> I wrote up a list of "things I wish python would fix but I think probably never will" What else is on your list? I'd be interested to see what other parts of Python you would wish to change.
- pytester 7y agoAmong other things: * Implicitly casting strings, integers, dates, etc. to boolean (e.g. "if x" being true if x is a non empty string). Cause of more unexpected bugs that I can count, but would cause massive headaches if implemented and memories of the the 2-to-3 transition would scare anybody away from doing this I think. * Treating booleans as integers (True + True = 2). Probably wouldn't cause that many headaches if implemented but everybody still seems to think it's a neat idea for some reason. * Treating non-package dependencies of pip packages (e.g. C compilers, header files) as something that is either the package's problem or the OS's problem. Nobody looks at this problem and thinks "I should solve this".
- cm2187 7y agoFor your first error, you can do some foot-shooting with a statically typed language too. I remember a bug I made using C#, where I wanted to delete a collection of directories recursively. I got mixed up into the various inner loops and ended up iterating over the characters like you. But C# allows implicit conversions of char to string so the compiler was OK with it, and since those where network drive directories (starting with "\\server\"), the first iteration started deleting recursively the directory "\", which in windows means the root directory of the active drive (c:\)... And SSDs are fast at deleting stuff.
- takeda 7y agoPython is a dynamic language, that's what dynamic languages do, you don't have a type checker but have greater flexibility, but you don't have to settle on that, you can actually use mypy and annotate types and get best out of both worlds. > And another one, I typoed a variable name as groups_keys instead of group_keys (or vice-versa, I don't remember). Instead of just throwing an error, Python happily went along with it, used an uninitialized value, and then all the logic broke. This isn't what Python would do, if the variable was undefined Python will throw an error, so you must have defined it with this name or you're misremembering what have happened.
- ben509 7y agoIt has nothing to do with static vs. dynamic. There's no reason that being an early-binding language that a string has to be iterable itself, and the proposal to change this was only rejected as it broke too many things[1] and couldn't be automatically fixed. Point in the GP's favor: Fixing it would definitely not be a problem with an early-binding language! In fact, the nigh-impossibility of automated refactoring puts lie to the notion that late-binding languages are more "agile." It's a design flaw, in the same way Python 2's allowing comparisons between different types was a flaw, e.g. "a" < 3 succeeds. Python 3 now, correctly, throws a TypeError because there's no sensible ordering between the two things. (While I'm griping: Another design flaw is conflating iterables and iterators, which makes generators almost useless. Say a generator is pass to a function expecting an iterable. If the function uses it twice, the second time it silently returns nothing!) > This isn't what Python would do, if the variable was undefined Python will throw an error I think GP must have assigned to the name, in which case Python will create a lexically bound name. Python's rules for naming can make perfect sense or be quite surprising: try: x = four() except Thing: x = 5 print(x) # 4 or 5 for a in [1, 2, 3]: pass print(a) # 3 ?! [1]: https://mail.python.org/pipermail/python-3000/2006-April/000759.html https://mail.python.org/pipermail/python-3000/2006-April/000...
- nabdab 7y agoWhat’s supposed to be the surprising thing? Are you confusing pass and break and expecting it to print 1?
- yegle 7y agoThe first problem is a valid problem in Python: it essentially don't have a "char" type, instead it only have strings with size 1. This actually ruined type annotation in Python: you can't properly annotate a function to accept anything iterable except string. https://github.com/python/mypy/issues/4334 https://github.com/python/mypy/issues/4334 is a FR to at least check it with mypy.
- rcfox 7y agoIt's not a perfect solution, but you could perhaps hack it with @overload to say that the "str" version returns NoReturn.
- srean 7y agoWell this is anything but a new complaint. I would assume a user who has worked in Python for some modest amount of time to have made peace with this. One works in Python knowing that this can and will happen (well one does have linter on steroid like mypy now to counter these). Python code needs more testing, more run time type checking of function arguments than a statically typed language. If that's a deal-breaker then one shouldn't be using Python in the first place. What you gain though is some instant gratification, and the ability to get something off the ground quickly without spending time placating the type checker. Its great where your workflow involves lot of prototyping, exploration of the solution space and interactive use (ML comes to mind, but even there int32 vs int64 can byte, correction, bite). I see it as a trade off -- deferring one kind of work (ensuring type safety) over another. Hopefully that deferral is not forever. I like my type safety but sometimes I want that later. What I typically do is once I am happy with a module and I do not need the extreme form of dynamism that Python offers (something that's frequently true) I take away that dynamism by compiling those parts with Cython.
- theptip 7y agoThe bugs you describe should both be easy to catch with unit tests. It sounds like the problem is not that you're using Python, it's that your project lacks tests. Sure, you can typo this sort of thing; but it should be apparent within seconds when your tests go red. (And nowadays, you can also use type hints to give you a warning for this kind of thing, e.g. your IDE/mypy will complain about passing a string where the function signature specified a List.)
- totalperspectiv 7y agoSerious question: If you are writting unit tests to check types, why not just use a language that has a compiler that does that for you? And if you are writing python with type hints, why not just use a language that uses the types you spend time adding to make your program faster. Python is great for sharing ideas / concepts, but under some circumstances it seems irresponsible to choose it over other viable options like Go (if you use Python because it's easy), or C# (If you use Python because it's a 'safe' enterprise choice). (Ecosystem specific things aside at least)
- Drew_ 7y agoJust because you have a Python function that has strict requirements for input doesn't mean every function you're writing has strict requirements. Moreover, using a strongly typed language doesn't magically make you invulnerable to invalid input. Unit tests are useful in every language.
- HelloNurse 7y agoBecause the unit tests are not to "check types", they are to check that incorrect values (e.g. a string instead of a list of strings) do not occur. They are no different from other kinds of incorrect values, like attempting to cluster an odd number of items into pairs.
- theptip 7y agoAs the sibling comment said, I'm not proposing checking types in unit tests, I'm proposing checking that the behaviour is correct. If there's a code path that passes in a bare string instead of a list, and your logic breaks, then that code path should have a failing test case. However, type hints can provide another opportunity to catch this kind of mismatch before they even get committed. > under some circumstances it seems irresponsible to choose it over other viable options like Go (if you use Python because it's easy) This is probably true, but I think people tend to overuse this argument (i.e. use an overly broad set of "some circumstances"). I build fintech apps with Python, for example, and don't find any of these issues to be a problem. In my experience, if you implement sound engineering practices (thorough testing at unit, integration, and system levels, code review, clear domain models, good encapsulation of concerns, etc.), then the sort of errors that bite you are not ones that a type checker would help with. I agree that the worst Python code is probably far more unsound than the worst Go code, but I don't think that's the correct comparison; you should be comparing the Python and Go code that _you_ (or your team) would write. I think it's easy to be dogmatic about this kind of thing; in practice most people are substituting personal preference for technical suitability. Sure, there are cases where the performance or correctness characteristics of a particular language make it more suitable than another. But for most software, then whatever your team is expert in is the best choice.
- Iv 7y agoHere is the counter-argument to everybody who thinks there is too much python in the world: It could be javascript.
- tedzhu 7y agoTBO these are way too trivial stuff compared to the argument of the post you're replying to.
- d0mine 7y ago> Python happily went along with it, used an uninitialized value There is no such thing in Python. You should get NameError if a name doesn't refer to any object. >>> def f(): ... name ... >>> f() Traceback (most recent call last): File "<string>", line 1, in <module> File "<string>", line 2, in f NameError: name 'name' is not defined
- deleted 7y ago[deleted]
- recroad 7y agoAre you blaming Python for shitty design? It's a dynamic language. All dynamic languages have those issues.