Python Notes
Python Notes
Introduction
Features
Python Shell
Python is an interpreted scripting language, so it doesn’t need to be compiled.
Python comes with a python shell (interactive shell). It waits for the python
code from the user. When you enter the code, it interprets the code and shows
the result in the next line.
Program Structure
# This is a comment
# importing module
import random
# defining variables
x = 10
y = "Hello world"
z = True
# using if else
if z:
print(y)
else:
print(z)
# defining a function
def greet(name):
print("Hello, " + name + "!")
Indentation
Identifiers
Identifiers are the names used to identify variables, functions, classes or other
objects. They are user-defined names that follow specific rules and
conventions.
Identifiers can only contain letters ( a-z , A-Z , digits 0-9 , and
underscores _ ).
Naming Conventions
Use pascal case(Capitalize the first letter of each word) for class
names. MyClass
False, None, True, and, as, assert, async, await, break, class, continue, de
f, del, elif, else, except, finally, for, from, global, if, import, in, is, lambda, no
nlocal, not, or, pass,raise, return, try, while, with, yield
Comments
Any text preceded by a hash(#) symbol is considered a comment and is not
executed when the code runs.
Escape Sequences
Data Types
Variables can store data of different types, and different types can do different
things.
Check Data Types: To check the data type of certain data/variable we use
the type() function.
x = 10
print(type(x)) # int
Type Casting
Variables
Assignments
Simple Assignment
x = 10
Multiple Assignment
The same variable can hold different types of values at different times.
x = 42 # x is an integer
x = "Python" # Now x is a string
x = [1, 2, 3] # Now x is a list
Unpacking Collections
If you have a collection of values in a list, tuple etc. Python allows you to
extract the values into variables. This is called unpacking.
num = [1,2,3,4,5]
one, *rest = num
print(one)
print(rest)
Printing variables
x = "Hello"
y = " World"
print(x + y) # Hello World
# but if you use numbers then it will give error
a = 10
b = "Text"
print(a+b) # error
Global Variables
x = 10
def func():
print(x)
func() # 10
If you create a variable with the same name inside a function, this variable
will be local, and can only be used inside the function. The global variable
with the same name will remain as it was, global and with the original
value.
x = 10
def glb():
x = 20
print(x)
glb() # 20
print(x) # 10
Global Keyword
def func():
global x
x = 10
print(x) # 10
---------------------------
def fun():
global x
x = "Edited Text"
fun()
print(x)
1. Mutable Data Types: It can be modified after their creation, meaning the
same object in memory is updated without creating a new object. Eg: list,
dict, set, byte array, user-defined classes.
lst = [1,2,3]
lst[0] = 0
lst.append(4)
print(lst) # [0,2,3,4]
2. Immutable Data Types: It cannot be modified after they are created. Any
operation that appears to modify the object actually creates a new object
in memory. Eg: int, float, complex, str, tuple, frozenset, bytes, bool
x = 10
x = x + 10 # a new integer object is created with value 15
print(x) # 15
------------------------
x=3
y=x
# changing value of x
x = 13
Every python object has a distinct address that informs the application where
the item is located in memory. Python have id() function to read the object’s
memory address.
string = "Hello"
print(id(string)) # 12381972831
Operators
Operators are used to perform operations on variables and values.
# Arithmetic Operators
x + y # addition
x - y # subtraction
x * y # multiplication
x / y # division
x % y # modulus
x ** y # exponential
x // y # floor division: it roundts the results down to the nearest whole num
ber
------------------------
# Assignment Operators
x = 5 # value of x is 5
x += 5 # x = x + 5
x -= 5 # x = x - 5
x *= 5 # x = x * 5
x /= 5 # x = x / 5
x %= 5 # x = x % 5
x **= 5 # x = x ** 5
x //= 5 # x = x // 5
x &= 5 # x = x & 5
x |= 5 # x = x | 5
() # parenthesis
** # exponentiation
+x, -x, ~x # unary plus, unary minus, and bitwise NOT
*, /, //, % # multiplication, division, floor division, modulus
+, - # addition, subtraction
<<, >> # left shift, right shift
& # bitwise AND
^ # bitwise XOR
| # bitwise OR
==, !=, >, >=, <, <=, is, is not, in, not in # comparisions, identity and memb
ership
not # logical NOT
and # logical AND
or # logical OR
a = False
b = True
Lazy Evaluation
It refers to delaying the computation of a value or expression until it is actually
needed. It is commonly used in constructs like iterators, generators, and
comprehensions.
The input() function is used to receive input from the user in python.
print(f"Hello, {name}!")
print(f"Age: {age}")
sys.argv
Rest elements are the arguments that are provided in the command
line
import sys
file = sys.argv[0]
print(f"Filename: {file}")
first_arg = sys.argv[1]
print(f"First argument: {first_arg}")
Control Flow
Conditional Statements
If
a = 33
b = 200
if b > a:
print("b is greater than a") # here indentation is necessary else give
error
If-else
if b > a:
print("b is greater than a")
else:
print("b is not greater than a")
If-elif-else
if b > a:
print("b is greater than a")
elif a == b:
print("a and b are equal")
Short hand If
Nested If
if x > 10:
if x > 20:
# code
else:
# code
Pass Statement
if b > a:
pass
Loops
While
i=1
while i < 6:
print(i)
i += 1
i=1
while i < 6:
print(i)
i += 1
else:
print("i is no longer less than 6") # this will run after the while loop b
ecome false
For
It is used for iterating over a sequence (that is either list, a tuple, a
dictionary, a set or a string). It doesn’t require an indexing variable to set
beforehand.
for x in range(2,6):
print(x) # 2 3 4 5
for x in range(2,30,3):
print(x) # 2 5 8 11 14 17 20 23 26 29
--------------------
# Nested for loop
for x in range(3):
for y in range(3):
print(x,y)
Break
# break statement can stop the loop before it has lloped through all th
e items
for x in range(6):
if x == 4:
break
print(x)
# output: 0 1 2 3
Continue
# continue statement can stop the current iteration of the loop, and co
ntinue with the next iteration
for x in range(6):
if x == 4:
continue
print(x)
# output: 0 1 2 3 5
# declaring a function
def function_name():
# code
---------------------
# calling a function
function_name()
Types of Functions
print("Hello") # Hello
print(len([1,2,3])) # 3
print(type(42)) # <class 'int'>
2. User-defined Functions: These are the functions created by the user using
the def keyword.
def greet(name):
print("Hello, " + name)
square = lambda x: x ** 2
print(square(5)) # 25
def factorial(n):
if n == 0:
return 1
return n * factorial(n - 1)
print(factorial(5)) # 120
def square(x):
return x ** 2
numbers = [1,2,3,4]
squared_num = list(map(square, numbers))
print(squared_num) # 1 4 9 16
def outer():
def inner():
return "Hello from inner function!"
return inner()
Arguments are specified after the function name, inside the parentheses.
You can add as many arguments as you want, just separate them with a
comma.
Actual Arguments: The parameters which we use in the function call or the
parameters which we use to send the values/data during the function call
are called actual arguments.
x = 10
y = 20
sum(x,y) # x and y are actual arguments
display("Mohammad", "Abdullah")
Arbitrary Arguments: If you don’t know how many arguments that will be
passed into your function, add a * before a parameter name in the
function definition. This way the function will receive a tuple of arguments,
and can access the items accordingly.
def display(*names):
print("The first name is: " + names[0])
print("The last name is: " + names[1])
display("Mohammad", "Abdullah")
Keyword Arguments: You can also send arguments with the key=value
syntax. This way the order of the arguments does not matter.
display(lastname="Abdullah", firstname="Mohammad")
display(lname="Abdullah", fname="Mohammad")
def display(name="Mohammad"):
print(name)
display() # Mohammad
display("Abdullah") # Abdullah
Passing a List as an Argument: You can send any data types of arguments
to a function (string, number, list, dictionary) and it will be treated as the
same data type inside the function.
def display(fruits):
for x in fruits:
print(x)
display(fruits_name)
Return Values
def sum(a,b):
return a+b
print(sum(10,20)) # 30
Nested functions have their local scope and can access names from their
enclosing scopes.
Function Lifetime
Function defined at the module level persist for the life of the module.
For nested functions, the lifetime can extend beyond the execution of the
enclosing function if they are returned or stored somewhere.
Decorators
Decorators are design pattern that allows you to modify or enhance the
behavior of functions or methods without permanently modifying their source
code. They wrap a function, adding extra functionality before or after the
original function runs. A decorator is essentially a function that takes another
function as an argument, adds some functionality, and returns a new
function(modified version of the original).
ordinary()
# I got decorated
# I am ordinary
---------------------------------
return func(a,b)
return inner
divide(2,5) # 0.4
divide(2,0) # whoops! cannot divide
Iterators
Iterators are methods that iterate collections like lists, tuples, etc. Using an
iterator method, we can loop through an object and return its elements. A
python iterator object must implement two special methods, iter() and next() ,
collectively called the iterator protocol.
# define a list
list = [1,2,3]
Generators
We define a generator function using the def keyword, but instead of the
return statement we use the yield statement. Here, the yeild keyword is
used to produce a value from the generator. When the generator function
is called, it doesn’t execute the function body immediately. Instead, it
returns a generator object that can be iterated over to produce the values.
def generator_name(arg):
# statements
yield something
--------------------------
def calc(x,y):
yield x + y
yield x - y
yield x * y
yield x / y
result = calc(20,10)
print(next(result)) # 30
print(next(result)) #
print(next(result))
print(next(result))
Modules
A module is simply a python file with .py extension that contains variables,
functions, classes and runnable code. It allows you to break your code into
separate parts, making it easier to manage, maintain, and reuse across
different projects.
Importing Modules
# main.py
import greetings # import the whole module
greetings.display() # Hello!
Variables in Module
# person.py
person1 = {
"name": "Mohammad",
"age": 24
}
# main.py
import person
age = person.person1["age"]
print(age) # 24
Math Module
The built-in math module provides access to mathematical functions and
constants.
import math
print(math.pi) # 3.14159....
print(math.sqrt(16)) # 4.0
Random Module
The
module includes functions that generate random numbers and perform
random
random operations.
import random
print(random.randint(1,10)) # random integer between 1 and 10
print(random.random()) # random float between 0.0 and 1.0
names = ["Alice","Bob","Charlie"]
print(random.choice(names)) # randomly select an element from the list
Package
A package is a way of organizing related modules into a directory hierarchy. A
package is essentially a folder that contains one or more modules along with a
special __init__.py file(which can be empty or contain initialization code) to
indicate to python that the folder should be treated as a package.
Composition
class Engine:
def __init__(self, horsepower):
self.horsepower = horsepower
def start(self):
print("Engine starts with", self.horsepower, "horsepower.")
class Car:
def __init__(self, make, model, engine):
self.make = make
self.model = model
# Composition: Car has an Engine
self.engine = engine
def start(self):
print(f"Starting the {self.make} {self.model}...")
self.engine.start()