Unless you're just writing a simple HTTP API server, any amount of web programming in Python will likely require at least a little bit of Javascript. Like it or not (and I will try to argue in this post that you should like it for what it's good at), Javascript is really the only game in town when it comes to client-side scripting on a web page. Sure, there are a number of Python-to-Javascript transpilers out there, but using these just tends to limit the ability to use new Javascript features as they are rolled out to browsers and may limit the ability to use third-party Javascript libraries. At the very least, using one these transpilers introduces added complexity to deploying a web app1.
In this post, I will describe some things I've learned about Javascript from the perspective of someone who prefers to use Python as much as possible. This guide is mainly aimed at scientists and others who are not primarily programmers but who may find it useful to make a web app for their main work. It is assumed that the reader is at least moderately familiar with Javascript (Mozilla has a nice tutorial to get you up to speed if not).
Modules in Python make it very easy to encapsulate components without
polluting the global namespace. In contrast, Javascript in the browser
will make everything a global if you are not careful2. The good
news is that it doesn't require too much extra effort to use a sort of
module pattern in your Javascript code thanks to things like object
literals and closures. Imagine you are writing some code to do
some simple math operations that aren't in the Javascript
Math
library. Instead of doing this:
// Don't do this
function mean(x) {
var i;
var total = 0;
for (i = 0; i < x.length; i++) {
total += x[i];
}
return total/x.length;
}
you should prefer putting your functions in an object:
// This is better
var mathlib = {
mean: function (x) {
var i;
var total = 0;
for (i = 0; i < x.length; i++) {
total += x[i];
}
return total/x.length;
}
};
An even better approach is to wrap your definitions in a closure to keep things better encapsulated:
// This is even better!
var mathlib = (function (lib) {
lib.mean = function (x) {
var i;
var total = 0;
for (i = 0; i < x.length; i++) {
total += x[i];
}
return total/x.length;
};
return lib;
})(mathlib || {});
This pattern allows for splitting components for a larger module into different files, which is often a good idea from the perspective of readability when things start getting more complex. Rather than go into further detail, I'll refer you to an excellent article on the module pattern in Javascript by Ben Cherry.
HTTP/1.1 requires a new connection for every requested Javascript
file. While this problem is rectified in HTTP/2, not many web servers
and hosting providers support it yet as of late 2016. This has led to
many different options for bundling
multiple Javascript files into a single file which can be included in
a web page with just one script
tag. While these tools can be
tempting to use, I strongly recommend avoiding them as much as
possible (at least until you become more comfortable with the state of
modern Javascript) for the following reasons:
A good, (potentially) pure Python approach to bundling your Javascript files (for cases where it makes sense to split code into more than a single file) is the webassets module. Webassets offers a number of filters to run Javascript and CSS files through to accomplish tasks such as as minification and bundling. Here's a sample Tornado app:
import tornado.web
import tornado.ioloop
from webassets import Environment
class MainHandler(tornado.web.RequestHandler):
def initialize(self, assets):
self.assets = assets
def get(self):
self.render("index.html", assets=assets)
# Set up the webassets environment and make a bundle
assets = Environment(directory=".", url="/static")
js_files = ["mathlib.js", "thing.js", "class-example.js"]
assets.register("bundle", *js_files,
output="bundled.min.js",
filters="rjsmin") # webassets ships with this filter included
app = tornado.web.Application(
[(r'/', MainHandler, dict(assets=assets))],
static_path=".",
template_path=".",
debug=True)
app.listen(8123)
tornado.ioloop.IOLoop.current().start()
To include the bundled file in the template, you would do something like this in the template:
{% for url in assets['bundle'].urls() %}
<script src="{{ url }}"></script>
{% end %}
The careful reader may wonder why the for loop is used if all the
Javascript files will be bundled into a single file in the end. This
is because webassets has a helpful debug mode which eliminates the
need for source mapping. By adding assets.debug = True
to the
Python file, assets['bundle'].urls()
will return a list of all the
original, uncompressed Javascript files. This results in individual
script tags for each Javascript source file which makes debugging in
the browser considerably easier at the expense of a (likely) small
increase in startup time.
There are a lot of nice features in webassets, though many of the filters require third-party tools (often using Node.js) to be installed. For this reason, I discourage using most of these until and unless you are comfortable with the rabbit hole of the Node world.
(Aside: Recently, I learned of the DukPy interpreter. While it's still early, it looks like a promising way of being able to include things that currently require Node-based tools while keeping everything purely Pythonic.)
Among Python programmers, Javascript has a tendency to be considered a very poor programming language in terms of features and syntax. While this was once a more valid criticism, Javascript has steadily improved, and especially with the introduction of the ES2015 standard, it's a very comfortable language to work in3. In this section, I will cover a few of the more useful features made available in ES2015 with the small caveat that using them requires using fairly up-to-date browsers (which is not normally a problem among scientists who in my experience are all using either Firefox or Chrome, anyway).
In Python, self
by convention refers to the instance of a
class. This means that even with nested function definitions, the
reference to self
is always unambiguous, so you could do something
like
class Thing:
def __init__(self, value):
self.value = value
def method(self):
def function():
return self.value
return function()
and expect calling the method
method on an instance of Thing
to
correctly return thing.value
. In Javascript, the approximate
equivalent of self
is this
which is by default bound as a
reference to the function being called. In other words,
var thing = {
value: 1,
method: function () {
var func = function () {
return this.value;
};
return func();
}
};
console.log(thing.method());
will print undefined
because func
has no value
attribute. This
can be fixed by explicitly binding this
to func
(func =
func.bind(this)
), but this quickly becomes cumbersome when the number
of functions that need this fix grows. In part to simplify this,
ES2015 introduced so-called arrow functions which are kind of like
Python lambdas on steroids. One nice feature of arrow functions is
that they lexically bind the this
variable so we can rewrite the
above to read
var thing = {
value: 1,
method: function () {
var func = () => this.value;
return func();
}
};
console.log(thing.method());
which correctly outputs 1
.
Prior to ES2015, classes in Javascript had to be implemented with a function:
function MyClass(value) {
this.value = value;
}
var instance = new MyClass(10);
Implementing inheritance was cumbersome and required the use of the
prototype
attribute:
function Programmer(language) {
this.language = language;
}
// Add a method to the Programmer prototype
Programmer.prototype.programThings = function () {
console.log("Favorite language: " + this.language);
console.log(this instanceof Programmer);
};
// Create child classes
function PythonProgrammer() {
Programmer.call(this);
this.language = "Python";
}
PythonProgrammer.prototype = Object.create(Programmer.prototype);
function JavascriptProgrammer() {
Programmer.call(this);
this.language = "Javascript";
}
JavascriptProgrammer.prototype = Object.create(Programmer.prototype);
var pythonProgrammer = new PythonProgrammer();
var jsProgrammer = new JavascriptProgrammer();
pythonProgrammer.programThings();
jsProgrammer.programThings();
/* Output:
Favorite language: Python
true
Favorite language: Javascript
true */
With ES2015, classes can be defined in a more Pythonic way:
class MyBaseClass {
constructor(value) {
this.value = value;
}
method() {
return this.value;
}
}
class MyNewClass extends MyBaseClass {
secondMethod() {
return this.value + 1;
}
}
var instance = new MyNewClass(10);
console.log(instance.method());
console.log(instance.secondMethod());
which outputs
10
11
Despite the Zen of Python suggesting that "there should be one--
and preferably only one --obvious way to do it," there are 3 ways to
format strings as of Python 3.6. The newest of these ways is the
so-called "f-string" introduced by PEP 498. This allows you to
have variables dynamically inserted into strings without having to
explicitly use %-formatting or the str.format
method:
x = 20
print(f'{x} is 20')
ES2015 has a similar concept in template strings:
var x = 20;
console.log(`${x} is 20.`);
Like Python docstrings, Javascript template strings can also span multiple lines:
`this is
ok
even if it is
a pointless string.`
One of the most useful features of Python is to be able to easily iterate over a list:
for x in y:
print(x)
Javascript has long had, for example, Array.prototype.forEach()
to
iterate over an array, but this requires the use of a callback
function to act on each value in the array. Javascript now has the
more Pythonic for ... in
and for ... of
statements:
var list = [1, 2, 3, 4, 5];
var obj = {
a: 1,
b: 2,
c: 3
};
// for ... in on list makes x take on the *indeces* of list
console.log("for (x in list)");
for (let x in list) {
console.log(x);
}
// for ... of on list makes x take on the *values* of list
console.log("for (x of list)");
for (let x of list) {
console.log(x);
}
// for ... in on obj makes x take on the *keys* of obj
console.log("for (x in obj)");
for (let x in obj) {
console.log(x);
}
Generators are used frequently in Python to efficiently iterate over
values without having to pre-compute and store in memory each loop
iteration's result. In Javascript, a function must explicitly be
declared a generator by denoting it a function*
and using the
familiar yield
keyword:
function* itsATrap() {
while (true) {
yield "What is it?";
yield "It's a trap!";
}
}
var isItATrap = itsATrap();
console.log(isItATrap.next().value);
console.log(isItATrap.next().value);
console.log(isItATrap.next().value);
console.log(isItATrap.next().value);
There are quite a few other nice things introduced by ES2015, but the above illustrates features that are perhaps the most welcomed by Python programmers. For a good overview of all newer Javascript features, see this, for example.
To summarize, what follows are a series of short snippets showing some common Pythonic concepts and their Javascript analogs.
try:
thing()
except Exception:
print("oh no!")
raise ValueError("not a good value")
try {
thing();
} catch (error) {
console.error("oh no!");
}
throw "not a good value";
arr = [1, 2, 3]
obj = {
"a": 1,
"b": 2,
"c": 3
}
for val in arr:
print(val)
for key in obj:
print(key)
var arr = [1, 2, 3];
var obj = {
a: 1,
b: 2,
c: 3
};
for (let val of arr) {
console.log(val);
}
// or...
arr.forEach((value, index) => {
console.log(value);
});
for (let key in obj) {
console.log(key);
}
def gen(x):
while True:
yield x
x = x + 1
function* gen(x) {
while (true) {
yield x;
x++;
}
}
class Thing:
def __init__(self, a):
self.a = a
def add_one(self):
return self.a + 1
class OtherThing(Thing):
def __init__(self, a, b):
super(OtherThing, self).__init__(a)
self.b = b
def add_things(self):
return self.a + self.b
class Thing {
constructor(a) {
this.a = a;
}
addOne() {
return this.a + 1;
}
}
class OtherThing extends Thing {
constructor(a, b) {
super(a);
this.b = b;
}
addThings() {
return this.a + this.b;
}
}
expression = lambda a, b: a + b
// Arrow functions are more powerful than Python lambdas, but not in
// this example!
let expression = (a, b) => a + b;
// or...
let sameThing = function (a, b) {
return a + b;
}
from functools import reduce
mapped = map(lambda a: a + 1, range(10))
print(reduce(lambda a, b: a + b, mapped))
let arr = [];
for (let i = 0; i < 10; i++) {
arr.push(i);
}
let mapped = arr.map((a) => a + 1);
console.log(arr.reduce((a, b) => a + b));
These days, Javascript the language is much improved and potentially more Pythonic than ever before. Approaching a little Javascript from the perspective of a Python programmer, you can write good, clear code while avoiding many of the (mostly outdated) common pitfalls often brought up by Javascript detractors.
Not that modern Javascript tooling is really so good at reducing complexity... More on this later. ↩
Of course, there are tools like webpack that can let you use modern Javascript modules in the browser, but this requires the step of bundling all the Javascript sources into a browser-friendly bundle. Unless you are willing to dive deep into the, um, interesting world of Javascript tooling, I recommend against this as you get started with more complex Javascript. ↩
Although it would be a lot nicer if there were actually a standard library to speak of. ↩