Python Guide for JS-devs

I have been using JavaScript for most of my career, but I recently started working at a company that uses Python for most of its stack. Even though Python is easy to get started with, a couple of quirks are easy to get stuck on, and some best practices that I wish I knew earlier. Here is a list of my most significant hurdles and what I learned from the perspective of a JavaScript developer. Hopefully, it's useful for anyone else doing the same transition.

Six of One, Half Dozen of the Other

First, let's take a quick look at similar concepts with different names in the two languages.

Errors and Exceptions

Errors in JavaScript are called Exceptions in Python. They are otherwise identical in any meaningful way.

null and None

null in JavaScript is None in Python. There is no equivalent of undefined in Python.

Abbreviated keywords

Some keywords in Python are abbreviations of the actual words.

  • String is called str in Python.
  • Boolean is called bool in Python.
  • else if is called elif in Python. 🤯

I got stuck for longer than I wanted to admit the first time I tried to write else if. It's a strange quirk, but you will adapt fast if you know to look out for it.

Objects and Dictionaries

Objects in JavaScript are called Dictionaries in Python. There are two distinctions. Firstly dot-notation doesn't work in Python.

person = {"name": "Sherlock", "address": "Baker Street"}

print(person["name"]) # This works
print(person.name)    # This doesn't work

The other distinction is that Python lacks an equivalent of undefined in JavaScript. This means that if you try to access an undefined property, you will get an exception.

person = {"name": "John", "title": "Doctor"}

print(person["address"]) # Throws KeyError: 'address'

To avoid crashing whenever you want to access an optional property, you can use .get(). .get() is a method that returns the value of a key if it exists in a dictionary. If it can't be found .get() returns None. You can also give .get() an optional default parameter that will be returned instead of None if the key is undefined.

person = {"name": "Mycroft", "occupation": "Government official"}

print(person.get('name')) # prints Mycroft
print(person.get('age')) # prints None
print(person.get('age', 35)) # prints 35

Tuples and Lists

In Python, there are two equivalents to JavaScript's arrays. In most cases, you use lists, and they fill the same purpose.

clues = ["chair", "safe", "saucer of milk"]

for clue in clues:
  print(f"{clue} is a helpful clue")

There is another native data type that you can use to create collections of elements. If you want to return two values from a function or keep pairs of values, you can use tuples. It is often used instead of dictionaries if you want something a bit less verbose. Tuples are read-only lists with some extra syntax sugar.

def get_nemesis():
  return ("James", "Moriarty", 1835) # Return a tuple

# User is a tuple here
nemesis = get_nemesis()
print(nemesis[0], nemesis[1]) # Prints James Moriarty
nemesis[0] = "John" # This throws an Exception

# You can destructure a tuple
(first_name, last_name, born) = get_nemesis()

# The parenthesis are optional when destructuring
first_name, last_name, born = get_nemesis()

List (and Dict) comprehension

List comprehension is one of the most special features if you come from JavaScript. In JavaScript you can quickly compose arrays by using array functions like .map(), .sort(), and .filter(). Python has a couple of those array functions, but they are a bit ugly to use. Here is an example of doubling only even numbers from a list.

const numbers = [1, 2, 3, 4]

const result = numbers
  .filter(num => num % 2 == 0)
  .map(num => num + num)

The example above in Javascript is equivalent to this example below in Python:

numbers = [1, 2, 3, 4]

result = map(
    lambda num: num + num,
    filter(lambda num: num % 2 == 0, numbers),
)

You can't chain list functions as they aren't a part of the list class like they are a part of the array prototype in JavaScript. In Python, you can use list comprehensions instead.

numbers = [1, 2, 3, 4]

result = [
  num + num           # new value (map)
  for num in numbers  # list to iterate
  if num % 2 == 0     # filter
]

There are even dictionary comprehensions to create dictionaries from lists (or other iterables) quickly 🤩:

numbers = [1, 2, 3, 4]

result = {
  num: num % 2 == 0
  for num in numbers
}

# Results in {1: False, 2: True, 3: False, 4: True}

Concurrency

This topic is too large to cover in this post, but it's good to know that there are traps that you should look out for. In JavaScript, it's (almost) not possible to do any blocking task. This allows you to know that every library you use is guaranteed to handle concurrency benevolently. And it's difficult to end up in deadlocks.

Python supports synchronous statements, which can mean that you can block your thread for long times. This makes some code easier to write but concurrency a bit more complicated.

Python has two different methods to handle concurrency. You can use traditional OS threads. Additionally, Python recently added a non-blocking one threaded asynchronous native library called asyncio. It is very similar to Node's event loop on the surface, but there are some differences.

Firstly the library is a lot more complicated and low-level than Node. Non-blocking I/O operations are a native part of the JavaScript language (actually not the language but the execution environment), and the syntax feels very native. In Python, you get access to lots of low-level components of the event loop. You have to start the event loop yourself, and depending on if you are inside or outside the loop, you should use different functions to control the event loop. It can be challenging to remember the various quirks.

Secondly, Python's support for synchronous statements can be a significant drawback when using asyncio. If you accidentally call a blocking statement, you block the whole thread. You have to be careful and explicitly run all blocking code in "executors".

Even though there are quirks, I still prefer asyncio to thread management. You can learn more about asyncio by watching this video or by reading this book.

The last tip to remember is never to mix asyncio and threads. The documentation to asyncio is imperfect, and the documentation for using threads together with asyncio is nonexistent. I have wasted too much time trying to get it to work to attempt to mix them again.

Lambdas

I love anonymous arrow functions in JavaScript. I use them all the time, especially if I want a small function with 3-5 statements. Python has a similar concept called lambda functions, but they have one fatal flaw. Lambdas can only contain one statement. So it's not possible to have a multiline lambda. You will have to declare a proper function for those cases. 👎

Package Management and Reproducible Environments

npm is one of the best features of Node. It's undeniable that the quality of the available packages in Python is not as good. Additionally, the documentation is often much better and nicer looking for JavaScript packages. I highly suspect the reason for that is that there is considerable overlap between JavaScript developers and web developers. 😅

But the more significant hurdle was not the availability of pip packages. It was the actual package manager that I missed the most. When you use npm, you install packages locally for a specific project. This means that you can have different versions of the same library in different Node projects on your computer simultaneously. With pip, you can only install packages globally.

This sounds more stupid than it is. Python has solved the isolation using another method. It is best practice to set up a virtual environment for every Python project. You explicitly tell your shell to activate a virtual environment, and when it is activated, the set of global packages is entirely separate from the default environment.

Even though this works fine, I still mess up and forget to activate my virtual environments often and accidentally install libraries globally all the time. I miss how easy it is to use npm. Two other features I miss are npm scripts and good package version management.

To replace pip, I have started using pipenv. It manages my virtual environment and package versions almost as well as npm. It also supports scripts. The best part is that it doesn't matter if I have activated the virtual environment when I run the scripts. Pipenv automatically runs them in my virtual environment regardless.

The standard library

Python has a fantastic standard library! The rich native library compensates for the lack of community-produced pip packages. I enjoy finding native libraries that solve my issues cause then I know that I don't have to compare multiple open source libraries like I have to do with npm packages.

The Node libraries are very lean and only offer the necessary native operations that must be a part of the standard library, like providing I/O operations. The standard Python libraries often overlap in functionality, which is unheard of in the JavaScript world.

The best example of how extensive the libraries are is if you google "Get current timestamp using Python". You will see this article in the top results. The article suggests three different methods, using three different standard libraries (time, datetime, calendar).

Can you think of any other quirks that took time for you to figure out? Please leave a comment!

19