knowledge/technology/dev/programming/languages/Python.md

26 KiB

website obj mime extension rev
https://www.python.org concept text/python py 2024-02-19

Python

Python is an interpreted programming language.

Usage

Python is a interpreted language, so you will need a python interpreter. You can code python interactively or run a script with:

python3 [script.py] [args]

Or you can prefix your script with a shebang:

#!/usr/bin/python3
print("Hello World")

Syntax

Python hello world:

print("Hello World")

Comments

Python lets you do comments with the # sign.

# This is a comment

Variables

You can define variables like this:

myvar = 3
# define multiple
a, b = 5, 9

Data Types

In Python, there are several built-in data types. Here are some examples:

Numeric Types

# Integer
my_int = 10

# Float
my_float = 3.14

String

my_string = "Hello, world!"

Boolean

my_bool = True

List

my_list = [1, 2, 3, 4, 5]

Tuple

my_tuple = (1, 2, 3)

Dictionary

my_dict = {'name': 'John', 'age': 30}

Set

my_set = {1, 2, 3}

Explicit typing

Explicit typing refers to the practice of explicitly declaring the data type of variables, parameters, or return values in a programming language. While Python is dynamically typed, meaning you don't have to declare the data type of variables explicitly, there are situations where you might want to provide type hints for clarity and documentation purposes, especially in large codebases or when working in a team.

Python 3.5 introduced type hints as a way to provide optional type information. Type hints are annotations that indicate the expected types of variables, function parameters, and return values. They do not affect the runtime behavior of the program but can be used by static analysis tools and IDEs to perform type checking.

def add(x: int, y: int) -> int:
	"""Add two integers and return the result."""
	return x + y

In this example, x: int and y: int indicate that x and y should be integers, and -> int indicates that the function returns an integer.

Type hints are not enforced by the Python interpreter, so you can still pass arguments of different types to a function that has type hints. However, tools like mypy can be used to perform static type checking and catch type errors at compile time.

if statements

In Python, if statements are used for conditional execution.

x = 10

if x > 5:
	print("x is greater than 5")

You can also include elif (short for "else if") and else clauses for more complex conditions:

x = 10

if x > 10:
	print("x is greater than 10")
elif x == 10:
	print("x is equal to 10")
else:
	print("x is less than 10")

You can also use logical operators such as and, or, and not to combine conditions:

x = 5
y = 3

if x > 0 and y > 0:
	print("Both x and y are positive")
elif x > 0 or y > 0:
	print("At least one of x or y is positive")
else:
	print("Both x and y are non-positive")

Remember to use proper indentation to define blocks of code within if, elif, and else statements in Python.

for statements

In Python, for loops are used to iterate over sequences like lists, tuples, strings, or other iterable objects. Here's a basic example:

# Iterate over a list
my_list = [1, 2, 3, 4, 5]
for item in my_list:
	print(item)

You can also use the range() function to generate a sequence of numbers to iterate over:

# Iterate over a range of numbers
for i in range(5):
	print(i)

You can iterate over other iterable objects as well, such as strings:

# Iterate over a string
my_string = "Hello"
for char in my_string:
	print(char)

break and continue statements

In Python, break and continue are used to control the flow of loops.

break statement

The break statement is used to exit a loop prematurely. Here's an example:

# Break the loop when x reaches 5
x = 0
while True:
	print(x)
	x += 1
	if x == 5:
		break

In this example, the loop will iterate indefinitely until x equals 5. Once x reaches 5, the break statement is executed, and the loop terminates.

continue statement

The continue statement is used to skip the rest of the code inside a loop for the current iteration and proceed to the next iteration. Here's an example:

# Skip even numbers and print odd numbers
for i in range(10):
	if i % 2 == 0:
		continue
	print(i)

In this example, when i is an even number, the continue statement is executed, and the loop moves to the next iteration without executing the print(i) statement. As a result, only odd numbers are printed.

pass statement

In Python, the pass statement is a null operation. It is used when a statement is syntactically required but you do not want to execute any code. It acts as a placeholder. Here's an example:

if x < 10:
	pass  # This block does nothing
else:
	print("x is greater than or equal to 10")

match statement

The match statement was introduced in Python 3.10 as a replacement for the switch statement. It provides a more flexible and readable way to perform pattern matching. Here's an example:

def weekday_name(day_num):
	match day_num:
		case 1:
			return "Monday"
		case 2:
			return "Tuesday"
		case 3:
			return "Wednesday"
		case 4:
			return "Thursday"
		case 5:
			return "Friday"
		case 6 | 7:
			return "Weekend"
		case _:
			return "Invalid day" 

print(weekday_name(3))  # Output: Wednesday
print(weekday_name(6))  # Output: Weekend
print(weekday_name(9))  # Output: Invalid day

In this example, the match statement takes an expression (day_num) and matches it against several patterns using case clauses. The _ acts as a wildcard pattern that matches any value not covered by previous cases.

The | operator allows you to match multiple patterns together, like case 6 | 7: which matches either 6 or 7.

Functions

In Python, functions are blocks of reusable code that perform a specific task. They allow you to break down your program into smaller, manageable pieces.

Defining a Function

You can define a function using the def keyword followed by the function name and parentheses containing any parameters the function takes. The function body is indented below the function definition. Here's an example:

def greet(name):
	"""This function greets the person with the given name."""
	print("Hello, " + name + "!")

In this example, greet is the function name, and it takes one parameter name.

Calling a Function

To call a function, simply use the function name followed by parentheses containing any required arguments. Here's how you call the greet function defined above:

greet("Alice")

Ordering of the function arguments matters, unless you use explicit named arguments:

def eat(food, amount):
	print(f"I eat {amount} {food}")

# Ordering matters
eat("Pizza", "3") # Output: I eat 3 Pizza
eat("3", "Pizza") # Output: I eat Pizza 3

# Named arguments
# Improved readability
eat(food="Pizza", amount="3") # Output: I eat 3 Pizza
# Ordering does not matter
eat(amount="3", food="Pizza") # Output: I eat 3 Pizza

Returning Values

Functions can also return values using the return keyword. Here's an example:

def add(a, b):
	"""This function adds two numbers."""
	return a + b

You can capture the returned value by assigning it to a variable or use it directly. Here's how you can use the add function:

result = add(3, 5)
print(result)  # Output: 8

Default Parameters

You can provide default values for parameters in a function. If a parameter is not provided when calling the function, it will use the default value. Here's an example:

def greet(name="World"):
	"""This function greets the person with the given name."""
	print("Hello, " + name + "!")

You can call this function with or without an argument:

greet() # Output: Hello, World!
greet("Alice")  # Output: Hello, Alice!

Lambda expressions

Lambda expressions, also known as lambda functions or anonymous functions, are a way to create small, unnamed functions in Python. They are defined using the lambda keyword, which is followed by a list of parameters, a colon, and an expression. Lambda functions can take any number of arguments but can only have one expression. They are typically used when you need a simple function for a short period of time and don't want to define a full-fledged function using the def keyword.

Here's the basic syntax of a lambda expression:

lambda arguments: expression

Here's an example of a lambda function that takes two arguments and returns their sum:

add = lambda x, y: x + y

You can then call this lambda function like a regular function:

result = add(3, 5)
print(result)  # Output: 8

Lambda functions are often used in conjunction with built-in functions like map(), filter(), and reduce() to perform operations on iterables.

For example, you can use a lambda function with map() to apply a function to each element of a list:

numbers = [1, 2, 3, 4, 5]
squared = map(lambda x: x**2, numbers)
print(list(squared))  # Output: [1, 4, 9, 16, 25]

Documentation Comments

In Python, documentation comments, also known as docstrings, play a crucial role in explaining the purpose and usage of functions, classes, modules, and packages.

  1. Module-Level Docstrings: These comments describe the module's purpose, its contents, and any important information for users. They appear at the beginning of the Python file, enclosed in triple quotes (''' or """).
'''This module provides utility functions for string manipulation.'''
  1. Function and Method Docstrings: These comments explain the purpose, parameters, return values, and any exceptions raised by a function or method. They are placed immediately after the function or method definition, again enclosed in triple quotes.
def greet(name):
	'''Greet the user by name.
	
	Args:
		name (str): The name of the user.
	
	Returns:
		str: A greeting message.'''
	return f"Hello, {name}!"
  1. Class Docstrings: Similar to function docstrings, these comments describe the purpose and usage of a class. They are placed immediately after the class definition.
class Calculator:
	'''A simple calculator class.'''
	
	def add(self, x, y):
		'''Add two numbers.'''
		return x + y

Modules

In Python, a module is a file containing Python definitions and statements. The file name is the module name with the suffix .py. Modules are used to organize Python code into reusable units, facilitating better code management and sharing of functionality across different projects.

Creating Modules

To create a module, simply save your Python code in a file with a .py extension. For example, if you have a file named my_module.py containing the following code:

# my_module.py
def greet(name):
	return f"Hello, {name}!"

You can now import this module in another Python script using the import statement:

import my_module

print(my_module.greet("Alice"))  # Output: Hello, Alice!

Module Attributes

Modules can contain functions, classes, and variables. These can be accessed using dot notation (.) after importing the module.

# Accessing functions from a module
import math
print(math.sqrt(25))  # Output: 5.0
# Accessing variables from a module
print(math.pi)  # Output: 3.141592653589793

Module Search Path

When you import a module, Python searches for it in the directories listed in the sys.path variable. This includes the current directory, directories in the PYTHONPATH environment variable, and standard library directories.

Importing Specific Attributes

You can import specific attributes (functions, classes, or variables) from a module using the from ... import ... syntax.

from math import sqrt
print(sqrt(25))  # Output: 5.0

This method allows you to use the imported attribute directly without qualifying it with the module name.

Aliasing Modules and Attributes

You can alias module names and attributes using the as keyword.

import math as m
print(m.sqrt(25))  # Output: 5.0

from math import sqrt as square_root print(square_root(25))  # Output: 5.0

Executing Modules as Scripts

When you run a Python script directly, the code in the script is executed line by line. However, sometimes you may want certain code in a module to only run when the module is executed as a script, not when it's imported.

You can achieve this by adding the following block:

if __name__ == "__main__":
	# Code to run when the module is executed directly
	print("This code runs only when the module is executed directly.")

Modules in Folders and Multiple Python Files

In larger projects, organizing code into modules within folders can enhance maintainability and scalability. Here's how you can structure your project:

  1. Creating a Folder Structure: Organize your project files into folders based on functionality or modules.
  2. Module Files: Each module can be contained within its own .py file within the appropriate folder. For example:
my_project/
├── main.py
├── utils/ 
│   ├── __init__.py 
│   ├── helper_functions.py 
│   └── constants.py 
├── models/ 
│   ├── __init__.py 
│   ├── user.py 
│   └── product.py
  1. __init__.py Files: The presence of an __init__.py file in a folder indicates to Python that the folder should be treated as a package. This file can be empty or contain initialization code for the package.
  2. Importing Modules from Folders: You can import modules from folders using dot notation (.) to traverse the folder structure.
# Importing modules from folders
from utils.helper_functions import some_function
from models.user import User

String Formatting

String formatting in Python refers to the process of creating strings with placeholders that are replaced by the values of variables or expressions. Python offers several methods for string formatting, each with its own advantages and use cases.

Using % Operator (Old Style Formatting)

The % operator is an older method of string formatting in Python. It uses a syntax similar to C's printf function.

name = "Alice"
age = 30
formatted_string = "Name: %s, Age: %d" % (name, age)
print(formatted_string)  # Output: Name: Alice, Age: 30

Using str.format() Method

The str.format() method provides more flexibility and readability compared to the % operator.

name = "Bob"
age = 25
formatted_string = "Name: {}, Age: {}".format(name, age)
print(formatted_string)  # Output: Name: Bob, Age: 25

You can also use named placeholders for better clarity:

formatted_string = "Name: {name}, Age: {age}".format(name=name, age=age)

Using f-strings (Formatted String Literals)

f-strings provide a concise and intuitive way to format strings by embedding expressions directly inside string literals.

name = "Charlie"
age = 35
formatted_string = f"Name: {name}, Age: {age}"
print(formatted_string)  # Output: Name: Charlie, Age: 35

f-strings support expressions and function calls:

formatted_string = f"Name: {name.upper()}, Age in 5 years: {age + 5}"

Errors and Exceptions

In Python, errors and exceptions are inevitable parts of programming. Errors occur when the Python interpreter encounters a problem while executing code, while exceptions are events that disrupt the normal flow of the program due to errors. Understanding how to handle errors and exceptions is crucial for writing robust and reliable Python code.

Syntax Errors

Syntax errors, also known as parsing errors, occur when the Python interpreter encounters invalid syntax in your code. These errors prevent the interpreter from executing the code.

# Syntax Error: Missing colon in the if statement
if x == 5
	print("x is 5")

Exceptions

Exceptions are errors that occur during the execution of a Python program. Common exceptions include TypeError, ZeroDivisionError, ValueError, NameError, and FileNotFoundError, among others.

# ValueError: int() argument must be a string or a number, not a list
num_list = [1, 2, 3]
num = int(num_list)

Handling Exceptions with try-except Blocks

You can handle exceptions using try-except blocks, allowing you to gracefully manage errors and prevent program termination.

try:
    num = int(input("Enter a number: "))
    result = 10 / num
    print("Result:", result)
except ValueError:
    print("Please enter a valid number.")
except ZeroDivisionError:
    print("Cannot divide by zero.")

Handling Multiple Exceptions

You can handle multiple exceptions in a single except block or use separate except blocks for each exception type.

try:
    # Code that may raise exceptions
except (ValueError, ZeroDivisionError) as e:
    # Handle multiple exceptions
    print("An error occurred:", e)
try:
    # Code that may raise exceptions
except ValueError as ve:
    print("ValueError occurred:", ve)
except ZeroDivisionError as ze:
    print("ZeroDivisionError occurred:", ze)

Handling All Exceptions

You can also use a generic except block to catch all exceptions. However, this approach is not recommended as it may catch unexpected errors and hide bugs.

try:
	# Code that may raise exceptions
except Exception as e:
	# Handle all exceptions
	print("An unexpected error occurred:", e)

Finally Block

The finally block is used to execute cleanup code, such as closing files or releasing resources, regardless of whether an exception occurred or not.

try:
	# Code that may raise exceptions
except SomeException:
	# Handle exception
finally:
	# Cleanup code

Raising Exceptions

You can raise exceptions manually using the raise statement to indicate that an error has occurred under certain conditions.

x = -1
if x < 0:
	raise ValueError("x must be a non-negative number")

Classes

In Python, a class is a blueprint for creating objects. Classes provide a way to organize and structure code by grouping related data and functionality together. Objects created from classes are instances of those classes and can have their own attributes and methods.

Defining a Class

To define a class in Python, use the class keyword followed by the class name and a colon (:). Inside the class definition, you can include attributes and methods.

class MyClass:
	"""A simple example class"""
	
	# Class attribute
	class_variable = 10

	# Constructor method (initializer)
	def __init__(self, name):
		self.name = name  # Instance attribute
	
	# Instance method
	def greet(self):
		return f"Hello, {self.name}!"

Creating Objects (Instances)

To create an object (instance) of a class, use the class name followed by parentheses (()). This calls the class's constructor method (__init__) to initialize the object.

# Creating objects of MyClass
obj1 = MyClass("Alice")
obj2 = MyClass("Bob")

Accessing Attributes and Methods

You can access attributes and methods of an object using dot notation (.).

# Accessing attributes
print(obj1.name)  # Output: Alice

# Accessing methods
print(obj2.greet())  # Output: Hello, Bob!

Class and Instance Attributes

Class attributes are shared among all instances of a class and are defined outside any method within the class. Instance attributes are unique to each instance and are typically initialized within the constructor method.

class Dog:
	species = "Canis familiaris"  # Class attribute
	
	def __init__(self, name, age):
		self.name = name  # Instance attribute
		self.age = age    # Instance attribute

Inheritance

Inheritance allows a class (subclass) to inherit attributes and methods from another class (superclass). This promotes code reuse and supports hierarchical relationships between classes.

class Animal:
	def speak(self):
		return "Animal speaks"

class Dog(Animal):
	def speak(self):
		return "Woof!"
		
class Cat(Animal):
	def speak(self):
		return "Meow!"

Encapsulation

Encapsulation is the bundling of data and methods that operate on the data within a class. It helps in hiding the internal implementation details of a class and protecting the data from external interference.

class BankAccount:
	def __init__(self, balance):
		self._balance = balance  # Encapsulated attribute
	
	def deposit(self, amount):
		self._balance += amount

	def withdraw(self, amount):
		if amount <= self._balance:
			self._balance -= amount
		else:
			print("Insufficient funds")

Polymorphism

Polymorphism allows objects of different classes to be treated as objects of a common superclass. This enables code to be written in a more generic way, promoting flexibility and extensibility.

class Shape:
	def area(self):
		pass

class Rectangle(Shape):
	def __init__(self, length, width):
		self.length = length
		self.width = width
	
	def area(self):
		return self.length * self.width

class Circle(Shape):
	def __init__(self, radius):
		self.radius = radius

	def area(self):
		import math
		return math.pi * self.radius ** 2

Dataclasses

Python 3.7 introduced the dataclasses module, which provides a decorator and functions for automatically adding special methods to classes. Dataclasses are a convenient way to create classes primarily meant to store data, with less boilerplate code.

from dataclasses import dataclass

@dataclass
class Point:
	x: int
	y: int
	
# Usage
p = Point(1, 2)
print(p)  # Output: Point(x=1, y=2)

Decorators

Decorators are a powerful feature in Python that allow you to modify or extend the behavior of functions or methods without changing their implementation. Decorators are implemented using functions themselves and are denoted by the @ symbol followed by the decorator function name.

Function Decorators

Function decorators are the most common type of decorators in Python. They wrap a function, allowing you to execute code before and/or after the wrapped function is called, or modify its behavior.

def my_decorator(func):
	def wrapper():
		print("Something is happening before the function is called.")
		func()  # Call the original function
		print("Something is happening after the function is called.")
	return wrapper

@my_decorator
def say_hello():
	print("Hello!")

# Calling the decorated function	
say_hello()

Decorator Syntax

The @decorator_function syntax is a shorthand for function = decorator_function(function). It applies the decorator function to the function immediately below it.

# Equivalent to: say_hello = my_decorator(say_hello) @my_decorator
def say_hello():
	print("Hello!")

Decorators with Arguments

Decorators can accept arguments by wrapping them in an additional function. This allows you to customize the behavior of decorators based on parameters.

def repeat(num_times):
	def decorator_repeat(func):
		def wrapper(*args, **kwargs):
			for _ in range(num_times):
				result = func(*args, **kwargs)
			return result
		return wrapper
	return decorator_repeat
	
@repeat(num_times=3)
def greet(name):
	print(f"Hello, {name}!")

# Calling the decorated function
greet("Alice")

Class Decorators

In addition to function decorators, Python also supports class decorators. Class decorators wrap entire classes instead of individual functions.

def add_methods(cls):
	cls.method1 = lambda self: print("Method 1 added dynamically.")
	cls.method2 = lambda self: print("Method 2 added dynamically.")
	return cls
	
@add_methods
class MyClass:
	pass
	
# Creating an instance of the decorated class
obj = MyClass()
obj.method1()  # Output: Method 1 added dynamically.

Built-in Decorators

Python provides several built-in decorators, such as @staticmethod, @classmethod, and @property, which are commonly used in object-oriented programming.

class MyClass:
	def __init__(self, value):
		self._value = value
		
	@staticmethod
	def static_method():
		print("This is a static method.")
		
	@classmethod
	def class_method(cls):
		print("This is a class method.")

	@property
	def value(self):
		return self._value
		
# Using the built-in decorators
MyClass.static_method()
MyClass.class_method()
obj = MyClass(10)
print(obj.value)

Standard Library

The Python Standard Library is a collection of modules and packages that provides a wide range of functionality to Python developers. These modules cover various domains such as file I/O, networking, data manipulation, web development, and more. Leveraging the Python Standard Library can save you time and effort by providing pre-built solutions for common tasks. Some of the most commonly used built-in modules include:

  • os: Operating system interfaces for file and directory manipulation.
  • sys: System-specific parameters and functions.
  • datetime: Date and time manipulation.
  • random: Random number generation.
  • math: Mathematical functions.
  • json: JSON encoding and decoding.
  • io: Core tools for working with streams.
  • os.path: Functions for manipulating file paths.
  • shutil: High-level file operations such as copying and archiving.
  • glob: Unix-style pathname pattern expansion.
  • tempfile: Temporary file and directory creation.
  • socket: Low-level networking interface.
  • http.client: HTTP client library.
  • urllib: URL handling modules.
  • ftplib: FTP client library.
  • smtplib: SMTP client library for sending email.
  • csv: CSV file reading and writing.
  • pickle: Object serialization.
  • re: Regular expression matching
  • logging: Flexible logging system for Python.
  • argparse: Command-line argument parsing.
  • collections: Additional data structures beyond the built-in ones.
  • concurrent: Utilities for concurrent programming.
  • multiprocessing: Process-based parallelism

Third Party Modules

  • NumPy: Fundamental for numerical computing. Provides high-performance multidimensional arrays.
  • Pandas: Offers data structures and tools for data manipulation and analysis, particularly with labeled data.
  • Matplotlib: Comprehensive library for creating static, animated, and interactive visualizations.
  • Requests: Simplifies making HTTP requests, facilitating interaction with web services and APIs.