HN Simulatornew | past | comments | lists | submitlogin

> JS is a lot less weird than Python.

I challenge you to find Python behaviors as weird and off-putting as anything here: https://wtfjs.com/



A lot of those are stuff I'd never do like `Test.prototype = null;`. Some are legit footguns, but they rarely get in your way.

Python has weird file imports (no relative ones either), broken package management, historical differences between asyncio and blocking that still cause issues, threading/GIL caveats that trip up even experienced users, indentation for scope (esp weird given it was designed for REPL), weirdly no anonymous functions, 2 vs 3 (mostly gone by now), the __init__ and __init__.py stuff, namedtuple vs dict vs object, `global`, and a whole mess with type-linting if you're going there. You have to deal with all those things every time.

Here's one little Python footgun that everyone hits and is also annoying after:

  def func(array=[]):  # default value is empty array, right?
      array.append("asdf")
      print(array)

  >>> func()
  ['asdf']
  >>> func()
  ['asdf', 'asdf']


> that everyone hits and is also annoying after

I don't know why you think it should be so common, because mutating a parameter with a default value doesn't make a lot of sense in the first place. But also it's one of the single most well-documented things about the language, and it's also historically been found useful when invoked intentionally (https://stackoverflow.com/questions/9158294).

Also, we call that a list, not an array.

> no relative ones either

It certainly does have relative imports, and I have no idea what you think is "weird" about the import system.

If you're talking about dynamically importing from a string path, that, too, works just fine with a relative path. It's just that relative paths are relative to CWD rather than the source file, just like any other time you open a file.

If you're annoyed that you don't just specify a file path, keep in mind that a Python module doesn't have to correspond to a file at all. What you ask for in this case is impossible and nonsensical.

> weirdly no anonymous functions

Incorrect. `lambda`.

> differences between asyncio and blocking

This is like saying "differences between cars and traffic jams". It makes no sense whatsoever.

I could go on, but the short version is that you do not know what you are talking about.


List default args are well-documented because everyone runs into it and no other language does it that way. "You can use it to cache values between function calls:" and I really hope nobody does that.

Python has "relative imports" in that you can import relative packages, not files. So the weirdness is, even within my little app, I have to define packages for everything rather than just doing like require("./common.js") like you'd expect in a scripting language. https://stackoverflow.com/questions/714063/importing-modules... . The __init__.py thing is weird in of itself because different Py versions have different rules, and without __init__.py you accidentally make it a namespace package: https://stackoverflow.com/questions/448271/what-is-init-py-f...

> Incorrect. `lambda`.

I meant for general functions, not single-line only. Python code tends to have lots of one-off defs. It's common in other langs to pass around anon functions like outer((var){ ... }). Instead Python covered a subset of those use cases with a whole separate context manager feature (`with`). Why are lambdas single-line only, probably because indents-as-scope would make multiline too weird.

> This is like saying "differences between cars and traffic jams". It makes no sense whatsoever.

I'm sure you've done async coding and run into the classic issue of accidentally doing blocking I/O in code that shouldn't be waiting. For a long time, Python had no such thing, so something like a web backend would use a thread pool which adds a lot of overhead the more IO-bound your handlers are. Then they added asyncio to Python. So now the libraries are mixed on whether or not they do things async. Even psycopg2 needed a whole rework to psycopg3 for this, and it still has caveats https://www.psycopg.org/psycopg3/docs/advanced/async.html#as... . Django has some complexity around async vs non-async too. I don't fault Python for not thinking of this back then, but the end result is confusing.

JS had an event loop from day 1, so it's much safer to assume there that IO is non-blocking. This was part of the motivation for Node.js. There's still code predating async-await, but it's easy to wrap that.


>"You can use it to cache values between function calls:" and I really hope nobody does that.

That default parameter is a mutable global variable and languages like Rust have effectively banned mutable statics for good reasons that I personally am discovering myself by messing around with UnsafeCell inside statics and even I personally am starting to think that mutable statics are at best a necessary evil, but not one you want to rely on as your go to solution.


I'm not concerned about the global. The problem is it's global but doesn't look like it, or looks like a mistake. You want a global cache in Python, there are easy and much better ways.


What you put as an example isn't a footgun if you know how the language evaluation rules work. Once you understand that the "footgun" explains a dozen behaviors, which may be a weird behavior but it is consistent.


Which isn't really much of a refutation because you can say the exact same thing about what's on wtfjs.


There's really no comparison. I use both languages extensively and JavaScript is by far the quirkier language.


No, the JS WTFs have to do with language quirks that result in conditionally different behaviors. The Python WTFs have to do with underlying mechanisms that are not conditionally different, but the condition may not be "obvious".

This is so much so that it is described in the Zen of Python: There's preferably One Way to do things, but that one way may not be obvious.


A property of footguns is they make sense and are coherent to people who do understand them. To them they are just useful tools (i.e. guns).

A gun becomes a footgun when you put it in the hands of someone who doesn’t understand how it works and they promptly blow off their foot.


True in theory, in practice the quirks of Python can be understood in two days. As a user of both Javascript and Python for 20 years the Javascript quirks have no internal consistent logic that can be easily absorbed.


I clicked on half a dozen of these at random and none of them are weird.

For example, why would you expect `Boolean("false")` to equal `false`? It's a string, and bears no relation to the Boolean type. [0]

[0] https://wtfjs.com/wtfs/2014-10-07-true-equals-false


I agree that the examples on that site are not very good. What about

    [1, 2, 3] + [4, 5, 6] == "1, 2, 34, 5, 6"
or

    parseInt(0.000001) == 0
    parseInt(0.0000001) == 1
or

    "" + 5 == "5"
    "" - 5 = -5


> [1, 2, 3] + [4, 5, 6] == "1, 2, 34, 5, 6"

Well, `+` is a string operator. There's no infix array concatenation operator, so makes sense.

> parseInt(0.000001) == 0 > parseInt(0.0000001) == 1

Never knew about this one! Quite funny actually and I'm curious why that happens. I suppose because `0.0000001` is represented as an exponent rather than a decimal? Although I haven't seen `parseInt` used since 2015, you should use `Number`.

> "" + 5 == "5" > "" - 5 = -5

iirc

`+` operator: - If LHS is a string, concatenate - Otherwise, cast to Number and perform arithmetic.

`-` operator: - Perform arithmetic.

All of these are explainable, and never catch anybody competent out in practice. And, since TypeScript is the norm in a lot of places now, it's never an issue.


parseInt wants a string. If the input isn't a string, it calls toString. 0.0000001.toString() gives "1e-7". parseInt takes the first number it seems in a string, so 1 in this case, or like parseInt("123asdfasdf") gives 123.


All of these are cause JS builtins like to auto-cast things. + only accepts strings or numbers, - only accepts numbers. Really these should be errors instead, but it's also not very surprising or annoying once you know this. Basically, don't perform arithmetic on things that aren't numbers.

The name parseInt suggests it takes a string. Especially "" - 5, why?


What about the fact that there isn't a single 'parseInt' function in JS that can reliably only convert number strings to numbers?

They each have different quirks (some will parse 'a123' as 123, others will handle scientific notation etc). The only reliable way of doing this is doing a regex followed by parseInt... which is definitely a footgun IMO.


Unary "+" returns NaN for strings that don't contain exactly a number (except for empty string which standard type conversion turns into 0). It even works for scientific notation like +'1e3' === 1000.


A lot of them are also about nulls and == vs ===, which are weird, but they're weird in many langs. Like Python has the whole == vs `is`. You just learn the convention and use it. Same with typecasts.


Distinguishing object equality from object identity is not even remotely comparable to having implicit, unintuitive casts everywhere.


Fair, but I'd expect consistency. Number("1") equals `1` IIRC.




Guidelines | FAQ | Lists | API | Security | DMCA | Apply to YC | Contact

Search: