As ever, the notes are revision and cribsheet notes taken from the other sources. None of it is mine, but its a quick reminder.

Notes from : https://docs.python.org/3/contents.html

Python 3.6.4

and some comments from using it.

Duck typing

It’s finally time to return to duck typing. So here goes.

If it quacks like a duck it is one. ie typeless languages.

i=1       # Looks like an int to me
s='guess' # String

Python Versions

Python 3 is better, but Python 2 is backed into Linux and many systems, so bad luck you. They are not compatible.

Modules and packages

Modules are files, packages are directories containing a special file called

__init__.py

REPL and running

python -c command [arg]
python -m module [arg]
python -i -m module [arg] # interactive mode

Script name of - means stdin. Access to args is via sys.argv[0] etc

Garbage collection

There is a background thread doing reference counts to remove zero reference variables. You can also delete yourself using

del a

Format

Python uses white space and indentation rather than brackets. Read pip8 before you type anything, and pycharm IDE with inspect will help you.

The world of python

Things to read about, are venv, PyPI, wheels, anaconda, miniconda, pip, pycharm, pyTest, tox

Basically, its as wide and deep as any other tech stack. Sorry.

multiple assignment

a, b = 0,1

Strings

s1 = 'I am a string'
s2 = "I am a string"
s3 = '"I contain quotes"'
s4 = "'I contain aspostrophe's'"
s5 = r'Use r for a raw string, so this \n stays unchanged' 
s6 = '''A
multiline string'''
s7 = """Another
multiline string"""
s8 = 'a ' 'concatinated ' 
     'string' # Strings next to each other are concatinated
s9 = 'word'

Sequences

Strings are sequences

s9[0]   # w
s9[-1]  # d    Use - to index from the end
s9[0:2] # wo   This is slicing
s9[1:]  # ord  Slice with default end
s9[:2]  # wo   Slice with default start

Other common sequence operations

len(s9)   # calls convention dunder function __len__

Lists

Are mutable - can be changed.

squares = [1,4,9]
squares[0]
squares[-1]            # last item
squares[-3:]           # Slicing is oK  
squares[:]             # Shallow copy, also works on strings...and sequences in general
squares + [16, 25]     # Concatination
squares[3] = 9         # mutable - replace values
squares[0:3] = [1,2,3] # Assign to slices
squares[0:3] = []      # remove slices
squares[:] = []        # clear
squares.append[16]     # is more efficient than squares = squares + [16]

Other useful operations:

append, extend, remove, pop, clear, index, count, sort, reverse, copy
del a[0]    # remove an element at index location
del a[2:4}
del a[:]
del a       # delete the entire variable

Lists have the map and filter functions (as well as others)

l2 = list(filter(lambda x: x==4, squares))
new_list = list(map(lambda x: x*2, squares))

Generators

def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]

Each time the generator is called it continues from where it left off. This is the yield keyword.

Operations

2**2 # 2 to the power of 2
_ # In interactive mode this is the previous result, so you can use it like a calculator

Printing

print('The value is', i, end='!')  # end means change the end char from \n

If you have a class you can determine two formats by implementing:

str()  # produces human readable format
repr() # output for the interpreter - or syntax error if none

Other useful formatting functions are:

str="foo"
print("[",str.rjust(10),"] [",str.ljust(10),"] [",str.center(10),"] [",str.zfill(10),"]")

format

You can use format to replace format fields by index

foo="foo"
print("Hi {0}, you owe {1:.3f}".format(foo, 0.1))

If

if ok in ('y','ye','yes):
    print('OK')
elif x==0:                       # can have multiple elif.
    print('zero')
else:                            # else is optional;
    print('stupid example')

for and generators using range

for n in squares:
    print(n)
# If altering an array, then copy it first
for n in squares[:]:
    squares[1]= n**n +1   # Sorry, its a stupid example
for i in range(5):        # generates an arithmetic progression
    print(i)
range(5,10)               # 5 through to 9
range(0,10,2)             # 0, 3, 6, 9

# To iterate over indexes in a sequence
for i in range(len(squares)):
    print(i, squares[i])
# range is an object, not a list
list(range(5))            # use range to generate a list

for and break

for n in range(2,10):
    for x in range(2, n):
        if n%x==0:
            print(n, ' equals ', x, '*', n//x)    # n//x returns an int.  n/x returns float 
            break         # leave the loop
    else:
        print(n, ' is a prime number') # loop fell through

Continue

for num in range(2,10):
    if num%2==0:
        print('Found an even number ', num)
        continue
    print('Number ',num)

pass

Does nothing useful, its a placeholder saying you will come back to it

class MyEmptyClasss:
    pass

Functions

No camel case for functions, but do use them for classes.

def fib(n):
    """Print stuff"""   # If first line is a string then its a doc string.

# Example with default values
def ask_ok(prompt, retries=4):
    pass

Nested functions can reference variables from the enclosing scope

Functions can have annotations which are referenced via an attribute called annotations_

All functions return a value, None is built in.

Keyword arguments

This shows a call with a named (keyword) argument being given a value.

ask_ok('Hi', retries = 6)

Dictionary and variadic parameters

Variable length arguments (arbitary args) are taken as *args. This is a tuple holding all the args which are not named using a keyword.

Dictionary argumnents - ie keyword=value are taken as **kwords.

def foo(*args, **key_word_args):
    pass

Unpacking

args = [3,6]
list(range(*args))  # ie range takes two params, and *args unpacks into the args

# Can also unpack dictionaries into key word (named) arguments
def foo(v, s):
    pass
    
d = {'v':'four', 's':'bleed'}
foo(**d)

Lambda expressions

Lambda is a keyword, syntactic sugar for a function definition.

d = lambda x: x*2
print(d(5))

Also useful for lists filter and map

l2 = list(filter(lambda x: x==4, squares))
new_list = list(map(lambda x: x*2, squares))

Documentation strings

Can access the doc string using foo.doc

They are right after a class or a function defn. Format as below

""""Do Something

    Stuff
"""

List comprehensions

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

Tuples

are immutable.

t= 1,2,3
t1=t, (1,2,3)
t2="Single item",   # The trailing comma means this is a tuple.
t3 = () #empty

Dictionary

d = {}
d1 = ("Jack": 4098, "sape": 4139)
d1["Jack"]  # accesses value
dict( [('J',1), ('K',2)] )

Modules

Any file is a module, eg fib.py

python fix.py args

Make it executable by adding this at the end of the file

if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))

Lots of hooks into the code, for example

import sys
dir(sys) # lists all the names a module defines

File access

Good practice to use the with form as it will ensure resources are closed even if there is an exception

with open('workfile') as f:
    read_data = f.read()

There are built in formats for json and pickle (pickle is a python only format)

Google them : json.dump, json.load

Exceptions

try :
    #if no issues here then except is skipped
except ValueError:
    #if exception is ValueError then gthis is run

Or

try :
    #if no issues here then except is skipped
except (RuntimeError, TypeError, NoneError):
    #if exception is any of these then gthis is run

and

raise  #Raising (throwing...java, hey ho) an exception

also

try :
    # 
except Exception as inst:
    # inst is now the exception
else :
    # executes if no except
finally :
    # at the end, exception or not

Nested directory structure

Create packages, in each dir you need

__init__.py

so that python knows its a python package dir.

When import you can do:

from path.topackage import common

You can have a list in

__init__.py

called

__all__

to allow a wild card import, but that is considered bad form.

# Dont use it, even though it works
from path.topackage import *

Namespaces

You can define things as nonlocal and global if you want to make your code hard to understand.

Classes

Python isn’t Java, Scala, C# or C++…etc. But, hey, here goes.

class MyClass :
    """A simple class"""
    
    i=1234              #Class variable shared by all instances
    def __init__(self): # Constructor
        self.whatever="foobar"
        
    def f(self):        # all functions get self as first param
        self.data=[]    # create a new member variable

A class is basically a namespace bound to the class name.

Method objects are part of the class. Function objects are part of the class instance.

Class type is stored in

object.__class__

You can do multiple inheritance, search is depth first, left to right. If it finds duplicates then it uses the first one it finds.

class DerivedClass(Base1, Base2, Base3):

__name

Anything that starts __ should not be considered part of the public API and is subject to change. Have at most one _ trailing, the name will be mangled.

You can then keep a reference to your private function object, so if its overridden you still call your copy.

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

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

Libraries

Nice simple language, but now you have to learn all the libraries too.

eg

glob.glob(ospath.join('mydir', 'subdir', '*.html'))
os.listdir(os.path.join('mydir','subdir'))

Project structure

You could use one that is compatible with DistUtils, PyTest and tox, or you could just hack.

You could in fact not bother at all and ship jupiter notebooks.

miniconda

This is a packaged python environment. Download it, and then open the anaconda prompt.

conda create -n firstenv
activate firstenv
conda install pandas

# eg of using with azure cloud
pip install azure-cli
az login