# Fast and basic Python introduction#

Prepared by G. Aznar Siguan

Most of the examples come from the official Python tutorial: https://docs.python.org/3/tutorial/

## Contents#

• Numbers and Strings

• Lists

• Tuples

• Sets

• Dictonaries

• Functions

• Objects

## Numbers and Strings#

The operators +, -, * and / work just like in most other languages:

print('Addition: 2 + 2 =', 2 + 2)
print('Substraction: 50 - 5*6 =', 50 - 5*6)
print('Use of parenthesis: (50 - 5*6) / 4 =', (50 - 5*6) / 4)
print('Classic division returns a float: 17 / 3 =', 17 / 3)
print('Floor division discards the fractional part: 17 // 3 =', 17 // 3)
print('The % operator returns the remainder of the division: 17 % 3 =', 17 % 3)
print('Result * divisor + remainder: 5 * 3 + 2 =', 5 * 3 + 2)
print('5 squared: 5 ** 2 =', 5 ** 2)
print('2 to the power of 7: 2 ** 7 =', 2 ** 7)


The integer numbers (e.g. 2, 4, 20) have type int, the ones with a fractional part (e.g. 5.0, 1.6) have type float. Operators with mixed type operands convert the integer operand to floating point:

tax = 12.5 / 100
price = 100.50
price * tax


Strings can be enclosed in single quotes (’…’) or double quotes (”…”) with the same result. \ can be used to escape quotes.

If you don’t want characters prefaced by \ to be interpreted as special characters, you can use raw strings by adding an r before the first quote.

print('spam eggs')  # single quotes
print('doesn\'t')   # use \' to escape the single quote...
print("doesn't")    # ...or use double quotes instead
print('"Yes," he said.')
print("\"Yes,\" he said.")
print('"Isn\'t," she said.')


Strings can be indexed (subscripted), with the first character having index 0.

Indices may also be negative numbers, to start counting from the right. Note that since -0 is the same as 0, negative indices start from -1.

word = 'Python'
print('word = ', word)
print('Character in position 0: word =', word)
print('Character in position 5: word =', word)
print('Last character: word[-1] =', word[-1])
print('Second-last character: word[-2] =', word[-2])
print('word[-6] =', word[-6])


In addition to indexing, slicing is also supported. While indexing is used to obtain individual characters, slicing allows you to obtain substring:

print('Characters from position 0 (included) to 2 (excluded): word[0:2] =', word[0:2])
print('Characters from position 2 (included) to 5 (excluded): word[2:5] =', word[2:5])


## Lists#

Lists can be written as a list of comma-separated values (items) between square brackets. Lists might contain items of different types, but usually the items all have the same type.

Like strings (and all other built-in sequence type), lists can be indexed and sliced:

squares = [1, 4, 9, 16, 25]
print('squares: ', squares)
print('Indexing returns the item: squares:', squares)
print('squares[-1]:', squares[-1])
print('Slicing returns a new list: squares[-3:]:', squares[-3:])
print('squares[:]:', squares[:])


Lists also support operations like concatenation:

squares + [36, 49, 64, 81, 100]


Unlike strings, which are immutable, lists are a mutable type, i.e. it is possible to change their content:

cubes = [1, 8, 27, 65, 125]  # something's wrong here
cubes = 64  # replace the wrong value
cubes.append(216)  # add the cube of 6
cubes.append(7 ** 3)  # and the cube of 7
cubes

# Note: execution of this cell will fail

# Try to modify a character of a string
word = 'Python'
word = 'p'


List comprehensions provide a concise way to create lists. Common applications are to make new lists where each element is the result of some operations applied to each member of another sequence or iterable, or to create a subsequence of those elements that satisfy a certain condition.

squares = []
for x in range(10):
squares.append(x**2)
squares

# lambda functions: functions that are not bound to a name, e.g lambda x: x**2
# Map applies a function to all the items in an input_list: map(function_to_apply, list_of_inputs)
squares = list(map(lambda x: x**2, range(10)))
squares

squares = [x**2 for x in range(10)]
squares


## Tuples#

A tuple consists of a number of values separated by commas, for instance:

t = 12345, 54321, 'hello!'
t

t

# Tuples may be nested:
u = t, (1, 2, 3, 4, 5)
u

# Note: execution of this cell will fail

# Tuples are immutable:
t = 88888

# but they can contain mutable objects:
v = ([1, 2, 3], [3, 2, 1])
v


Tuples are immutable, and usually contain a heterogeneous sequence of elements that are accessed via unpacking or indexing. Lists are mutable, and their elements are usually homogeneous and are accessed by iterating over the list.

t = 12345, 54321, 'hello!' # tuple packing
x, y, z = t # tuple unpacking
x, y, z


## Sets#

A set is an unordered collection with no duplicate elements. Basic uses include membership testing and eliminating duplicate entries. Set objects also support mathematical operations like union, intersection, difference, and symmetric difference.

Curly braces or the set() function can be used to create sets.

basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
basket # show that duplicates have been removed

'orange' in basket # fast membership testing

'crabgrass' in basket

# Demonstrate set operations on unique letters from two words
b = set('alacazam')
a # unique letters in a

a - b # letters in a but not in b

a | b # letters in a or b or both

a & b # letters in both a and b

a ^ b # letters in a or b but not both

# check set documentation
help(set)

# check which methods can be applied on set
dir(set)

# Define a new set and try some set methods (freestyle)


## Dictionaries#

Unlike sequences, which are indexed by a range of numbers, dictionaries are indexed by keys, which can be any immutable type; strings and numbers can always be keys.

It is best to think of a dictionary as an unordered set of key: value pairs, with the requirement that the keys are unique (within one dictionary). A pair of braces creates an empty dictionary: {}. Placing a comma-separated list of key:value pairs within the braces adds initial key:value pairs to the dictionary; this is also the way dictionaries are written on output.

tel = {'jack': 4098, 'sape': 4139}
tel['guido'] = 4127
tel

tel['jack']

del tel['sape']

tel['irv'] = 4127
tel

list(tel.keys())

sorted(tel.keys())

'guido' in tel

'jack' not in tel


## Functions#

We can create a function that writes the Fibonacci series to an arbitrary boundary:

def fib(n):    # write Fibonacci series up to n
"""Print a Fibonacci series up to n."""
a, b = 0, 1            # two assignments in one line
while a < n:
print(a, end=' ')
a, b = b, a+b      # two assignments in one line
print()

# Now call the function we just defined:
fib(2000)


The value of the function name has a type that is recognized by the interpreter as a user-defined function. This value can be assigned to another name which can then also be used as a function. This serves as a general renaming mechanism:

print(fib)
print(type(fib)) # function type
f = fib
f(100)


Be careful when using mutable types as inputs in functions, as they might be modified:

def dummy(x):
x += x

xx = 5
print('xx before function call: ', xx)
dummy(xx)
print('xx after function call: ', xx)

yy = 
print('yy before function call: ', yy)
dummy(yy)
print('yy after function call: ', yy)


### Default argument values:#

The most useful form is to specify a default value for one or more arguments. This creates a function that can be called with fewer arguments than it is defined to allow. For example:

def ask_ok(prompt, retries=4, reminder='Please try again!'):
while True:
ok = input(prompt)
if ok in ('y', 'ye', 'yes'):
return True
if ok in ('n', 'no', 'nop', 'nope'):
return False
retries = retries - 1
if retries < 0:
raise ValueError('invalid user response')
print(reminder)

#This function can be called in several ways:

#giving only the mandatory argument:
ask_ok('Do you really want to quit?')

# giving one of the optional arguments:
ask_ok('OK to overwrite the file?', 2)

# or even giving all arguments:
ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')


Functions can also be called using keyword arguments of the form kwarg=value:

ask_ok('OK to overwrite the file?', reminder='Come on, only yes or no!')


Default None values: None default values can be used to handle optional parameters.

def test(x=None):
if x is None:
print('no x here')
else:
print(x)
test()


## Objects#

Example class definition:

class Dog:                    # same as "class Dog(object)"

kind = 'canine'           # class variable shared by all instances

def __init__(self, name): # initialization method
self.name = name      # instance variable unique to each instance
self.tricks = []      # creates a new empty list for each dog

def add_trick(self, trick):   # class method
self.tricks.append(trick)


When a class defines an _init_() method, class instantiation automatically invokes _init_() for the newly-created class instance:

d = Dog('Fido') # creates a new instance of the class and assigns this object to the local variable d
d.name

e = Dog('Buddy') # creates a new instance of the class and assigns this object to the local variable e

d.tricks # unique to d

e.tricks # unique to e

d.kind # shared by all dogs

e.kind # shared by all dogs


### Inheritance:#

The syntax for a derived class definition looks like this:

class DerivedClassName(BaseClassName)

A derived class can override any methods of its base class or classes, and a method can call the method of a base class with the same name. Example:

class Animal:         # base class

def __init__(self, kind):
self.kind = kind
self.tricks = []

def add_trick(self, trick):   # class method
self.tricks.append(trick)

class Dog(Animal):   # derived class

def __init__(self): # override of __init__ base method
super(Dog, self).__init__('canine') # call Animal __init__ method with input string

fido = Dog() # fido is automatically an animal of kind 'canine'
print(fido.kind)
print(fido.tricks)


Python supports a form of multiple inheritance as well. A class definition with multiple base classes looks like this:

class DerivedClassName(Base1, Base2, Base3):

### Private Variables and Methods:#

“Private” instance variables that cannot be accessed except from inside an object don’t exist in Python. However, there is a convention that is followed by most Python code: a name prefixed with an underscore (e.g. _spam) should be treated as a non-public part of the API (whether it is a function, a method or a data member).

Example of internal class use of private method __update. The user is not meant to use __update, but update. However, _update can be used internally to be called from the _init method:

class Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)

def update(self, iterable):
for item in iterable:
self.items_list.append(item)

__update = update   # private copy of original update() method

class MappingSubclass(Mapping):

def update(self, keys, values):
# provides new signature for update()
# but does not break __init__()
for item in zip(keys, values):
self.items_list.append(item)