Last update: 2022-02-19
High-Level Overview
- Python is an interpreted, high-level, dynamically typed, garbage-collected, general-purpose programming language.
- It supports multiple programming paradigms, including structured (particularly, procedural), object-oriented, and functional programming.
- Python’s design philosophy emphasizes code readability with its notable use of significant whitespace.
- Its language constructs and OO approach aims to help programmers write clear, logical code for small and large-scale projects.
Environment Setup
- Python 3.x
- Editor/ Integrated Development Environment (IDE)
- Virtual Environment Manager (optional)
Windows Setup
Linux Setup, e.g., Ubuntu
# Installing Python 3.9 using apt
sudo apt update
sudo apt install software-properties-common
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update
sudo apt install python3.9
Resources
- Free Guides
- Pluralsight
- Linux Academy/ A Cloud Guru
Python Syntax
Basics
Python uses whitespace to delimit control flow blocks (following the off-side rule).
#!/usr/bin/env python
"""
This is a docstrings (documentation strings) documenting a module, class, method or function.
"""
# a comment, variable declaration and assignment
i = 1
# input/ output
in_cnt = int(input("input a number: "))
print(f"You entered: {in_cnt}")
# control statements
if in_cnt >= 10:
print("gte10")
elif in_cnt > 7:
pass
elif in_cnt > 5:
print("gt5")
else:
print("lt5")
# loops
while i < in_cnt:
i += 1
if i >= 5 and i <8:
continue
print("*" * (i-1))
if i == 8:
break
for i in range(in_cnt):
print("^" * i)
# function definition
def test(x, y=1):
return x * y
# anonymous/ inline function
lambda num: num ** 2
# inline if statement / conditional expression / 'if' operator
var_a = 'a' if 'a' > 'b' else 'b'
# recursion
def fibonacci_of(n):
if n in {0, 1}: # Base case
return n
return fibonacci_of(n - 1) + fibonacci_of(n - 2) # Recursive case
Data Types
Since Python is dynamically typed, values carry type (at the runtime). However, all variables in Python are also objects.
- Text –
str
- Numeric –
int, float, complex
- Sequence –
list, tuple, range
,str
- Mapping –
dict
- Set –
set, frozenset
- Boolean –
bool
- Binary –
bytes, bytearray, memoryview
# Strings - ordered sequence of characters
my_str = 'HEllo WoRlD'
print(len(my_str))
words = my_str.split() # default sep = " "
# String functions
print("Lowercase:", my_str.lower())
print("Uppercase:", my_str.upper())
print("Capitalized:", my_str.capitalize())
print("Title Case:", my_str.title())
print("Second char: " + my_str[1])
# String interpolation / f-strings (Python 3.6 +)
name = input("what is your name? ")
print(f'Hello, {name}!')
# Indexing strings -- string[index]
m = "test_string"
print("First char: " + m[0])
print("Last char: " + m[-1])
print("Middle char: " + m[int(len(m)/2)])
# Slicing strings -- string[start_index, stop_index, stride]
print("Even index chars: " +m[::2])
print("Odd index chars:"+m[1::2])
print("Reversed message: " + m[::-1])
# Numbers - ints, floats
my_int = 1
my_float = 1.0
my_sci_num = 4.5e9 # 4.5 * (10**9)
# Bools - True/ False
my_bool = False
print(my_bool)
# Typecasting
a = int("1") # convert string to int
b = float("1.25") # convert string to float
c = str(123) # convert int to string
e = bool("value") # convert value to bool (anything but 0 or empty = Treu)
# Other types include: ascii(), chr(), hex(), ord(), type()
Collections
- Ordered sequential types
Lists
(dynamic arrays)Tuples
(imutable sequence type, fixed length)Strings
- Unordered types (mappings)
Dictionaries
Sets
# Lists - sorted, ordered sequence of objects (mutable)
list1 = ["one", 2, 11, 4, 15, 6, 7]
list1.index(1) # get the item under specified index
list1.append(8) # append item to the end of the list
list1.insert(0, 1) # insert item at the specified position
list1.pop() # takes an index param, default =-1 i.e. the last element
# List functions
sorted_list = sorted(list1) # new sorted list
reversed_list = list(reverse(list1)) # reverse iterator
# List slicing
list2 = list1[1:] # slice from the 2nd element on
# List comprehension [ item_output expression for item_output in list ]
# can also add if() front of item_output
list3 = [x for x in range(100)]
powers_of_eight = [8**n for n in range(1, 6)]
# filter() -- takes a function and a list and applies function to the list
def square(num):
return num ** 2
squares = list(map(square, my_num))
for n in filter(square, my_num):
print(n)
# Dictionaries - unordered, unsorted, key-value pairs (mutable)
my_dict = {'key1': 'value1',
'key2': 'value2',
'key3': ['one', 'two', 'three', 4, 5, 6]}
my_dict['key4'] = "value4"
my_dict['key1'] = "new value"
del my_dict['key2']
print(f"Keys: {list(my_dict.keys())}")
print(f"Values: {list(my_dict.values())}")
print(f"Items: {list(my_dict.items())}")
# Tuples - ordered sequence of objects (immutable)
tup = (1, 2, 3, 4, 4, 4)
print(tup[3])
print(tup.count(4))
# tuple unpacking
tup_list = [(1, 2), (3, 4), (5, 6)]
for (a, b) in tup_list:
print(a)
# Sets - unordered collections of unique elements (mutable)
my_set = set()
my_set.add(1)
my_set.add(2)
my_set.add(2)
print(my_set)
Generators
Generators allow us to declare a function that behaves like an iterator and generate a sequence of values over time. The main difference in syntax is the yield
statement, which returns one element at a time, and there’s no need to store the entire list in memory, e.g. range() is a generator.
"""
Intro to python generators - lazy evaluation
"""
# in memory list example
def create_cubes(n):
result = []
for x in range(n):
result.append(x ** 3)
return result
print(create_cubes(10))
# generator - more memory efficient
def cubes(n):
for x in range(n):
yield x ** 3
print(cubes(10))
# generator objects (return of generator function) need to be iterated over
def gen_fibon(n):
a = 1
b = 1
for _ in range(n):
yield a
a, b = b, a + b
print(list(gen_fibon(10)))
# char range generator examnple
def char_range(start, stop, step=1):
stop_modifier = 1
start_code = ord(start)
stop_code = ord(stop)
if start_code > stop_code:
step *= -1
stop_modifier *= -1
for value in range(start_code, stop_code + stop_modifier, step):
yield chr(value)
Decorators
A decorator @decorator_name
is a function that takes another function as a parameter and extends the behaviour (adds new functionality to an existing object) of the latter function without explicitly modifying it.
"""
Intro to decorators
"""
# decorator - adding extra functionality on the runtime
# commonly used in web frameworks such as flask and django e.g. routing etc.
# @ is used to declare decorators
# returning a function from within a function
def func():
print('upper function')
def func2():
print('nested function')
def func3():
print('nested 2 levels')
return 72
return func3()
return func2()
test = func()
print(test)
def cool():
def super_cool():
return 'I''m so fancy'
return super_cool
# pointer/ delegate
some_func = cool()
print(some_func)
# decorator example - long way using a wrapper function
def new_decorator(original_function):
def wrap_func():
print('Some extra code, before the original function')
original_function()
print('Some extra code, after the original function')
return 42
return wrap_func()
def func_needs_decorator():
print('I need to be decorated')
decorated_func = new_decorator(func_needs_decorator)
print(decorated_func)
# short way using @ declaration
@new_decorator
def func_needs_decorator2():
print('I want to be decorated 2')
Object Oriented Programming (OOP)
"""
Object oriented programming in python
"""
import unittest
# self. is the same as this.
# class names - use camel casing
# methods - use lower casing and underscores
# class properties = class attributes
# constructor = __init__(self, args**)
class Dog():
# class object attributes/ constants
species = 'mammal'
def __init__(self, breed):
self.breed = breed
def speak(self):
print('woof')
def bark(self, num):
for _ in range(num):
print("woof ")
# inheritance
# pass base class as a parameter in derived class constructor
# e.g. base class -- moreless an abstract class
class Animal():
def __init__(self):
print('animal created')
def who_am_i(self):
print('i am an animal')
def eat(self):
print('i am eating')
class MoreAbstractClass():
def __init__(self, *args, **kwargs):
pass
def abstract_method(self):
raise NotImplementedError('derived class must implement this method')
# e.g. deriver class
class Cat(Animal):
def __init__(self):
Animal.__init__(self)
print('cat created')
def speak(self):
print('meow')
# polymorphism
# when object share the same methods
# pass an object as a parameter to the method
# it'll be auto resolved based on its type e.g.
def pet_speak(pet):
print(pet.speak())
# special methods (__ - dunder)
# __init__(self) - constructor __str__(self) - to string
# __len__(self) - length __del__(self) - delete
# access modifiers: global - public, _ - protected, __ private
gloabl a = "global, it's terrible, global scope e.g., inside function"
_b = "protected, it can still be accessed and modified from outside the class"
__c = "private, it cannot be accessed or modified from the outside"
# when using tuples in classes unpacking can be done in __init__ or any other method
# unittest - a built-in library
# unit testing using a test class e.g.
class TestCap(unittest.TestCase):
def test_one(self):
text = 'python'
result = str.capitalize(text)
self.assertEqual(result, 'Python')
Common Problems
# How to reverse a string
def reverse_string(s):
return s[::-1]
# Is given string a palindrome
def is_palindrome(item):
item = str(item)
return item == item[::-1]
# How do you prove that the two strings are anagrams
def are_anagrams(s1, s2):
if len(s1) != len(s2): return False
return list(s1).sort() == list(s2).sort()
# How to get count of each char in a string
def repeating_chars(s):
m = {}
for i in (range(0, len(s))):
if s[i] in m.keys():
m[s[i]] += 1
else:
m[s[i]] = 1
return {k:v for k,v in m.items() if v >1}
# Get the number of vowels and consonants in a string
def count_cons_vols(s):
v_list = ['a', 'o', 'e', 'i', 'o', 'u']
v, c = 0, 0
for i in range(0, len(s)):
if s[i] in v_list:
v += 1
else:
c += 1
return (c,v)
# Find the count for the occurrence of a particular character in a string.
def char_count(c, s):
d = {}
for i in range(0,len(s)):
if s[i] in d.keys():
d[s[i]] += 1
else:
d[s[i]] = 1
return int(d[c])
# Find missing number in a given array
def missing(arr):
n = len(arr)
total = (n+1)*(n+2)/2
sum_arr = sum(arr)
return total - sum_arr
# Two Sum Problem
def sum_two(arr, target):
hash_table = {}
for i in range(len(arr)):
comp = target - arr[i]
if comp in hash_table:
return (i, hash_table[comp])
else:
hash_table[arr[i]] = i
# FizzBuzz
def fizzbuzz(upper_number):
for number in range(1, upper_number + 1):
if number % 3 == 0 and number % 5 == 0:
print("FizzBuzz")
elif number % 3 == 0:
print("Fizz")
elif number % 5 == 0:
print("Buzz")
else:
print(number)
# Fibonacci Sequence using recursion
def fib(n):
if n == 0: return 0
elif n == 1: return 1
else: return fib(n-1) + fib(n-2)
# Fibonacci Sequence using a generator
def gen_fibon(num):
a, b = 0, 1
for _ in range(0, num+1):
yield a
a, b = b, a + b # Adds values together then swaps them