A Julia Primer for Python Programmers
Learn Julia With Me!
Nowadays I started learning Julia, the lightning fast data science language, which looks like a mix of many known languages (Python, R, Ruby, …) and is under the hood a Lisp (homoiconic — meaning the code consists of data structures which are valid data structures of the language; this allows metaprogramming since the code can be treated as data — a feature of Lisp languages).
Installation of Julia
Installing Julia into a conda environment
I like conda and I installed conda in my Ubuntu Linux and my Windows computer (which I use rarely).
Once conda is installed it is a breeze to install Julia.
I highly recommend you to use juliaup which helps you to manage different Julia versions on your machine.
conda create --name juliaup
conda activate juliaup
conda install -c conda-forge juliaup
# then, once Juliaup is installed, I recommend you to install the newest Julia
# and the most recent LTS version
juliaup add release
juliaup add lts
# you can install any version of julia e.g. 1.9.1
juliaup add 1.9.1
# you can check which versions are available for juliaup to download for you
juliaup list
# after installation, check which versions are installed
juliaup status # that with the star is default
# run a juliaup-installed julia by: julia +<version-number-or-release-name>
julia +release
julia +lts
julia +1.9.1Don’t use conda install -c conda-forge julia!! Because it seems fine, but later, when you install packages like Genie, it has problems with MbedTLS installation and subsequently HTTP. So avoid it!
Anything installed with juliaup however, worked fine!
Installing Julia in Ubuntu Linux
You can install the Julia in the OS’s repository:
$ sudo snap install julia --classicHowever, I favor to use a conda environment and install there juliaup as described above.
Installing Julia in MacOS
I guess it is (I don’t have a MacOS, only Ubuntu and Windows):
$ brew install julia
# or probably better:
$ brew install homebrew/cask-versions/julia-lts
# this might however install an older Julia version!However, also here I would recommend conda -> juliaup . Install conda e.g. by brew install --cask miniconda .
Installing Julia in Windows
In windows, I would first install scoop,
> # install scoop in PowerShell
> Set-ExecutionPolicy RemoteSigned -scope CurrentUser
> iwr -useb get.scoop.sh | iexand then do
> # install julia
> scoop install juliaThis all should install Julia v1.9.3 in your computer.
But also here I would rather recommend:
scoop install miniconda
conda initAnd then to follow the instructions for conda.
Julia REPL
After installation, fire on your console the command julia . — You will then be in the REPL of Julia — similar to how you know it from Python, R, Ruby, etc.

Installing Some Julia Packages
Let us install some useful packages for handling packages (we won’t use them in this session, but probably in a later session):
# import Pkg
using Pkg # or: import Pkg
# install Package Templates (for creating Julia Packages later)
Pkg.add("PkgTemplates")
# install also a useful package for Julia Package development
Pkg.add("Revise")
# install the Web framework
Pkg.add("Genie")
# and from then on, after successful installation, you can import them:
using PkgTemplates
using Revise
using Genie
# other useful packages
Pkg.add("Plots") # for plotsYou can import packages either by using packagename or import packagename. In case of using, all variables, functions and objects which were declared to be export-ed are directly available for the rest of the session, while import requires you to use dot notation after the packagename, which can be annoying.
# let's say MyPackage is exporting a function and a variable:
# my_func, my_var
using MyPackage
my_func(my_var) # immediately avilable after `using`import MyPackage
MyPackage.my_func(MyPackage.my_var) # more specific, but one has to write
# the packagename explicitely# you could however explicitely import, to not to have
# to type always `MyPckage.`
import MyPackage: my_func, my_var
# after that, one can directly call the function or variable
my_func(my_var)Let’s Define Functions
function mysum(x, y)
x + y
end
mysum(3, 5) ## 8So the function definition is very similar to Python, just that we use function instead of def, we don’t need a : at the end of the first line, but an end which marks in Julia the end of a block (which Python usually marks by using indentation changes).
Like all functional languages, the last expression’s value gets returned implicitly.
In contrast to Python, positional arguments and keyword arguments are strictly distinguished from each other through a ; and when calling the function, you are allowed to use x= only for keyword arguments.
Optional Arguments
work like in Python.
function mysum1(x, y=3)
x + y
end
mysum1(3) ## 6
mysum1(x=1) ## error, since you can use `=` only for keyword arguments
mysum1(3, 4) ## 7
# define y as keyword argument
function mysum2(x; y, z=3) # only arguments after the `;` are keyword arguments
x + y
end
mysum2(1, 3) ## error, because keyword args have to be called using `=`
mysum2(1, y=3) ## 7Explicit return() or return like in Python is possible, but should be only used when it is not so clear otherwise that a value would be returned (e.g. if the value is not a final expression of a block).
function example_func(x, y)
if x == 2 && y == 3
println("Returning explicitely")
return 5
end
# some other code
# and then the return value:
x + y
end
example_func(2, 3)
## Returning explicitely
## => 5
example_func(2, 4)
## => 6Python’s *args and **kwargs
Using the splat operator — 3 dots …following the variable — in contrast to Python’s variable-preceding * or **. It depends on whether the splat-ted variable is before or after the ; in the lambda-list (the arguments area) of a function, whether it takes the function of * or **.
# *args
function myvariadic_sum(args...)
reduce(+, args) # reduce is like Python functools' reduce
end
myvariadic_sum(1, 2, 3) ## 6
myvariadic_sum(1, 2, 3, 4, 5, 6) ## 21
myargs = [1, 2, 3]
my_variadic_sum(myargs...) ## 6
# **kwargs
function myvariadic_func(args...; kwargs...)
println("args: $(join([x for x in args], ","))")
println("kwargs: $kwargs")
end
myvariadic_func(1, 2, 3; Dict(:a => 1, :b => 2)...)
## args: 1,2,3
## kwargs: Base.Pairs(:a => 1, :b => 2)Python’s dir() Command For Introspection
The Julia equivalent would be names() when introspecting a module and fieldnames() when introspecting object’s (Structure’s — equivalent of Python’s classes) fields.
As a functional language implementing multiple dispatch, however, the methods of a class are not defined inside the class, but outside the class.
So while in Python, with a single dir(PythonObject) command, one could see all fields (attributes, properties) and methods of the class at once,
in Julia, you have to run fieldnames(JuliaObject) to see the fields of the object and methodswith(JuliaObject)to see all methods listed which use the class (struct) in any form in the method.
This is important to know for me, because I use introspection a lot when programming with Python.
Control Structures
While in Python, changing the indentation marks the beginning or end of a new block, in Julia, the keyword end is marking the end of a block and not some whitespaces.
Conditionals
Are like Python, except that and is && and or is ||
If-elseif-else
Instead of elif in Python, use elseif and don’t forget to end the entire block with an end (like in Ruby).
a = 1
b = 3
if a > b
println("$a > $b")
elseif a == b
println("$a == $b")
else
println("$a < $b")
end
## a < bIf-Oneliner
In python, we can have one-liner x if 1 == 1 else y . In Julia, one can use the one-liner conditional as we know from C:
1 == 1? true : false ## this returns trueFor Loop
For loops are probably the most useful looping in Python. Also here, don’t forget to use end at the end when you leave the indentation.
You can use for loops exactly like in Python.
for x in [1, 2, 3]
println(x)
end
## prints
## 1
## 2
## 3
for (i, x) in enumerate(['a', 'b', 'c'])
println(i, x)
end
## 1a
## 2b
## 3cWhile Loop
Is also just the difference that you don’t need a : at the end of the condition and you need end at the end of the block.
counter = 1
while counter < 10
println(counter)
counter += 1
end
## prints 1 to 9 each number in a new lineList Comprehension
Julia overtook List Comprehensions from Python.
julia> [x for x in [1, 2, 3, 4, 5]]
5-element Vector{Int32}:
1
2
3
4
5
julia> [x for x in [1, 2, 3, 4, 5] if x < 3]
2-element Vector{Int32}:
1
2
julia> [x < 3 ? x : 10 for x in [1, 2, 3, 4, 5]]
5-element Vector{Int32}:
1
2
10
10
10
# nested for loop
julia> [(x, y) for x in [1, 2, 3, 4, 5], y in "abcdef"]
5×6 Matrix{Tuple{Int32, Char}}:
(1, 'a') (1, 'b') (1, 'c') (1, 'd') (1, 'e') (1, 'f')
(2, 'a') (2, 'b') (2, 'c') (2, 'd') (2, 'e') (2, 'f')
(3, 'a') (3, 'b') (3, 'c') (3, 'd') (3, 'e') (3, 'f')
(4, 'a') (4, 'b') (4, 'c') (4, 'd') (4, 'e') (4, 'f')
(5, 'a') (5, 'b') (5, 'c') (5, 'd') (5, 'e') (5, 'f')
[(x, y) for x in [1, 2, 3, 4, 5] for y in "abcdef"]
30-element Vector{Tuple{Int32, Char}}:
(1, 'a')
(1, 'b')
(1, 'c')
(1, 'd')
(1, 'e')
(1, 'f')
(2, 'a')
(2, 'b')
(2, 'c')
⋮
(4, 'e')
(4, 'f')
(5, 'a')
(5, 'b')
(5, 'c')
(5, 'd')
(5, 'e')
(5, 'f')
# parallel looping with zip()
julia> [(x, y) for (x, y) in zip([1, 2, 3, 4, 5], "abcdef")]
5-element Vector{Tuple{Int32, Char}}:
(1, 'a')
(2, 'b')
(3, 'c')
(4, 'd')
(5, 'e')Variable Assignment
Multiassignment is possible like in Python:
a, b, c = 1, 2, 3
# swap
a, b = b, aWith Optional Typing
Like Python’s optional Types, but much more performant, since compiled, Julia gives you the possibility to add Type hints for the compiler which mostly leads to significant speed gains, since type-checks become unnecessar this way. The most basic type is T from which all types are originating. Julia has a hierarchically ordered Typing system. (Similar to Haskell). But in contrast to Haskell, the typing is optional. Types can be attached to variables using the :: operator. An can be composed (e.g. in arrays and nested arrays or Vectors). Also the type hints of Python can expressed composed types.
a::Int64, b::Int8, c::Float64 = 1, 2, 3
typeof(a) ## In64
typeof(b) ## Int8
typeof(c) ## Float64Define New (Composite) Types Using Structs and Apply Multiple Dispatch
Python classes can be created by Structures/Structs. Different than Python, all functions are methods. And not just methods, but multiple dispatch methods. Therefore, when defining functions, you can determine which type an argument should be of. And the definition you give will be only used, when the function/method call was done with the matching set of argument types.
struct Point
x::Float64
y::Float64
end
p1 = Point(1, 3) ## Point(1.0, 3.0)
p2 = Point(5, 9) ## Point(5.0, 9.0)
# define a method which deterines how to add them
function add(point1::Point, point2::Point)
Point(point1.x + point2.x, point1.y + point2.y)
end
add(p1, p2) ## Point(6.0, 12.0)
# The beauty of multiple dispatch is, you can import any existing methods
# and extend them for your type.
# e.g. + is from the Base package.
# if you try to extend directly:
julia> function +(point1::Point, point2::Point)
Point(point1.x + point2.x, point1.y + point2.y)
end
ERROR: error in method definition: function Base.+ must be explicitly imported to be extended
# you get this error.
# you do it correctly by:
import Base.+
julia> function +(point1::Point, point2::Point)
Point(point1.x + point2.x, point1.y + point2.y)
end
+ (generic function with 208 methods)
# and now you can use the + operator for the points
p1 + p2 ## Point(6.0, 12.0)Multiple Dispatch makes the Object system very powerful, because in contrast to traditional C/C++, Java, thus also Python-like single dispatch OOP, it allows you to really implement the Open-Closed Principle and true Polymorphism which is truly extensible.
Types and Structures We Know From Python
Most of the types we know from Python exist also in Julia. We don’t have to use Type hints and could use Julia just like Python (and would be still much faster than Python).
# Python's `int`
# Julia has signed and unsigned integers
# default is Int64
# Int8, Int16, Int32, Int64, Int128
# UInt8, UInt16, UInt32, UInt64, UInt128
# Python's `float`
# Julia has floats
# Float16, Float32, Float64
# Python's `str`
# Julia uses only double quotes for strings
# String
"This is a string"
"""This is
a
multiline
string"""
# in contrast to Python, we have char's which a single quoted
# Char
'a', 'b', 'c' ## ('a', 'b', 'c')
# and of course Bool
true, false
# and we have
Nothing ## probably equivalent of NoneOne can define — using Union — some composite Types:
IntOrString = Union{Int,AbstractString}We have Lists, Tuples, Dictionaries:
# Python's Lists correspond to one-dimensional Arrays with elements
# of the Type Any - so Array{Any, 1} - which are also called Vectors
# in Julia and also use the [ ] notation like in Python.
julia_vector = [1, 2, 'a', "abc", 1.23] # Vector{Any} or Array{Any, 1}
# Tuples are like Tuples in Python and use () - they are immutable
julia_tuple = (1, 2, 'a', "abc", 1.23) # Tuple{Any}
# Dictinoaries are a little bit more bulky in their notation.
empty_dct = Dict() # Dict{Any, Any}
untyped_dct = Dict("a" => 1, "b" => 2, "c" => 3) # Python's dictionaries
typed_dct = Dict{String, Integer}("a" => 1, "b" => 2, "c" => 3)
# Type-hinted dictionary
# we have get() method like in Python
get(untyped_dct, "a") ## 1
get(untyped_dct, "d", Nothing) ## Nothing
# keys and values methods like in Python
keys(untyped_dct) ## ("a", "b", "c")
values(untyped_dct) ## (1, 2, 3)
# loop over a dictionary
[(k, v) for (k, v) in untyped_dct]
# check existence of key
"a" in keys(untyped_dct)
## true
# access value
untyped_dct["b"] ## 2
# merge dicts
merge(Dict("a" => 1), Dict("a" => 2, "b" => 3))
Dict{String, Int32} with 2 entries:
"a" => 2
"b" => 3So, this was a glance into Julia. Obviously the Typing system requires a closer look from our side.
Further articles will follow. Please follow me and email subscribe to get noticed when I publish the next article about Julia.
Read about how to handle the packaging manager of Julia in https://readmedium.com/exploring-julias-package-manager-892ad1e7e7d1 .






