python_notes
python_notes
1. Introduction to Python
Python's development began in the late 1980s by Guido van Rossum at Centrum
Wiskunde & Informatica (CWI) in the Netherlands as a successor to the ABC language,
which was inspired by SETL. Van Rossum is Python's principal author, and his
continuing central role in deciding the direction of Python is reflected in the title given
to him by the Python community: Benevolent Dictator For Life (BDFL) – a title he
relinquished on July 12, 2018.
Python 2.0 was released in 2000, introducing features like list comprehensions and a
garbage collection system capable of collecting reference cycles. Python 3.0, released
in 2008, was a major revision of the language that is not entirely backward-compatible,
and much Python 2 code does not run unmodified on Python 3. However, Python 3 has
gained widespread adoption and is the recommended version for new development.
Web Development: Frameworks like Django and Flask are popular for building
robust web applications.
Data Science and Machine Learning: Libraries such as NumPy, Pandas, Scikit-
learn, TensorFlow, and PyTorch make Python a dominant language in these
fields.
Automation and Scripting: Its simplicity and extensive libraries make it ideal for
automating repetitive tasks and system administration.
Desktop GUI Applications: Libraries like PyQt and Tkinter allow for the creation
of desktop applications.
To start coding in Python, you need to set up your development environment. This
typically involves installing Python and choosing an Integrated Development
Environment (IDE) or a code editor.
Installation
1. Download Python: Visit the official Python website (python.org) and download
the latest stable version for your operating system (Windows, macOS, Linux).
2. Run Installer:
Windows: Run the installer. Make sure to check the box that says "Add
Python X.Y to PATH" during installation. This will allow you to run Python
from the command line.
Linux: Python is usually pre-installed. You can check your version by typing
python3 --version in the terminal. If you need a newer version, you can
install it via your distribution's package manager (e.g., sudo apt-get
install python3 on Debian/Ubuntu).
3. Verify Installation: Open your terminal or command prompt and type python -
-version (or python3 --version ). You should see the installed Python version.
While you can write Python code in any text editor, IDEs and specialized code editors
offer features that significantly enhance productivity, such as syntax highlighting, code
completion, debugging tools, and version control integration.
VS Code (Visual Studio Code): A lightweight yet powerful code editor developed
by Microsoft. It supports Python development through extensions, offering
features like IntelliSense, debugging, and Git integration. It's highly customizable
and popular among developers for various languages.
Sublime Text: A sophisticated text editor for code, markup, and prose. It's known
for its speed, sleek user interface, and powerful features, extensible with plugins.
Atom: A free and open-source text editor developed by GitHub. It's highly
customizable and comes with a built-in package manager, making it easy to add
new features.
Choosing an IDE or editor depends on your personal preference and project needs. For
beginners, VS Code or PyCharm Community Edition are excellent starting points due
to their comprehensive features and ease of use.
2. Python Basics
Python's simplicity makes it an excellent language for beginners. Let's dive into the
fundamental building blocks of Python programming.
Variables and Data Types
A variable is a named location used to store data in the memory. It's like a container
that holds values. In Python, you don't need to declare the type of a variable explicitly;
Python infers it based on the value assigned.
print(my_integer) # Output: 10
print(my_float) # Output: 20.5
print(my_string) # Output: Hello, Python!
print(my_boolean) # Output: True
Booleans ( bool ): Represent truth values, either True or False . Used in logical
operations.
Operators
Operators are special symbols that perform operations on one or more operands
(values or variables).
Arithmetic Operators
+ Addition 10 + 5 15
- Subtraction 10 - 5 5
* Multiplication 10 * 5 50
// Floor Division 10 // 3 3
% Modulus (remainder) 10 % 3 1
** Exponentiation 2 ** 3 8
Used to compare two values and return a Boolean result ( True or False ):
Logical Operators
True and
and Returns True if both statements are true False
False
Assignment Operators
= x = 5 x = 5
+= x += 3 x = x + 3
-= x -= 3 x = x - 3
*= x *= 3 x = x * 3
/= x /= 3 x = x / 3
//= x //= 3 x = x // 3
%= x %= 3 x = x % 3
**= x **= 3 x = x ** 3
Python provides built-in functions for taking input from the user and displaying
output.
input() : Used to get input from the user. The input is always returned as a
string.
Sometimes you need to convert values from one data type to another. Python provides
built-in functions for this:
int() : Converts to an integer.
num_str = "123"
num_int = int(num_str)
print(num_int, type(num_int)) # Output: 123 <class 'int'>
num_float = 3.14
num_int_from_float = int(num_float)
print(num_int_from_float, type(num_int_from_float)) # Output: 3 <class 'int'>
(truncates decimal)
int_val = 100
str_val = str(int_val)
print(str_val, type(str_val)) # Output: 100 <class 'str'>
3. Control Flow
Control flow statements are used to execute specific blocks of code based on certain
conditions or to repeat code multiple times. This allows programs to make decisions
and perform actions dynamically.
Conditional statements allow your program to execute different code blocks based on
whether a condition is true or false. Python uses if , elif (else if), and else
keywords for this purpose.
# if statement
x = 10
if x > 5:
print("x is greater than 5")
# if-else statement
y = 3
if y % 2 == 0:
print("y is an even number")
else:
print("y is an odd number")
# if-elif-else statement
score = 85
if score >= 90:
print("Grade: A")
elif score >= 80:
print("Grade: B")
elif score >= 70:
print("Grade: C")
else:
print("Grade: F")
Loops are used to execute a block of code repeatedly. Python provides for and
while loops.
for Loop
The for loop is used for iterating over a sequence (that is, a list, tuple, dictionary, set,
or string).
# Iterating over a list
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
while Loop
Key points for loops: * Ensure the loop condition eventually becomes false to avoid
infinite loops. * Indentation defines the loop body.
The break statement terminates the loop entirely and transfers control to the
statement immediately following the loop.
for i in range(10):
if i == 5:
break # Exit the loop when i is 5
print(i) # Output: 0, 1, 2, 3, 4
continue Statement
The continue statement skips the rest of the current iteration of the loop and
proceeds to the next iteration.
for i in range(5):
if i == 2:
continue # Skip printing when i is 2
print(i) # Output: 0, 1, 3, 4
pass Statement
The pass statement is a null operation; nothing happens when it executes. It is used
as a placeholder when a statement is syntactically required but you don't want any
code to execute.
4. Data Structures
Data structures are fundamental ways to organize and store data in a computer so that
it can be accessed and modified efficiently. Python offers several built-in data
structures that are highly versatile and widely used.
Lists
# Creating a list
my_list = [1, 2, 3, "apple", "banana", True]
print(my_list) # Output: [1, 2, 3, 'apple', 'banana', True]
Modification
Since lists are mutable, you can change their elements after creation.
my_list = [10, 20, 30, 40]
my_list[0] = 15 # Change element at index 0
print(my_list) # Output: [15, 20, 30, 40]
# Add elements
my_list.append(50) # Add to the end
print(my_list) # Output: [15, 20, 30, 40, 50]
# Remove elements
my_list.remove(20) # Remove first occurrence of value 20
print(my_list) # Output: [15, 25, 30, 40, 50]
numbers = [5, 1, 4, 2, 8]
numbers.sort() # Sort the list in ascending order
print(numbers) # Output: [1, 2, 4, 5, 8]
Tuples
# Creating a tuple
my_tuple = (1, 2, "hello", 3.14)
print(my_tuple) # Output: (1, 2, 'hello', 3.14)
Immutability
Tuples are often used for heterogeneous (different types) data, where the sequence of
elements is important, and for data that should not be changed.
Dictionaries
# Creating a dictionary
my_dict = {"name": "Alice", "age": 30, "city": "New York"}
print(my_dict) # Output: {'name': 'Alice', 'age': 30, 'city': 'New York'}
# Changing a value
my_dict["age"] = 31
print(my_dict) # Output: {'name': 'Alice', 'age': 31, 'city': 'New York',
'email': 'alice@example.com'}
Sets
A set is an unordered collection of unique elements. Sets are mutable and are defined
by enclosing elements in curly braces {} (similar to dictionaries, but without key-
value pairs) or by using the set() constructor.
Creation and Operations
# Creating a set
my_set = {1, 2, 3, 2, 1} # Duplicate elements are automatically removed
print(my_set) # Output: {1, 2, 3}
# Adding elements
my_set.add(4)
print(my_set) # Output: {1, 2, 3, 4}
# Removing elements
my_set.remove(2) # Raises KeyError if element not found
print(my_set) # Output: {1, 3, 4}
# Set operations
set_a = {1, 2, 3, 4}
set_b = {3, 4, 5, 6}
Sets are particularly useful for membership testing, removing duplicates from a
sequence, and performing mathematical set operations.
5. Functions
In Python, functions are defined using the def keyword, followed by the function
name, parentheses () , and a colon : . The code block within the function must be
indented.
# Defining a simple function
def greet():
print("Hello, world!")
Default Parameters
You can provide default values for parameters. If an argument is not provided for such
a parameter, its default value is used.
def greet_default(name="Guest"):
print(f"Hello, {name}!")
Keyword Arguments
You can pass arguments using key = value syntax. This allows you to pass
arguments in any order.
def describe_person(name, age):
print(f"{name} is {age} years old.")
describe_person(name="David", age=25)
describe_person(age=30, name="Eve") # Order doesn't matter with keyword
arguments
def sum_all(*numbers):
total = 0
for num in numbers:
total += num
return total
def print_info(**data):
for key, value in data.items():
print(f"{key}: {value}")
Return Statement
The return statement is used to exit a function and return a value (or values) to the
caller. If no return statement is present, the function implicitly returns None .
result = multiply(4, 5)
print(result) # Output: 20
The scope of a variable refers to the region of the code where the variable is accessible.
Local Scope: Variables defined inside a function are local to that function and
cannot be accessed from outside.
Global Scope: Variables defined outside any function are global and can be
accessed from anywhere in the program.
def my_function():
local_var = "I am local"
print(global_var) # Can access global variable
print(local_var) # Can access local variable
my_function()
print(global_var) # Output: I am global
# print(local_var) # This would raise a NameError
modify_global()
print(f"Outside function, x: {x}") # Output: Outside function, x: 20
Lambda Functions
As your Python programs grow larger, it becomes impractical to keep all the code in a
single file. Python provides a way to organize code into reusable units called modules
and packages.
Importing Modules
A module is simply a Python file ( .py ) containing Python definitions and statements.
Modules allow you to logically organize your Python code. When you import a module,
you can use the functions, classes, and variables defined within it.
# Example: math_operations.py
# def add(a, b):
# return a + b
#
# def subtract(a, b):
# return a - b
result_add = math_operations.add(10, 5)
print(result_add) # Output: 15
result_subtract = math_operations.subtract(10, 5)
print(result_subtract) # Output: 5
# Method 3: Import all names from a module (generally discouraged for clarity)
from math_operations import *
Python has a rich standard library with many built-in modules that you can import,
such as math , random , datetime , os , sys , etc.
import math
print(math.sqrt(16)) # Output: 4.0
import random
print(random.randint(1, 10)) # Output: a random integer between 1 and 10
To create your own module, simply save your Python code in a file with a .py
extension. For example, if you save the add and subtract functions in a file named
my_module.py , you can then import my_module in other Python scripts located in the
same directory or in a directory included in Python's path.
Understanding Packages
my_project/
├── main.py
└── my_package/
├── __init__.py
├── module_a.py
└── sub_package/
├── __init__.py
└── module_b.py
# In main.py
Packages help in avoiding naming conflicts and provide a clear structure for larger
projects. The __init__.py file can also contain initialization code for the package,
though it's often left empty for simple packages.
7. File Handling
Before you can perform any operations on a file, you need to open it. The open()
function is used for this purpose. It returns a file object, which has methods for
reading, writing, and other file operations.
# Modes:
# "r": Read - Default mode. Opens a file for reading. Error if the file does
not exist.
# "w": Write - Opens a file for writing. Creates the file if it does not exist,
or truncates (empties) the file if it exists.
# "a": Append - Opens a file for appending. Creates the file if it does not
exist. If the file exists, new content is added to the end.
# "x": Create - Creates the specified file. Returns an error if the file
exists.
# "t": Text - Default mode. Opens in text mode.
# "b": Binary - Opens in binary mode (e.g., for images, executables).
Once a file is opened in read mode, you can use various methods to read its content:
read(size) : Reads at most size bytes (or characters in text mode). If size is
omitted or negative, the entire content of the file is read.
readlines() : Reads all lines from the file and returns them as a list of strings.
Writing to Files
When a file is opened in write ( "w" ) or append ( "a" ) mode, you can use the write()
method to add content.
write(string) : Writes the given string to the file. It does not add a newline
character automatically.
File operations can often lead to errors (e.g., file not found, permission denied). It's
good practice to handle these errors using try-except blocks.
try:
with open("non_existent_file.txt", "r") as file:
content = file.read()
print(content)
except FileNotFoundError:
print("Error: The file was not found.")
except Exception as e:
print(f"An unexpected error occurred: {e}")
# Instance method
def bark(self):
return f"{self.name} says Woof!"
def description(self):
return f"{self.name} is {self.age} years old."
# Accessing attributes
print(my_dog.name) # Output: Buddy
print(your_dog.age) # Output: 5
print(my_dog.species) # Output: Canis familiaris (class attribute)
# Calling methods
print(my_dog.bark()) # Output: Buddy says Woof!
print(your_dog.description()) # Output: Lucy is 5 years old.
Methods: Functions defined inside a class that perform actions on the object's
data.
self parameter: The first parameter of any instance method, it refers to
the instance of the class itself. It allows methods to access and manipulate
the instance's attributes.
Inheritance
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement abstract method")
class Cat(Animal):
def __init__(self, name, breed):
super().__init__(name) # Call the constructor of the base class
self.breed = breed
def speak(self):
return f"{self.name} says Meow!"
def get_breed(self):
return f"{self.name} is a {self.breed}."
Polymorphism
Polymorphism means "many forms." In OOP, it refers to the ability of different objects
to respond to the same method call in their own way. This is often achieved through
method overriding (as seen in inheritance) or by having functions that can operate on
objects of different types.
class Bird:
def fly(self):
print("Bird is flying")
class Airplane:
def fly(self):
print("Airplane is flying")
def make_it_fly(obj):
obj.fly()
bird = Bird()
airplane = Airplane()
Encapsulation is the bundling of data (attributes) and methods that operate on the
data within a single unit (class). It also involves restricting direct access to some of an
object's components, which can prevent accidental modification of data. In Python,
encapsulation is achieved through conventions (prefixing with underscores) rather
than strict access modifiers.
class BankAccount:
def __init__(self, balance):
self.__balance = balance # Private attribute
def get_balance(self):
return self.__balance
account = BankAccount(1000)
account.deposit(500)
account.withdraw(200)
# print(account.__balance) # This would cause an AttributeError
print(account.get_balance()) # Access balance through a public method
9. Error and Exception Handling
Types of Errors
Syntax Errors (Parsing Errors): These occur when the parser detects an
incorrect statement. They prevent the program from running. python # Example
of a syntax error # if x > 5 # print("Hello") # Missing colon after 5
Runtime Errors (Exceptions): These occur during the execution of the program.
Even if a statement is syntactically correct, it may cause an error during
execution. When a runtime error occurs, Python raises an "exception." ```python
# Example of a runtime error (ZeroDivisionError) # result = 10 / 0
Example of a runtime error
(NameError)
print(undefined_variable)
len(123)
* **Logical Errors:** These are the hardest to find. They occur when
the program runs without crashing, but produces incorrect or
unexpected results because of a flaw in the program's logic. python
Example of a logical error
average = sum_of_numbers /
count_of_numbers
If count_of_numbers is sometimes 0,
it leads to ZeroDivisionError (runtime
error)
If sum_of_numbers or
count_of_numbers are calculated
incorrectly, it leads to wrong average
(logical error)
```
Python uses the try and except statements to handle errors. The code that might
raise an exception is placed inside the try block. If an exception occurs, the code
inside the except block is executed.
# Basic try-except block
try:
num1 = int(input("Enter a number: "))
num2 = int(input("Enter another number: "))
result = num1 / num2
print(f"The result is: {result}")
except ZeroDivisionError:
print("Error: Cannot divide by zero!")
except ValueError:
print("Error: Invalid input. Please enter a valid number.")
except Exception as e: # Catch any other unexpected errors
print(f"An unexpected error occurred: {e}")
except block: This block is executed if an exception occurs in the try block.
You can specify the type of exception to catch (e.g., ZeroDivisionError ,
ValueError ). You can have multiple except blocks to handle different types of
exceptions.
else block: (Optional) This block is executed if no exception occurs in the try
block.
try:
x = 10
y = 2
result = x / y
except ZeroDivisionError:
print("Division by zero!")
else:
print(f"Division successful. Result: {result}") # Executed only if no
exception
file = None
try:
file = open("data.txt", "r")
content = file.read()
print(content)
except FileNotFoundError:
print("File not found.")
finally:
if file:
file.close()
print("File closed.")
Raising Exceptions
You can also raise your own exceptions in Python using the raise keyword. This is
useful when you want to enforce certain conditions or signal an error in your custom
functions or classes.
def validate_age(age):
if not isinstance(age, (int, float)):
raise TypeError("Age must be a number.")
if age < 0:
raise ValueError("Age cannot be negative.")
print(f"Valid age: {age}")
try:
validate_age(25)
validate_age(-5) # This will raise a ValueError
validate_age("abc") # This will raise a TypeError
except (ValueError, TypeError) as e:
print(f"Validation Error: {e}")
Raising custom exceptions allows you to create more robust and predictable code by
explicitly handling situations that are considered erroneous within your application
logic.
Python offers many advanced features and concepts that can significantly enhance
your programming capabilities. Here, we briefly touch upon a few of them.
List Comprehensions
List comprehensions are generally more readable and often faster than traditional
for loops for creating lists.
Generators
Generators are a simple and powerful tool for creating iterators. They are written like
regular functions but use the yield statement instead of return . When a generator
function is called, it returns an iterator object without starting execution immediately.
When the next() method is called on the iterator, the function executes until it hits a
yield statement. The value provided by yield is returned to the caller. Execution is
paused until next() is called again.
Generators are memory-efficient because they produce items one at a time, only when
requested, rather than creating all items in memory at once.
def countdown(num):
print("Starting countdown")
while num > 0:
yield num
num -= 1
for x in countdown(3):
print(x)
# Output:
# Starting countdown
# 3
# 2
# 1
Decorators
Decorators are a powerful and unique feature in Python that allow you to modify or
enhance the functionality of functions or methods without changing their source code.
They are essentially functions that take another function as an argument, add some
functionality, and return the modified function.
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Something is happening before the function is called.")
func(*args, **kwargs)
print("Something is happening after the function is called.")
return wrapper
@my_decorator
def say_hello(name):
print(f"Hello, {name}!")
say_hello("World")
# Output:
# Something is happening before the function is called.
# Hello, World!
# Something is happening after the function is called.
Decorators are widely used in web frameworks (e.g., Flask, Django for routing),
logging, authentication, and performance measurement.
Python's strength lies significantly in its vast ecosystem of external libraries and
frameworks, which extend its capabilities for almost any task. These libraries are
typically installed using pip (Python's package installer).
Requests: An elegant and simple HTTP library for making web requests.
To install a library, you would typically use a command like: pip install numpy
pandas
These advanced topics and external libraries are just a glimpse into the extensive
capabilities of Python, allowing developers to build complex and powerful
applications across various domains.