Python Experience from a Julia Perspective

I wrote python scripts long time ago. The last year I’ve mainly used Julia for my scripting needs. I have a bunch of scripts I need to share with other people and I don’t want to require people to have Julia installed, so I am trying to rewrite my scripts to Python.

Here are some of my observations about using Python again after primarily using Julia for my scripting needs.

First Impressions

I get this from writing help(“print”) in Python:

print_stmt ::= "print" ([expression ("," expression)* [","]]
| ">>" expression [("," expression)+ [","]])

"print" evaluates each expression in turn and writes the resulting
object to standard output (see below). If an object is not a string,
it is first converted to a string using the rules for string

While for Julia when I write ?print I get:

earch: print println print_joined print_escaped print_shortest print_unescaped print_with_color sprint @printf isprint @sprintf


Write (to the default output stream) a canonical (un-decorated) text representation of a value if there is one, otherwise call
show. The representation used by print includes minimal formatting and tries to avoid Julia-specific details.

Since the built in REPL is terrible, I installed bpython. Nice syntax coloring and completion. It doesn’t feel completely stable though. I’ve had instances were everything seems to stop working properly and many times in the middle of executing commands, bypthon abruptly quits. Ironically I never experience this with the Julia REPL which isn’t even in version 1.0 yet.

Right arrow completion based on something written in your REPL history works, just like in fish shell and Julia. That is nice, I love that functionality.

I don’t like the way bpython defaults to showing results of operations and functions. Here is a REPL session to illustrate the problem:

>>> a = 2 + 3
>>> 2 + 3

See how when you assign to a variable you don’t get to see the result. Compare this with Julia:

julia> 2 + 3

julia> a = 2 + 3

I think that is the behavior you want. bython behavior strikes me as an odd default. Why would you not want to see results in a REPL? Isn’t that why you are using it?

Interacting with arrays

if not a:
print "List is empty"

That fits poorly Zen of Python:

Explicit is better then implicit

Everything useful has to be imported

One has to assume this splitting function is hidden in some library you need to import.

And when you do import something you have to be careful about how you name things. E.g. I got the error message:

NameError: global name 'os' is not defined

I look at my code which said:

previousDir = os.getcwd()

Looked okay, except it turns out that since I had written

from os import getcwd

rather than:

import os

Python had no idea what os was. I had to write

previousDir = getcwd()

This runs counter to what I would assume. Most languages I am used to, you can always qualify a name fully, whether it is Java, C++, Swift or Julia.

String literals complicated

"Hello how are you today {}".format(name)

Poorly designed standard functions

One should think there was a natural extension of this for creating a path with the home directory. In Julia you can write e.g.:

joinpath(homedir(), "bar", "foo")

But there is no such natural extension in python. Instead you have to write:


Another common thing to do for a script would be to e.g. remove a directory.

This is how you remove directory foo in Julia and all its contents. Pretty straightforward and matches how the rm shell command works.

rm("foo", recursive=true)

With Python there is no rm to find and rmdir found in os package/module has no options to delete anything but empty directories. Instead we have to look at an entirely different module named shutil with the function rmtree. This strikes me as inconsistent. You have to look for a closely related function in an entirely different library with a non-intuitive name.

Integration with the shell

E.g. I was testing out code that made a temporary file.

filename, io = mktemp()

And when working with it I wanted to quickly check the contents of the file. Shell commands like cat are in my fingertips for such things. In Julia I can write:

;cat $filename

To look at the contents since the variables in Julia are exported to the shell as environment variable. I often use that as a way of editing files:

;mate $filename

Opens the file for editing in TextMate. Or checking git status:

;git status

I could not find any similar way of interacting with the shell.

Python whitespace sensitivity isn’t worth it

Running shell programs is awkward

run(`codesign --verify $app`)

That will execute the command as if I ran it from the shell, dumping STDOUT and STDERR to the console in normal fashion. But there are many ways of executing a command depending on your needs:

run(`ls -l`)                # show result in terminal
lines = readlines(`ls -l`) # store result as individual lines
text = readall(`ls -l`) # store result as block of text

Python on the other hand does not have the convenient back-ticks for working with shell commands. I can run call command but that will not print result to console.

call(["ls", "-l"])

Having to separate out each argument explicitly is also somewhat cumbersome. I ended up writing a simple utility to convert my Julia back-ticks command invocations to the comma separated Python argument list. To get more control over outputs you can use Popen in Python but it is far away from the elegant solution found in Julia. I mimicked the Julia run function with:

def run(cmd):
proc = Popen(cmd, stdout=PIPE, stderr=PIPE)

It was not obvious how this worked and I had to spend some time investigating it. In contrasts I already knew readlines and readall worked for reading files in Julia, so I simply tried to see if I could use them for reading the output of processes. This shows the power of a well defined orthogonal features. Julia functionality combined very well in predictable ways so that you don’t need lots of edge cases to handle different variants and data types.

That was one of the original beauties of Unix, that you could treat reading and writing to a network interface, sound input/output, files etc just as reading and writing to a file. In similar fashion Julia allows you do treat file descriptors and processes in a similar fashion. Even including variables is similar. Whether dealing with string literals or back-ticks for expressing a command line, you can refer to a variable foo as $foo and it will be escaped properly.

I can write `codesign — verify $app` to get a runnable shell expression or “codesign — verify $app” to show that expression to the user. In contrast Python distinguishes the two with:

["codesign", "--verify", app]
"codesign --verify {}".format(app)

Which has no shared consistency.


But as a replacement for many of the things you might do in a bash script I think Julia offers a superior experience. Here are some examples of what I do in Julia which does not require extensive libraries:

  • Converting SVG images to PNG tools of different resolutions, utilizing a command line tool.
  • Modifying and resigning iOS apps.
  • Changing source code in various ways. E.g. obfuscating method names or string literals.
  • Modifying a git repository. E.g. as part of constructing a git repo from a subversion repository.

These are the sort of things you might have used bash for but which gets clunky because Bash control structures, string and array manipulations are awkward to use.

Geek dad, living in Oslo, Norway with passion for UX, Julia programming, science, teaching, reading and writing.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store