We encourage you to actively follow along these with the code examples by typing them into a Python read-eval-print-loop (REPL) or an editor. We suggest using the following online editor which requires no installation: ReplIT
python
in the terminal to start the REPL.python -i <script name>
to start REPL with a script.ipython
(same arguments) instead of the standard REPL. It has autocomplete and syntax highlighting.Python has a built-in function help
for printing documentation during REPL sessions or in Jupyter Lab. For example:
help(print)
Help on built-in function print in module builtins: print(...) print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False) Prints the values to a stream, or to sys.stdout by default. Optional keyword arguments: file: a file-like object (stream); defaults to the current sys.stdout. sep: string inserted between values, default a space. end: string appended after the last value, default a newline. flush: whether to forcibly flush the stream.
Python does not use static types like most traditional languages, i.e. the type of a variable is never declared. Instead, variables can contain objects of any type and are declared by assigning a value to it.
For example, the following will not compile in Java:
int a;
a = 10;
a = "hello";
but its Python equivalent is completely legal:
a = 10
a = 'hello'
print(a)
hello
Objects still have actual types though! In a sense, you could use dynamic typing in Java by making all variables and fields of the type Object
!
Examples:
print(type(10))
print(type('hello'))
<class 'int'> <class 'str'>
Thus you can still easily get type errors. For example:
a-'hello'
The consequence: type errors are only discovered at runtime.
As software developers this might seem very disconcerting, but don't worry! If you like static typing or want to develop a large Python project, you should look into type hints .
We don't care about what an object is, only about what it can do. For example:
class A:
def f(self):
print('A')
class B:
def f(self):
print('B')
def func(obj):
obj.f()
Because there are no static types, we can call func
with any arguments we like, e.g.
func(A())
A
func(B())
B
func(0)
Discuss in groups of 2/3 people for 5 minutes
''
or ""
._
and many common unicode characters.class MyClass: # Class definition
def my_method(self, param): # Method definition
return param
def outer_function(): # Function definition
print('outer')
# Individual statements
obj = MyClass()
print(obj.my_method(2))
2
int
for whole numbers (no size limit)float
for real numbers (usually 64 bit)str
for stringsbool
for booleans (True
/ False
)None
is equivalent to null
in Java (but not equal, it is a special type)+
, -
, *
, %
work as expected for numbers./
performs float division, //
performs integer division.**
performs exponentiation.+
is also used for concatenation of strings.42 % 10
10 + 2*3 + 10**5
10 / 3
10 // 3
'hello' + ' world'
True
and False
in Python. In general, all values evaluate to True
in a boolean expression except a 0
, the empty string ''
, empty collections, and None
.and
, or
, not
).>
, <
, >=
, <=
, ==
, !=
are the same as in Java.&
, |
work the same as in Java.bool({})
'' or True
0b101 & 0b011 # 0bxx is a binary literal
Functions are defined in Python using the following syntax:
def ():
<parameters>
is a comma-separated list of parameter names. It is possible to specify default parameter values like so:
def (a, b=2):
Note: All parameters without default values must precede parameters with default values.You can define anonymous functions in Python using the lambda
syntax, for example:
func = lambda x: x**5
func(10)
Anonymous functions shouldn't be assigned to variables like in the example. Instead use them only in higher order functions.
Use the built-in function print
for printing strings or any object
print('hello')
print(10)
print([1, 2, 3])
print('abc', 'def')
hello 10 [1, 2, 3] abc def
Python supports easy string formatting by prepending a string with f
and sorrounding variables in {<var>}
, e.g.
print(f'formatted {10}')
formatted 10
It is possible to add format options. Link to tutorial.
You can convert objects to strings using the built-in str()
function (also used by print
). If you want to know how to define custom formatters for you own objects, see here
.
#
. No block comments.def my_function(x, y):
"""This is the docstring. This function does blah blah blah."""
# This is a regular comment
...
help
on a function:help(my_function)
Help on function my_function in module __main__: my_function(x, y) This is the docstring. This function does blah blah blah.
Python reserves the following keywords. That means they can not be used as identifiers!
Python code is organised into modules which are essentially a bunch of files in a directory. A single python file is itself a module. Modules are imported using the import
keyword. For example:
import datetime
datetime.datetime.now()
Specific classes, functions or submodules can imported using the following syntax:
from datetime import datetime
datetime.now()
Imports can also be renamed. This is often desirable to keep a clean namespace:
import datetime as dt
dt.datetime.now()
Python's control flow statements are very similar to ones from Java and C. The major difference, aside from not using curly braces is that parentheses are not needed for the expression to be evaluated.
x = 2
if x == 3:
print("X equals 3.")
elif x == 2:
print("X equals 2.")
else:
print("X equals something else.")
print("This is outside the 'if'.")
X equals 2. This is outside the 'if'.
Note that else if
is shortened to elif
. They are both perfectly legal in Python although you should always use elif
!
These are identical to the Java/C version except the syntactic differences:
while x > 0:
x -= 1
print(x)
0
For loops are a bit different in Python compared to Java. There is only one version with the syntax:
for - in
:
...
Regular counting loops are recreated using the range
function (docs
) which generates a sequence of integers in a specified range:
for i in range(5, 10, 2):
print(i)
5 7 9
Looping over collection elements is very similar to Java's foreach
loop
for e in ['a', 'b', 'c']:
print(f'letter: {e}')
letter: a letter: b letter: c
for c in 'abc':
print(f'letter: {c}')
letter: a letter: b letter: c
Sometimes you want to use both the current item and its index in a given iteration. Instead of using range, use enumerate
:
for i, e in enumerate(['a', 'b', 'c']):
print(i, e)
0 a 1 b 2 c
We will return to the i, e
notation later.
Python uses the keywords break
and continue
similarly to Java/C for stopping and skipping loop iterations.
Python has four main collection classes:
Lists are mutable and are defined using square brackets:
[1, 'a', 3]
Tuples are immutable and are defined using parentheses:
('hello', 1.2)
Strings are immutable and are defined using quotas (''
or ""
):
'hello' + " world"
or as multiline strings using triple quotes:
"""I am
a multiline
string."""
Accessing a single element is similar to Java/C:
a = [1, 2]
a[0]
b = ('a', 'bc')
b[1]
c = 'hello'
c[3]
A sublist can be created using a special slicing syntax a[<start>:<stop>]
:
a = [1, 2, 3, 4, 5]
a[2:4]
Negative indices are used to access elements from the back of a list/tuple:
a[-2]
Lists are actually arrays with automatic resizing like ArrayList
in Java and allow appending in amortised constant time and access in constant time.
Changing values use the same syntax as Java/C:
a = [1, 2, 3, 4]
a[1] = 5
a
It is also possible to assign a list to a slice:
a[1:3] = [4, 8]
a
Appending is done using the method append
:
a.append(2) # Note that this is changes the list!
a
Sets contain unique items in no particular order. They are equivalent to HashSet
in Java.
s = {2, 3, 4}
s
Dictionaries contain key-value pairs indexed by the key. They are equivalent to HashMap
in Java.
d = {'key1': 0, 'key2': 3}
d
len
function is used to get the length of a collection.in
keyword is used to check existence of an element, e.g. 2 in [2, 3, 4]
..items()
and .values()
for iterating over key-value pairs and values respectively.Classes use the following syntax for declaration:
class ():
def __init__(self, ):
self. = # Instance variable / field
def (self, ):
__init__
function is always the constructor in Python.self
. It is equivalent to this
in Java but is mandatory both as a first argument to methods and when accessing/creating instance variables.<name>()
. This is equivalent to calling new
in Java.class A:
def __init__(self):
print('A')
super().__init__()
class B:
def __init__(self):
print('B')
super().__init__()
class C(A, B):
def __init__(self):
print('C')
super().__init__()
C()
C A B
try:
a = '2' + 3
except TypeError as e:
print('catched: ', e)
finally:
print('always runs')
catched: can only concatenate str (not "int") to str always runs
You can except multiple errors in the following way:
try:
...
except (A, B, C):
...
try:
...
except A:
...
except B:
...
You can raise exceptions with:
raise ValueError("Should have been something else.")
You can define your own exceptions with:
class MyError(Exception):
...
raise MyError('something')
Discuss in groups of 2/3 people for 5 minutes
Python has an official style guide named PEP-8 which you should try to follow. Most editors and IDEs have built in support for it or supports linters (mypy, pylint, etc.) that have.
MyClass
.convert_images
.MAX_VALUE
.The complete guide is avaiable here .
import this
The Zen of Python, by Tim Peters Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. Special cases aren't special enough to break the rules. Although practicality beats purity. Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess. There should be one-- and preferably only one --obvious way to do it. Although that way may not be obvious at first unless you're Dutch. Now is better than never. Although never is often better than *right* now. If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea -- let's do more of those!
Summary: Use the same guidelines you have learned for software development in general.
Python has a special syntax, called comprehensions, for performing common operations on collections. These are similar to comprehensions in other languages such as Haskell. They are essentially a filter and map operation combined.
List comprehensions are the most common and have the following syntax:
a = [2, 3, 4, 5]
[str(e) for e in a]
It is possible to add guards to a comprehension like so:
[e for e in a if e%2==0]
A similar syntax can be used on sets and dictionaries:
a = {2, 3, 4}
{str(e) for e in a}
{str(e): e**2 for e in a}
A lazy form of list comprehension can be created by exchanging the []
sorrounding the expression with ()
:
g = (e for e in a if e%2==0)
g
list(g)
Python supports a smart syntax for easy packing and unpacking of multiple values.
a, b = (2, 3)
print(f'a: {a}, b: {b}')
a: 2, b: 3
This can be used for easy swapping of values
b, a = a, b
print(f'a: {a}, b: {b}')
a: 3, b: 2
All iterables (collections and strings) can be unpacked in this manner!
a, b = 'ab'
print(f'a: {a}, b: {b}')
a: a, b: b
s = {'a': 1, 'b': 2}
a, b = s
print(f'a: {a}, b: {b}')
a: a, b: b
a, b = s.items() # get key-value pairs!
print(f'a: {a}, b: {b}')
a: ('a', 1), b: ('b', 2)
# skip
def mygen():
for i in range(5):
yield i
a, b, c, d, e = mygen()
You can force unpacking in functions by prepending an iterable variable with *
. E.g.
def myfunc(a, b):
print(a, b)
data = ('abc', 'def')
myfunc(*data)
abc def
Python supports two different methods for accepting a variable number of function/method arguments. First is unnamed arguments:
def vararg(*args):
print(args)
vararg(2, 3, 4)
(2, 3, 4)
Similarly, a function can accept a dictionary of named arguments:
def kwarg(**kwargs):
print(kwargs)
kwarg(a=1, b=2)
{'a': 1, 'b': 2}
Note that the names args
and kwargs
are simply conventions, although they are very strong conventions.
Python supports most common operations usually associated with functional languages. Closures are supported as lambda
functions. The functions map
, zip
, and filter
are built-in but more functions are accessible through the functools
module.
Here's an example using map
:
a = range(10)
b = map(str, a)
b
The functional tools in Python are lazy and thus we need to force the calculation to get the result:
list(b)
zip
allows you to combine two iterables into a new iterable with tuples containing elements from both inputs. E.g.
idx = range(5)
char = 'ABCDE'
zipped = zip(idx, char)
list(zipped)
Functions in Python are, like everything else, objects. This means they are first-class citizens and can therefore be used to build higher-order functions by simply using a parameter as a function object.
Example:
def apply_print(func, v):
print(func(v))
apply_print(len, 'abc')
3
The yield
keyword is used to turn an ordinary function into a generator. The keyword returns the specified value and essentially pauses the function at that point. This is similar to the same keyword in C#. This is very useful to define custom iterators:
def mygen(start):
while True:
yield start
start += 1
for i in mygen(10):
print(i)
if i>20:
break
10 11 12 13 14 15 16 17 18 19 20 21
Python objects support a number of special methods called dunder or magic methods. They allow access to modification of language features such as operator overloading.
__init__()
method which is used for object initialisation. __add__
, __mul__
, etc.__call__
method allows you to call instances of an object.__getitem__
and __setitem__
allows you to use the collection index/assignment syntax.A simple example:
class Squares:
def __getitem__(self, item):
return item**2
s = Squares()
s[500]
Please don't ever do stuff like this! As with other languages supporting operator overloading, use it extremely sparingly.
Decorators are supported in Python using an @<decorator-name>
to decorate a function, method, or class. Typical built-in decorators are:
class MyClass:
@staticmethod
def method(a):
print(a)
@property
def x(self):
return 'a'
@x.setter
def x(self, value):
print('setting x')
obj = MyClass()
obj.method(2)
obj.x = 'a'
obj.x
2 setting x
You can define your own decorators! They are simply functions or callable objects.
def my_decorator(func):
def wrapper(*args, **kwargs):
print('starting')
func(*args, **kwargs)
print('done')
return wrapper
@my_decorator
def func(a):
print('my function', a)
# equivalent to
def func(a):
print('my function', a)
func = my_decorator(func)
func(2)
starting my function 2 done
IO operations require extra management due to the uncertainty of the operations. Specifically, it is often necessary to encapsulate code in try/catch
blocks and manually close resources. Context managers makes this much easier by providing a keyword with
used to create a block with automatic setup and finalising. For example:
with open(somefile) as file:
file.read()
# Do stuff
You can create your own context managers as well! Link to docs
Python supports type hints in the language. They are not handled by the interpreter though. For example:
def typed_func(a: str, b: int) -> str:
return f'{a}{b}'
typed_func(2,3)
Why?? It's complicated and depends on who you ask.
Still:
mypy
), some of which are built into IDEs like PyCharm.help
and in most editor hints as well - this makes understanding an API so much easier.My advice? Use type hints when creating anything to be reused.
In Python, literally everything is an object. That means that even classes (yes the descriptions) are objects. When the interpreter reads a new class definition, a new class object is instantiated.
class MyClass(int):
pass
type(MyClass)
MyClass.mro()
This process of creating class objects can be modified by using metaclasses. They essentially allow modifications to the expected behavior of classes, making it possible to add functionality to the language.
A basic example:
class Meta(type):
def __new__(cls, name, bases, attr):
print(f'Creating new class "{name}"')
print(f'inheriting from {bases}')
return type.__new__(cls, name, bases, attr)
class MyClass(str, metaclass=Meta):
pass
a = MyClass()
b = MyClass()
Creating new class "MyClass" inheriting from (<class 'str'>,)
Python has a library asyncio
for writing concurrent code using native syntax. Like C#, Python has the keywords async
and await
for specifying and controlling concurrent code. Link to docs
An example using both:
def create_printer(msg):
def inner():
print(msg)
return inner
printer = create_printer(5)
printer()
5
Just to prove it to you:
printer.__closure__[0].cell_contents