Python gotchas for JavaScript developers

Python and JavaScript are close languages, yet they have a couple of surprising differences. In this article I list and explain a few of them

Python gotchas for JavaScript developers

If you're a JavaScript developer who's interested in topics like machine learning, data science, or even if you want to learn some new tool to broaden your experience, there's a high chance that you are trying out (or going to try out) Python. While both languages are quite similar, there are a bunch of surprising differences that can confuse you. In this article I list a couple of such gotchas together with examples and explanations which will help you understand why both languages behave differently.

And if you are looking for a new language to learn as a JavaScript developer, check out my video where I talk about how to choose your next language.

Anonymous functions are very limited

First on the list: anonymous functions aka closures aka lambdas. They're a very common tool in JavaScript. To be fair they'r nothing special, just ad-hoc created functions that do not have any specific name. Any time you write array.map((el) => doSomething(el)) you write an anonymous function. Heck, to be fair probably 99% of JS functions you write these days are technically  anonymous. Look at this:

const myFun = () => alert("I have no name!");
const otherFun = function() { alert("Me neither!"); }

These 2 functions are anonymous. You can refer to them via variable to which they're assigned, but if that variable ever changes the value, you can't find the function again, it's lost. In order to have a "real" named function, you need to do this:

function myFun() { alert("I have a name"); }

class MyClass {
    otherFun() { alert("Me too!"); }
}

There are certain other differences between named and anonymous functions in JS, but in general they're very similar and you can easily use either of them most of the time. However, in Python the situation is different. While the language supports both named and anonymous functions, the latter are very limited: they can consist of only one expression (essentially they can do one operation). To show you an example:

fun1 = lambda : print("works!")
fun2 = lambda a, b : a + b

wrongFun = lambda: # this will throw invalid syntax error!
    a = 1
    b = 2
    return a + b

This means that the following piece of code is impossible to write using lambdas in Python:

makeRequest().then((result) => {
    logOutput(result.data);
    saveContent(result.data["content"]);
    return result;
}); 

Why is there such limitation? As Python's creator himself explains:

I find any solution unacceptable that embeds an indentation-based block in the middle of an expression.  Since I find alternative syntax for statement grouping (e.g. braces or begin/end keywords) equally unacceptable, this pretty much makes a multi-line lambda an unsolvable puzzle.

Basically in order to allow multi-line anonymous functions, a single expression would need to respect the tab-based indendation rules (currently it doesn't) or use another block separators (like {} in JS). Guido van Rossum, creator of the language, rejected both these ideas.

What's the solution then? Well, simply give the function a name! To be fair it's not that much of a deal, rather an inconvenience, but also a gotcha, something that I didn't expect when I first learned Python.

Expressions vs statements

Related to the previous point are differences between statements and expressions in both languages. If you're not sure what are these two, a brief explanation is that expression is something that produces a value, while statement is just a piece of code that performs something, but it does not return any value. In other words, you can ask yourself: can I assign it to a variable? If yes, it's an expression, otherwise it's a statement. For example:

const a = 3; // 3 is an expression
const b = a + 12; // arithmetic operations are expressions
const c = (z = 10); // (z = 10) is also an expression
const d = (if (a > 2) { 7 } else { 2 }); // this won't work! if is a statement

function myFun() { alert("alert"); }
const e = myFun(); // calling a function is an expression

Ok, so what's the issue? Well, the issue is that an expression in JavaScript might not be an expression in Python! For example, creating a function:

const something = () => 8;
const sthElse = function namedFun() { return 7; } 

This code is perfectly valid in JavaScript (even if it's not common to assign named functions to variables). What about Python?

something = lambda : 8; # this code is valid
sthElse = def namedFun(): return 7; # this code will crash!

In this case the 2nd example does not work, because defining a named function in Python is a statement, not an expression. Similarly with assignments let a = 10 in JS returns 10, while in Python a = 10 returns nothing, it does not produce any value.

I am not sure why Python function definition is a statement. A possible explanation is that on one hand indented lines inside an expression do not create a new scope (which is logical, why would they) and on the other hand function definition must create a new scope, therefore a function definition can't be an expression. That's just my theory though, maybe it was decided a priori that definition is a statement and that's it.

I can't think of any more diffecentec between expressions and statements in Python vs JavaScript, but if you're interested in how it looks in other languages you can check Ruby, where essentially everything is an expression (including if, for etc).

Tricky default parameters

Default values for function arguments are a feature so obvious that it is rarely ever mentioned besides basic tutorials. It's easy, if a value is not passed to your function explicitly, instead of throwing an error, you just give it a certain, hardcoded value.

const processJob = (name, args, delay = 0) {
    Job.fetchByName(name).startIn(delay).execute(args)
}

processJob("createUser", {name: "Ian"}, 60) // run in 60sec
processJob("createUses", {name: "Ion"}) // run now

In Python, default values have a catch though - instead of being evaluated every time a function is called, default values are evaluated only once. Why does it matter? Because if you decide to modify the argument inside your function, it will not be brought back to its previous state! Let me show it using an example. First, JavaScript:

const addOne = (ary = []) => {
    ary.append(1);
    return ary;
}

addOne([3,2]); // returns [3,2,1]
addOne([3,2]); // returns [3,2,1] again

addOne(); // returns [1]
addOne(); // returns [1] again, this is crucial here    

Now lets compare it with Python:

def addOne(ary=[]):
    ary.append(1)
    return ary
    
addOne([3,2]) # returns [3,2,1]
addOne([3,2]) # returns [3,2,1]

again addOne() # returns [1]
addOne() # BOOM! returns [1,1]
addOne() # and now returns [1,1,1]

See the difference? The default argument is always the same array. Unlike most of the languages I know, Python does not recreate this object every time. The common solution to this problem is unfortunately a rather ugly idiom:

 def addOne(ary=None):
     if ary is None:
         ary = []

     ary.append(1)
     return ary      

Note that this difference applies only to complex data types like arrays, dictionaries etc. If your argument is a string or a number, the default value will not change from one call to another, so you can safely use it. However if you want to have an array or dictionary by default, you need to pass None as a default value.

Nothing is private!

Alright, there are way more examples, but let's not turn this article into a compedium. Let me just mention one last gotcha in this post - privacy... or rather lack of it. As you probably know, JavaScript does not have an explicit concept of private methods or variables. I mean whatever you put in a class can technically be accessed outside of it. There is a proposal to turn add private methods and fields with kind of an unusual syntax, but for now it's not there yet.

Still, there are ways to make some stuff kind of private in JS. For example you can write a set of functions and variables and export only a few of them:

const x = 12;
const y = 10;

const pubFun = () => console.log('public');
const priFun = () => console.log('private');

export { x, pubFun };

And of course you can use Typescript, which has a full Java-ish (C++-ish?) set of function/field access control.

On the other hand, in Python essentially everything is accessible - there is no built-in protection mechanism. There is no export keyword - everything can be imported from any module, everything can be accessed in any class. Python promotes an "unwritten agreement" approach, where underscores used as prefixes indicate that the function or variable should not be used outside of its original class or module:

var1 = 0; # go ahead, use it whenever you need
_var2 = 0; # should not be used outside of its class/module
__var3 = 0; # DEFINITELY should not be touched

To be precise, Python does a little bit to discourage use of functions with double underscore (read more about it here), but you still can use "private" functions whenever you want, wherever you want.

More gotchas?

Programming languages are very complex tools and therefore have a lot of surprises. Sometimes they're obvious and we learn them very early and sometimes it takes months or even years to notice a behavior that surprises us. I'm sure Python has more getchas for JS developers. If you have some other examples, tell me!

And if you're curious about why Python became such a popular language, check out my other post where I compare it to other dynamic, scripting languages.