Table of contents
- Introduction:
- Installing Python
- Execution of python program
- Variables
- Datatypes
- Naming Conventions
- Indentation in python
- Basic Operations
- String Manipulation
- TypeCasting
- User Input
- Control Flow
- Loops
- Function
- Defining a Function:
- Example Function Without Parameters:
- Example Function with Parameters and a Return Value:
- Function Documentation (Docstrings):
- Calling a Function:
- Default Argument Values:
- Function Scope:
- Returning Values from Functions:
- Recursion
- Components of a Recursive Function:
- The Recursive Process:
- Example - Factorial Calculation:
- Advantages of Recursion:
- Disadvantages of Recursion:
- Summary:
- Keyword arguments
- *args in functions
- **kwargs in functions
- Exception Handling
- File Handling
- Opening and Closing Files:
- Reading from Files:
- Writing to Files:
- Using with Statements (Context Managers):
- Binary File Handling:
- Exception Handling for File Operations:
- Copy in file handling
- 1. Copying Text Files:
- 2. Copying Binary Files:
- Using shutil for File Copying:
- Handling File Existence:
- File and Folder removal
- Deleting Files:
- Deleting Folders (Directories):
- Modules
- Random Module in python
- Object Oriented Programming
- Advanced Functional Programming
Github Repo for all of the related and in-detail code
Introduction:
Python is a high-level, general-purpose programming language that emphasizes code readability and versatility. The design philosophy of Python enhances readability by using significant indentation and English keywords. It supports multiple programming paradigms, including structured (particularly procedural), object-oriented, and functional programming. Python is dynamically typed and garbage-collected, meaning it handles memory management automatically, freeing up the programmer to focus on other aspects of the project
Python is known for its simplicity and easy-to-understand syntax. It's often described as a "batteries included" language due to its comprehensive standard library, which provides a wide variety of functionality out of the box. This, combined with the language's simplicity, makes Python a good choice for beginners looking to learn programming.
Python is also a versatile language, making it suitable for a wide range of applications. It is commonly used for web development, data science, machine learning, artificial intelligence, scientific computing, and much more Its versatility is further enhanced by a large collection of frameworks and libraries, which can significantly reduce development time
Python's popularity stems from its simplicity, readability, and versatility. Its syntax mimics natural language, making it easier to read and understand. As such, Python is beginner-friendly and is often the first choice for newcomers to programming. Python is an open-source language, meaning it's free to use, and it has a large and active community that contributes to its development and provides support to other programmers.
Here's a simple example of Python's simplicity. If we want to display the message "Hello, World!" in Python, the code would be:
print("Hello, World!")
This simplicity makes Python a great choice for beginners. In addition to being beginner-friendly, Python's simplicity and versatility make it a powerful tool for experienced programmers and developers. As a result, Python consistently ranks among the most popular programming languages in various surveys.
In summary, Python's simplicity, readability, versatility, and strong community support make it a popular choice for both beginners and experienced developers. Whether you're just starting in programming or you're an experienced developer looking to expand your skill set, Python is a valuable language to learn.
Installing Python
To install Python on your computer, you can follow these steps:
Check for Pre-existing Python Installation: Python comes pre-installed on many systems. To check if it's already installed on your system, open your command prompt (Windows) or terminal (macOS or Linux) and type
python --version
. If Python is installed, this command will display the version number. If it's not, you'll need to install it.Download Python: Navigate to the official Python website's downloads page. Select the version that is appropriate for your operating system (Windows, macOS, or Linux). If you're using Windows, you also have the option of installing Python from the Microsoft Store, which is an easy way to get up and running without any fuss, or you can simply go to the official website of python Download Python
Install Python: Run the installer file that you downloaded. During the installation, you may need to check the option "Add Python to PATH" to ensure that Python is accessible from any command prompt or terminal window. You can generally accept the default settings during the installation.
Verify Installation: After the installation is complete, you can verify that Python was installed correctly by re-opening your command prompt or terminal and running the
python --version
command again. This time, it should display the version number of the Python you installed.
Remember, Python is a versatile language with a large standard library. However, there might be additional libraries you'll want to use for specific projects. To install these additional libraries, Python uses a package manager called pip
. This should be included with your Python installation.
Here's a simple example of how to use pip
to install a popular library called requests
:
pip install requests
This command will download and install the requests
library, which you can then use in your Python programs to make HTTP requests.
In conclusion, installing Python is generally a straightforward process. Once installed, Python provides a versatile programming environment for beginners and experienced developers alike.
Execution of python program
The execution of a Python program involves a multi-step process that begins when you run a Python script. Here's a simplified overview of how a Python program gets executed:
Processing the Script: The Python interpreter processes the statements of your script sequentially. This means it reads and interprets your code line by line from top to bottom.
Compilation to Bytecode: The source code is then compiled to an intermediate format known as bytecode. Bytecode is a low-level platform-independent representation of your code, and its purpose is to optimize code execution. This step is bypassed the next time the interpreter runs your code if the bytecode is already available.
Execution: Finally, the bytecode is sent off for execution. At this point, the Python Virtual Machine (PVM) comes into play. The PVM is the runtime engine of Python and is responsible for executing the instructions of your bytecode one by one. It's not an isolated component but a part of the Python system you've installed on your machine.
This entire process is known as the Python Execution Model. It's important to note that this description corresponds to the core implementation of the language, that is, CPython. Other Python implementations may have slightly different execution models.
Additionally, Python provides several ways to execute external programs or system commands from within a Python script. The os.system()
, subprocess.run)
, and subprocess.Popen()
functions are commonly used for this purpose. For example, to execute a shell command like 'echo Hello, World!', you could use:
import os
os.system("echo Hello, World!")
Or using the subprocess
module:
import subprocess
subprocess.run(["echo", "Hello, World!"], shell=True)
These functions allow your Python script to interact with other programs and the operating system, providing a lot of flexibility in what you can do with Python.
Variables
In Python, variables are like containers that hold values. They're essential for storing, manipulating, and referencing data throughout a program. Variables in Python are created the moment you assign a value to them. You can think of them as labels attached to values, making it easy to keep track of different pieces of information in your program. For instance, if you were writing a program to keep track of your expenses, you might use variables to store the amount of money you spent on each purchase. This would be much easier than remembering all the different values yourself.
Here's an example of how to create a variable in Python:
name = "Your name"
In this example, name
is the variable, and "Your name" is the value assigned to it. You can then use this variable in your code, like so:
print(name) # This will output: Your name
It's important to note that Python is dynamically typed, meaning you don't have to declare the type of a variable when you create it. The Python interpreter infers the type based on the value you assign to the variable. This makes Python flexible and easy to use, especially for beginners.
Python variables work by holding references to objects. In Python, everything is an object, even the most basic data types like integers and strings. This means that when you create a variable and assign a value to it, Python stores that value in memory and the variable name is a reference to that memory location. When you access the variable, Python retrieves the value from the memory location.
Variables in Python can hold different types of data, including numbers, strings, lists, and dictionaries. The value of a variable can be changed during program execution. Here's an example:
# Assigning a value to the variable
message = "Hello, World!"
print(message) # This will output: Hello, World!
# Changing the value of the variable
message = "Hello, Python!"
print(message) # This will output: Hello, Python!
In this example, we first assign the string "Hello, World!" to the variable message
, and then change the value to "Hello, Python!".
In conclusion, variables in Python are fundamental for storing and manipulating data in a program. They're easy to create and use, making Python an accessible language for beginners. By understanding how to create and use variables, you can write more efficient and effective Python programs.
Datatypes
Python has several built-in data types that allow you to store, manipulate, and organize data in your programs. Here's an overview of some of the most commonly used data types:
Integers: These are whole numbers, without a decimal point. They can be positive or negative. For example, 5
, -3
, and 0
are integers. Python's integer type is int
.
x = 10
print(type(x)) # This will output: <class 'int'>
Floats: These are real numbers that contain a decimal point. For example, 3.14
, -0.01
, and 1.0
are floats. Python's float type is float
.
y = 3.14
print(type(y)) # This will output: <class 'float'>
Strings: These are sequences of characters. Strings can be created using single quotes ('
), double quotes ("
), or triple quotes ('''
or """
). For example, 'Hello'
, "World"
, and '''Hello, World!'''
are strings. Python's string type is str
.
message = "Hello, World!"
print(type(message)) # This will output: <class 'str'>
Lists: These are ordered collections of items, which can be of any type and can be a mix of different types. Lists are created using square brackets ([]
). For example, [1, 2, 3]
, ['apple', 'banana', 'cherry']
, and [1, 'apple', 3.14]
are lists. Python's list type is list
.
fruits = ['apple', 'banana', 'cherry']
print(type(fruits)) # This will output: <class 'list'>
Tuples: These are similar to lists, but are immutable, meaning their values cannot be changed once they are created. Tuples are created using parentheses (()
). For example, (1, 2, 3)
and ('apple', 'banana', 'cherry')
are tuples. Python's tuple type is tuple
.
coordinates = (10.0, 20.0)
print(type(coordinates)) # This will output: <class 'tuple'>
Dictionaries: These are collections of key-value pairs. Each key is unique and the values can be of any type. Dictionaries are created using curly braces ({}
). For example, {'name': 'Atharv', 'age': 20}
is a dictionary. Python's dictionary type is dict
.
person = {'name': 'Atharv', 'age': 20}
print(type(person)) # This will output: <class 'dict'>
Understanding these data types is fundamental to programming in Python, as they form the basis for storing and manipulating data in your programs.
Naming Conventions
Python has its own set of rules and conventions for naming different entities in your code such as variables, functions, classes, and more. Adherence to these conventions makes your code more readable and maintainable. These conventions are outlined in the Python Enhancement Proposal (PEP) 8, which is the official style guide for Python code. Here are some of the key naming conventions:
Variables and Functions
- Variable and function names should be lowercase, with words separated by underscores to improve readability. This is also known as
snake_case
. For instance,student_name
,calculate_sum
.
def calculate_sum(a, b):
return a + b
student_name = "Atharv Sankpal"
Classes
- Class names should use the
PascalCase
convention, also known asUpperCamelCase
. This means that the name starts with an uppercase letter and has no underscores between words. Each word in the name should also start with an uppercase letter. For example,ShoppingCart
.
class ShoppingCart:
def __init__(self, items=[]):
self.items = items
Constants
- Constants are usually defined on a module level and written in all capital letters with underscores separating words. For example,
MAX_OVERFLOW
.
MAX_OVERFLOW = 100
Private Instance Variables
- If the instance variable is intended to be used internally within a class, it can be named with a single underscore prefix, like
_my_variable
.
Avoiding Name Clashes
- To avoid name clashes with Python's built-in names, a single trailing underscore can be used, like
class_
.
Strongly Private Instance Variables
- If an instance variable should not be accessed directly, it can be named with a double underscore prefix, like
__my_variable
. This will mangle the attribute name and make it harder to access unintentionally.
Magic Methods
- Python has several special methods or attributes that have a double underscore prefix and suffix, like
__init__
,__len__
, etc. These are known as magic methods and are used to implement certain behaviors in classes.
It's important to note that these are conventions, not hard and fast rules. Python does not enforce these naming conventions, but following them can make your code more readable and Pythonic.
Indentation in python
Indentation in Python is not just a stylistic choice, but a fundamental part of the language's syntax. Unlike many other programming languages that use braces or other markers to define blocks of code (like loops or if-else statements), Python uses indentation to denote these blocks.
A block of code is a group of statements that are meant to be executed together. In Python, consistent indentation is used to define the beginning and end of these blocks. For example, in an if
statement, all the code that is indented under the if
line belongs to that if
block:
if 5 > 2:
print("Five is greater than two!")
In this example, the print
statement is part of the if
block because it's indented under the if
line.
If you don't indent your Python code correctly, you'll encounter IndentationError
and your code won't run. This is because the Python interpreter uses indentation to determine how statements are grouped together.
Apart from being a syntax requirement, proper indentation provides several benefits:
Readability: Indentation improves the readability of your code by making the structure of the code visually clear. This makes it easier for others (and yourself) to understand your code.
Reduced Errors: By enforcing consistent indentation, Python reduces the likelihood of errors that can occur due to misplaced or mismatched block delimiters (like braces) that are used in other languages.
Efficiency: Python's use of indentation simplifies the coding process. You don't have to worry about placing and matching block delimiters, and you can focus on the logic of your code.
Code Maintenance: Well-indented code is easier to maintain and update. When the structure of the code is clear, it's easier to make changes without breaking the code.
Collaboration: Consistent use of indentation makes it easier for teams to work together. When everyone uses the same coding style, it's easier to understand and work with each other's code.
In conclusion, indentation is a critical aspect of Python programming. It's essential for defining the structure of your code, and it provides several benefits in terms of readability, error reduction, and code maintenance. Whether you're a beginner or an experienced Python programmer, understanding and applying proper indentation is key to writing effective Python code.
Basic Operations
To perform basic arithmetic operations in Python, you can use the standard arithmetic operators:
Addition:
+
Subtraction:
-
Multiplication:
*
Division:
/
Modulo (remainder):
%
Exponentiation:
**
Here are some examples:
# Addition
result = 5 + 3
print(result) # Output: 8
# Subtraction
result = 10 - 2
print(result) # Output: 8
# Multiplication
result = 4 * 5
print(result) # Output: 20
# Division
result = 15 / 3
print(result) # Output: 5.0
# Modulo
result = 17 % 4
print(result) # Output: 1
# Exponentiation
result = 2 ** 3
print(result) # Output: 8
String Manipulation
To perform string manipulations in Python, you can use various built-in methods and operators. Here are some common operations:
- Concatenation: You can concatenate two or more strings using the
+
operator. For example:
greeting = "Hello"
name = "Sanmesh"
message = greeting + " " + name
print(message) # Output: Hello Sanmesh
- String Length: You can get the length of a string using the
len()
function. For example:
text = "Hello, world!"
length = len(text)
print(length) # Output: 13
- Accessing Characters: You can access individual characters in a string using indexing. Python uses zero-based indexing, so the first character is at index 0. For example:
text = "Hello"
first_char = text[0]
last_char = text[-1]
print(first_char) # Output: H
print(last_char) # Output: o
- Slicing: You can extract a substring from a string using slicing. Slicing allows you to specify a range of indices to extract a portion of the string. For example:
text = "Hello, world!"
substring = text[7:12]
print(substring) # Output: world
- String Methods: Python provides several built-in string methods for common manipulations, such as converting case, replacing characters, splitting, and joining strings. Here are a few examples:
text = "Hello, world!"
# Convert to uppercase
uppercase_text = text.upper()
print(uppercase_text) # Output: HELLO, WORLD!
# Replace characters
replaced_text = text.replace("world", "Python")
print(replaced_text) # Output: Hello, Python!
# Split into a list of words
words = text.split(", ")
print(words) # Output: ['Hello', 'world!']
# Join a list of words into a single string
joined_text = "-".join(words)
print(joined_text) # Output: Hello-world!
str.format()
Certainly! In Python, the str.format()
method is a powerful way to create and format strings. It allows you to embed variables and expressions inside strings while controlling the formatting of those values. This method provides more flexibility and readability compared to traditional string concatenation. Let's dive into the details of str.format()
.
Basic Usage:
The str.format()
method is called on a string and uses curly braces {}
as placeholders for values that you want to insert into the string. You can use the placeholders within the string and then provide the values to be inserted using the format()
method.
Here's the basic syntax:
formatted_string = "Hello, {}!".format(value)
In this example, {}
is a placeholder where the value
will be inserted.
Positional Arguments:
You can use positional arguments to specify the values to be inserted into the placeholders. The values are provided as arguments to the format()
method, and they are inserted into the placeholders in the order they appear.
name = "Prajwal"
greeting = "Hello, {}!".format(name)
print(greeting) # Output: "Hello, Prajwal!"
In this case, the name
variable is inserted into the {}
placeholder.
Named Arguments:
You can also use named arguments to specify which values should go into which placeholders. This allows for more clarity, especially when dealing with multiple placeholders.
first_name = "Prathamesh"
last_name = "Hegade"
full_name = "My name is {first} {last}.".format(first=first_name, last=last_name)
print(full_name) # Output: "My name is Prathamesh Hegade."
Here, the placeholders {first}
and {last}
are filled with the values specified using named arguments.
Value Formatting:
You can apply various formatting options to the inserted values using format specifiers. Format specifiers start with a colon :
and can include options like precision, width, alignment, and data type formatting.
price = 19.99
formatted_price = "The price is: ${:.2f}".format(price)
print(formatted_price) # Output: "The price is: $19.99"
In this example, :.2f
is a format specifier that formats the price
variable as a floating-point number with 2 decimal places.
Accessing Variables by Index:
You can also access variables by index within the str.format()
method, allowing you to reuse values multiple times within the string.
item = "apple"
quantity = 5
message = "I bought {1} {0}s today and ate {1} of them.".format(item, quantity)
print(message) # Output: "I bought 5 apples today and ate 5 of them."
Here, {0}
refers to the first argument (item) and {1}
refers to the second argument (quantity).
F-strings (Python 3.6+):
Starting from Python 3.6, a more concise and readable way to format strings is by using f-strings. F-strings allow you to embed expressions directly inside string literals by prefixing the string with the letter 'f'. The expressions inside curly braces are evaluated at runtime.
name = "Sanket"
greeting = f"Hello, {name}!"
print(greeting) # Output: "Hello, Sanket!"
F-strings provide a more intuitive and less error-prone way to format strings.
Summary:
The str.format()
method in Python is a versatile and powerful tool for string formatting. It allows you to create complex strings with placeholders and format the inserted values as needed. Whether you're working with positional or named arguments, applying format specifiers, or using f-strings, string formatting in Python offers flexibility and readability for your code.
TypeCasting
Typecasting, also known as type conversion, is the process of converting a value from one data type to another in a programming language. In Python, you can perform typecasting using various built-in functions or constructors for specific data types. Here's an overview of common typecasting methods in Python:
1. Implicit Typecasting (Type Coercion):
In Python, some data type conversions happen automatically during operations. This is known as implicit typecasting or type coercion. For example, when you perform arithmetic operations with different data types, Python will implicitly convert them to a common data type:
num_int = 5
num_float = 2.5
result = num_int + num_float # Implicitly converts num_int to a float
print(result) # Output: 7.5
2. Explicit Typecasting:
Explicit typecasting, also called explicit conversion, requires you to specify the desired data type explicitly. Python provides built-in functions and constructors for this purpose:
a. int()
, float()
, str()
, bool()
:
These functions allow you to explicitly convert values to integers, floating-point numbers, strings, or Boolean values, respectively:
# Explicitly converting to integer
num_str = "10"
num_int = int(num_str)
# Explicitly converting to float
num_str = "3.14"
num_float = float(num_str)
# Explicitly converting to string
num_int = 42
num_str = str(num_int)
# Explicitly converting to Boolean
zero = 0
non_zero = 42
bool_zero = bool(zero) # False
bool_non_zero = bool(non_zero) # True
b. list()
, tuple()
, set()
:
You can convert other iterable data types, like strings or lists, to lists, tuples, or sets using these constructors:
# Convert a string to a list of characters
text = "Hello"
char_list = list(text) # ['H', 'e', 'l', 'l', 'o']
# Convert a list to a tuple
my_list = [1, 2, 3]
my_tuple = tuple(my_list) # (1, 2, 3)
# Convert a list to a set
my_list = [1, 2, 2, 3]
my_set = set(my_list) # {1, 2, 3}
c. dict()
:
You can create a dictionary from a list of key-value pairs using the dict()
constructor:
pairs = [("a", 1), ("b", 2), ("c", 3)]
my_dict = dict(pairs) # {'a': 1, 'b': 2, 'c': 3}
User Input
Taking user input in Python can be done using the built-in input()
function. This function allows your program to pause and wait for the user to enter data through the keyboard. Here's a detailed explanation of how to use input()
:
Basic Input:
To take user input, simply call the
input()
function. It will display a prompt (if provided) and wait for the user to enter text, followed by pressing the "Enter" key.user_input = input("Enter your name: ") print("Hello, " + user_input)
In this example, the program prompts the user to enter their name, stores the input in the
user_input
variable, and then prints a greeting.Type Conversion:
By default,
input()
returns user input as a string. If you expect a different data type, like an integer or a floating-point number, you need to convert the input using type casting.age = int(input("Enter your age: ")) # Convert input to an integer
If the user enters non-integer text, this code will raise a
ValueError
exception. To handle such cases, consider using exception handling.Handling User Input:
It's a good practice to handle user input carefully. You may want to validate and sanitize the input to ensure it meets your program's requirements. For instance, you can check if the input is within a certain range or conforms to a specific format.
while True: try: age = int(input("Enter your age: ")) if age < 0: print("Age must be a positive number.") else: break # Exit the loop if input is valid except ValueError: print("Invalid input. Please enter a valid number.")
In this example, the program keeps asking for input until the user enters a valid positive integer.
Using Default Values:
You can provide a default value to the
input()
function, which will be displayed as a prompt to the user. If the user presses "Enter" without typing anything, the default value will be used.name = input("Enter your name [Guest]: ") or "Guest"
If the user enters their name, that input is stored in the
name
variable. If they press "Enter" without entering anything, "Guest" will be used as the default.Security Considerations:
Be cautious when using
input()
for sensitive information like passwords. User input is often echoed back to the screen, which can expose sensitive data. In such cases, it's better to use specialized libraries likegetpass
for secure input.import getpass password = getpass.getpass("Enter your password: ")
This will hide the password input while the user types it.
Remember that input()
is primarily for simple text-based input. If you need more advanced input handling, graphical user interfaces (GUIs) or web forms may be more appropriate for your application.
Control Flow
Conditional statements, often referred to as "if-else statements," are a fundamental concept in programming that allow you to control the flow of your code based on certain conditions. These conditions are evaluated as either true or false, and the program takes different actions depending on the outcome. In Python, conditional statements are implemented using the if
, elif
(short for "else if"), and else
keywords. Let's delve into the details of how conditional statements work in Python.
Basic Syntax:
The basic syntax of an if
statement in Python is as follows:
if condition:
# Code to be executed if the condition is true
The condition
in the if
statement is an expression that results in a boolean value (True
or False
). If the condition is True
, the indented block of code following the if
statement will be executed. If the condition is False
, that block of code is skipped.
Example:
x = 10
if x > 5:
print("x is greater than 5")
In this example, since the condition x > 5
is true (because x
is 10, which is indeed greater than 5), the statement inside the if
block is executed, and "x is greater than 5" will be printed.
The else
Statement:
Sometimes, you may want to execute a different block of code when the condition is False
. This is where the else
statement comes into play. The else
statement is used to define a block of code that should be executed when the condition in the if
statement is False
.
Here's the syntax:
if condition:
# Code to be executed if the condition is true
else:
# Code to be executed if the condition is false
Example:
x = 3
if x > 5:
print("x is greater than 5")
else:
print("x is not greater than 5")
In this case, since x
is 3, the condition x > 5
is False
, so the code in the else
block is executed, and "x is not greater than 5" is printed.
The elif
Statement:
Sometimes, you need to check multiple conditions and take different actions based on which condition is true. You can achieve this using the elif
statement (short for "else if"). You can have multiple elif
statements following an if
statement.
Here's the syntax:
if condition1:
# Code to be executed if condition1 is true
elif condition2:
# Code to be executed if condition2 is true
elif condition3:
# Code to be executed if condition3 is true
# ...
else:
# Code to be executed if none of the conditions are true
The conditions are evaluated in order, and the first one that is True
triggers the execution of the corresponding block of code. If none of the conditions are True
, the code in the else
block is executed.
Example:
x = 7
if x < 5:
print("x is less than 5")
elif x < 10:
print("x is less than 10 but not less than 5")
else:
print("x is 10 or greater")
In this example, because x
is 7, the first condition x < 5
is False
, but the second condition x < 10
is True
. Therefore, "x is less than 10 but not less than 5" will be printed.
Nested Conditional Statements:
You can also nest conditional statements inside each other to create more complex logic. This involves placing one if
statement inside another. Here's an example:
x = 5
y = 10
if x == 5:
if y == 10:
print("x is 5 and y is 10")
else:
print("x is 5, but y is not 10")
else:
print("x is not 5")
In this nested example, the inner if
statement is only evaluated if the outer if
condition is True
. This allows you to create more intricate branching in your code.
Logical Operators:
To create more complex conditions, you can use logical operators like and
, or
, and not
to combine or negate conditions.
and
: ReturnsTrue
if both conditions areTrue
.or
: ReturnsTrue
if at least one of the conditions isTrue
.not
: Negates the condition.
Here's an example using logical operators:
x = 7
if x > 5 and x < 10:
print("x is greater than 5 and less than 10")
In this case, both conditions (x > 5
and x < 10
) must be True
for the print
statement to execute.
Summary:
Conditional statements (if-else statements) are essential for controlling the flow of your Python code based on different conditions. You can use if
for the primary condition, elif
for additional conditions, and else
to handle the case when none of the conditions are met. Logical operators can be used to create more complex conditions, and you can nest conditional statements to handle intricate branching logic in your programs.
Loops
In Python, loops are control structures that allow you to repeatedly execute a block of code as long as a certain condition is met or to iterate over a sequence of data, such as a list, tuple, string, or range. Loops are essential for performing repetitive tasks and processing collections of data. Python provides two primary types of loops: for
loops and while
loops.
for
Loops:
A for
loop is used for iterating over a sequence (that can be a list, tuple, string, dictionary, etc.) or other iterable objects. It allows you to execute a block of code for each item in the sequence.
The basic syntax of a for
loop is as follows:
for variable in iterable:
# Code to be executed for each item in the iterable
Here's an example of a for
loop that iterates over a list of numbers:
numbers = [1, 2, 3, 4, 5]
for num in numbers:
print(num)
In this example, the loop iterates through each item in the numbers
list and prints it.
range()
Function:
A common use of for
loops is with the range()
function to generate a sequence of numbers. The range()
function creates a sequence of numbers from a starting point (inclusive) to an ending point (exclusive) with a specified step size.
for i in range(1, 6):
print(i)
This loop will print the numbers 1 through 5.
while
Loops:
A while
loop is used for repeatedly executing a block of code as long as a certain condition is true. It is useful when you don't know in advance how many times the loop will need to run.
The basic syntax of a while
loop is as follows:
while condition:
# Code to be executed while the condition is true
Here's an example of a while
loop that counts from 1 to 5:
count = 1
while count <= 5:
print(count)
count += 1
In this example, the loop continues executing as long as the condition count <= 5
is true.
Loop Control Statements:
break
Statement:
The break
statement is used to exit a loop prematurely, even if the loop condition is still true. It is often used when a specific condition is met, and you want to terminate the loop.
numbers = [1, 2, 3, 4, 5]
for num in numbers:
if num == 3:
break
print(num)
In this example, the loop will terminate when num
becomes equal to 3.
continue
Statement:
The continue
statement is used to skip the current iteration of a loop and move to the next iteration. It is useful when you want to skip certain items or conditions within a loop.
numbers = [1, 2, 3, 4, 5]
for num in numbers:
if num == 3:
continue
print(num)
In this example, when num
is equal to 3, the continue
statement is encountered, and the loop proceeds to the next iteration, skipping the print
statement.
Looping Through Data Structures:
Loops are particularly powerful when it comes to working with data structures like lists and dictionaries. You can iterate through the elements of these data structures to perform various operations.
Iterating Through Lists:
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
print(fruit)
This loop will print each fruit in the fruits
list.
Iterating Through Dictionaries:
person = {"name": "Atharv", "age": 20, "city": "Kolhapur"}
for key, value in person.items():
print(f"{key}: {value}")
This loop will print each key-value pair in the person
dictionary.
Summary:
Loops are fundamental to programming and are essential for iterating over data, performing repetitive tasks, and controlling program flow. for
loops are typically used when you know the number of iterations in advance, while while
loops are useful when you need to loop until a specific condition is met. Loop control statements like break
and continue
provide additional control over the loop's behavior. As a beginner, practicing loops is crucial for mastering the basics of Python programming and building more complex programs.
Function
In Python, functions are reusable blocks of code that perform a specific task. They are essential for organizing and modularizing your code, making it more readable, maintainable, and easier to debug. Functions help you avoid code duplication by allowing you to encapsulate a piece of functionality and call it whenever needed. Let's dive into the details of defining and using functions in Python:
Defining a Function:
You define a function in Python using the def
keyword, followed by the function name and a pair of parentheses ()
. Optionally, you can include one or more parameters (input values) inside the parentheses. The function's code block is indented and follows the colon :
at the end of the function declaration.
Here's the basic syntax:
def function_name(parameter1, parameter2, ...):
# Function code
# ...
return result # Optional return statement
function_name
: This is the name of the function. Choose a descriptive name that reflects the function's purpose. Function names in Python should follow naming conventions (e.g., lowercase with words separated by underscores, likecalculate_average
).parameters
: These are optional. Parameters are placeholders for the values you want to pass into the function. They are like variables that store the values you pass when you call the function.return
: This statement is optional. It allows the function to return a result to the caller. If omitted, the function returnsNone
.
Example Function Without Parameters:
def greet():
print("Hello, world!")
# Call the function
greet()
In this example, greet
is a simple function that prints "Hello, world!" when called.
Example Function with Parameters and a Return Value:
def add_numbers(a, b):
result = a + b
return result
# Call the function
sum_result = add_numbers(3, 4)
print("The sum is:", sum_result)
In this example, add_numbers
is a function that takes two parameters, a
and b
, adds them together, and returns the result. When calling the function with add_numbers(3, 4)
, it returns 7
, which is stored in the sum_result
variable and then printed.
Function Documentation (Docstrings):
It's good practice to document your functions using docstrings. A docstring is a multi-line string that provides a brief description of the function's purpose, its parameters, and what it returns. You can access a function's docstring using the help()
function or by using the .__doc__
attribute.
def calculate_average(numbers):
"""
Calculate the average of a list of numbers.
Parameters:
- numbers (list): A list of numeric values.
Returns:
- float: The average of the numbers.
"""
if not numbers:
return 0 # Avoid division by zero
total = sum(numbers)
return total / len(numbers)
# Access the docstring
help(calculate_average)
Calling a Function:
To call a function, you simply use its name followed by parentheses, passing any required arguments (values) inside the parentheses.
result = calculate_average([10, 20, 30])
print("The average is:", result)
In this example, we call the calculate_average
function with a list of numbers [10, 20, 30]
, and the result is printed.
Default Argument Values:
You can provide default values for function parameters, which allows you to call the function without providing those arguments. This is useful when you want to make certain arguments optional.
def greet(name="Guest"):
print(f"Hello, {name}!")
greet() # Prints "Hello, Guest!"
greet("Shiva") # Prints "Hello, Shiva!"
In this example, the name
parameter has a default value of "Guest." If you don't provide a name when calling greet()
, it uses the default value.
Function Scope:
Variables defined inside a function are local to that function by default. They are only accessible within the function's code block. Variables defined outside of functions are called global variables and can be accessed both inside and outside functions.
global_variable = 10
def my_function():
local_variable = 5
print("Local variable:", local_variable)
print("Global variable:", global_variable)
my_function()
print("Global variable outside function:", global_variable)
# This will result in an error
# print("Local variable outside function:", local_variable)
Returning Values from Functions:
Functions can return values using the return
statement. You can return one or multiple values from a function, separated by commas.
def add_and_subtract(a, b):
addition = a + b
subtraction = a - b
return addition, subtraction
result1, result2 = add_and_subtract(10, 5)
print("Addition:", result1)
print("Subtraction:", result2)
In this example, add_and_subtract
returns two values, which are unpacked into result1
and result2
.
Recursion
Recursion is a programming technique where a function calls itself to solve a problem or perform a task. Recursive functions break down complex problems into simpler, more manageable subproblems. Each recursive call processes a smaller part of the problem until a base case is reached, at which point the function returns results, and the recursive calls "unwind" or return to the original call, combining the results to solve the overall problem.
Here's a detailed explanation of recursion:
Components of a Recursive Function:
Base Case(s): These are one or more conditions that determine when the recursion should stop. Base cases provide the exit point for the recursive function, preventing infinite recursion.
Recursive Case(s): These are one or more instances where the function calls itself with modified inputs. Recursive cases break the problem down into smaller subproblems that are closer to the base case.
Input Transformation: In each recursive call, the function typically modifies its input or state to make progress toward the base case. This transformation ensures that the problem becomes simpler with each recursive call.
The Recursive Process:
Call Stack: When a function is called, a new frame (called an activation record or stack frame) is pushed onto the call stack, storing local variables and the return address.
Recursive Calls: The function calls itself, creating a new frame on the stack for each call. This continues until a base case is reached.
Base Case Hit: When the base case is satisfied, the function starts returning values.
Unwinding the Stack: As the function returns, the call stack "unwinds," and each frame is popped off the stack. Return values are combined or processed as frames are removed.
Example - Factorial Calculation:
A classic example of recursion is calculating the factorial of a number. The factorial of a non-negative integer n
(denoted as n!
) is the product of all positive integers from 1
to n
. For example, 5! = 5 * 4 * 3 * 2 * 1 = 120
.
Here's a recursive Python function to calculate the factorial of a number:
def factorial(n):
# Base case: if n is 0 or 1, return 1
if n == 0 or n == 1:
return 1
# Recursive case: n! = n * (n-1)!
else:
return n * factorial(n - 1)
# Example usage
result = factorial(5) # Calculates 5!
print(result) # Output: 120
In this example, the factorial
function calls itself with a smaller value (n-1
) until it reaches the base case (n == 0
or n == 1
). At that point, the recursive calls start returning, and the results are multiplied together to calculate the final factorial value.
Advantages of Recursion:
Simplicity: Recursive solutions can be simpler and more elegant than their iterative counterparts, especially for problems with a recursive structure.
Readability: Recursion can make code more readable when the problem naturally involves self-similar subproblems.
Disadvantages of Recursion:
Stack Overhead: Each recursive call consumes memory on the call stack, which can lead to a stack overflow error for deep recursion.
Performance: Recursive solutions can be less efficient than iterative ones, particularly for problems that don't naturally involve recursion.
Debugging Complexity: Debugging recursive code can be challenging due to the call stack's nested nature.
Recursion is a powerful and essential concept in programming, particularly in solving problems that exhibit recursive patterns, such as tree traversal, divide-and-conquer algorithms, and more. When used appropriately, it can lead to elegant and efficient solutions.
Summary:
Functions are a fundamental concept in Python and programming in general. They allow you to encapsulate code, make it reusable, and improve code organization. Key points to remember when defining and using functions in Python:
Use the
def
keyword to define a function.Functions can have parameters (inputs) and return values (outputs).
Document your functions using docstrings.
Call functions by their name and provide arguments as needed.
Variables defined inside functions are local by default, while variables defined outside functions are global.
By understanding and using functions effectively, you can write more structured and maintainable Python code.
Keyword arguments
Keyword arguments in Python functions allow you to pass arguments to a function by specifying the parameter names along with their values. This provides clarity and flexibility in function calls, especially when functions have multiple parameters with default values. Keyword arguments are particularly useful when you want to pass arguments out of order or only specify some of the arguments while relying on default values for the others.
Here's how you define a function that accepts keyword arguments:
def greet(name, age):
print(f"Hello, {name}! You are {age} years old.")
In this example, the greet
function accepts two parameters: name
and age
. To call this function using keyword arguments, you use the parameter names as keywords:
greet(name="Om", age=21)
greet(age=21, name="Abhijeet")
Key points to remember about keyword arguments:
Order Independence: When using keyword arguments, you can pass the arguments in any order, as long as you specify the parameter names:
greet(age=35, name="Yashraj")
Partial Argument Specification: You can specify some arguments as keyword arguments and leave others as positional arguments:
greet("Anurag", age=40)
Default Values: Keyword arguments are often used in functions with default parameter values:
def greet(name="Guest", age=0): print(f"Hello, {name}! You are {age} years old.") greet() # Using default values greet(name="Aniket") # Overriding one default value
Mixing Positional and Keyword Arguments: When calling a function, positional arguments must appear before keyword arguments:
greet("Hemant", age=69) # Valid greet(name="Paras", 22) # Invalid, positional argument follows keyword argument
Overriding Default Values: If you provide a value for a keyword argument, it will override any default value specified in the function definition:
greet("Gandhar", age=55) # Override age, use default name
Parameter Names Must Match: The keyword argument names you use in the function call must match the parameter names in the function definition:
greet(nickname="Vijay", years=27) # Invalid, parameter names don't match
Keyword arguments enhance the readability and maintainability of code, especially in functions with many parameters or when it's important to make the purpose of each argument explicit in the function call. They also provide flexibility when dealing with functions that have optional parameters with default values.
*args in functions
In Python, *args
is a special syntax used to pass a variable number of non-keyword (positional) arguments to a function. It allows you to define functions that can accept an arbitrary number of arguments without specifying them individually. The *args
parameter collects these arguments into a tuple within the function, making it easy to work with them. Let's go through an example to illustrate how *args
works:
def add_numbers(*args):
result = 0
for num in args:
result += num
return result
# Call the function with different numbers of arguments
sum1 = add_numbers(1, 2, 3)
sum2 = add_numbers(10, 20, 30, 40, 50)
print("Sum 1:", sum1)
print("Sum 2:", sum2)
In this example, we have defined a function called add_numbers
that takes *args
as its parameter. Inside the function, we initialize result
to 0 and then use a for
loop to iterate through the elements in the args
tuple, adding each element to the result
variable.
Now, let's break down how the function works with different calls:
add_numbers(1, 2, 3)
:The function is called with three arguments: 1, 2, and 3.
These arguments are collected into a tuple
args
inside the function:args = (1, 2, 3)
.The loop iterates through
args
, adding each element toresult
, resulting inresult = 1 + 2 + 3
, which is 6.The function returns 6, and it is stored in the
sum1
variable.
add_numbers(10, 20, 30, 40, 50)
:The function is called with five arguments: 10, 20, 30, 40, and 50.
These arguments are collected into a tuple
args
inside the function:args = (10, 20, 30, 40, 50)
.The loop iterates through
args
, adding each element toresult
, resulting inresult = 10 + 20 + 30 + 40 + 50
, which is 150.The function returns 150, and it is stored in the
sum2
variable.
As you can see, the *args
syntax allows the add_numbers
function to work with a varying number of arguments, making it flexible and useful for scenarios where you want to perform operations on multiple values without specifying them individually. You can pass any number of positional arguments to the function, and they will be collected into the args
tuple for further processing within the function.
**kwargs in functions
In Python, **kwargs
is a special syntax used to pass a variable number of keyword arguments to a function. It allows you to define functions that can accept an arbitrary number of keyword arguments without specifying them individually. The **kwargs
parameter collects these arguments into a dictionary within the function, making it easy to work with them. Let's go through an example using your display_info
function:
def display_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
# Call the function with different keyword arguments
display_info(name="Atharv", age=20, city="Kolhapur")
display_info(product="Laptop", price=55000, brand="HP", in_stock=True)
In this example, we have defined a function called display_info
that takes **kwargs
as its parameter. Inside the function, we use a for
loop to iterate through the items in the kwargs
dictionary, which contains the keyword arguments.
Now, let's break down how the function works with different calls:
display_info(name="Atharv", age=20, city="Kolhapur")
:The function is called with three keyword arguments:
name
,age
, andcity
.These keyword arguments are collected into a dictionary
kwargs
inside the function:kwargs = {"name": "Atharv", "age": 20, "city": "Kolhapur"}
.The loop iterates through
kwargs
, printing each key-value pair, resulting in the following output:name: Atharv age: 20 city: Kolhapur
display_info(product="Laptop", price=55000, brand="HP", in_stock=True)
:The function is called with four keyword arguments:
product
,price
,brand
, andin_stock
.These keyword arguments are collected into a dictionary
kwargs
inside the function:kwargs = {"product": "Laptop", "price": 55000, "brand": "HP", "in_stock": True}
.The loop iterates through
kwargs
, printing each key-value pair, resulting in the following output:product: Laptop price: 55000 brand: HP in_stock: True
As you can see, the **kwargs
syntax allows the display_info
function to work with a varying number of keyword arguments, making it flexible and useful for scenarios where you want to display information provided as keyword-value pairs. You can pass any number of keyword arguments to the function, and they will be collected into the kwargs
dictionary for further processing within the function.
Exception Handling
Exception handling is a critical aspect of programming in Python and many other programming languages. It allows you to gracefully handle and recover from unexpected errors or exceptions that may occur during the execution of your code. Exception handling ensures that your program does not crash abruptly when an error occurs, making your code more robust and user-friendly.
In Python, exceptions are raised when an error occurs during program execution. Exceptions can be raised by the Python interpreter or explicitly by your code. Python provides a structured way to handle exceptions using the try
, except
, else
, and finally
blocks.
Basic Exception Handling with try
and except
:
The basic syntax of exception handling in Python involves the use of the try
and except
blocks:
try:
# Code that may raise an exception
except ExceptionType as e:
# Code to handle the exception
try
: This block contains the code that might raise an exception.except
: If an exception of typeExceptionType
is raised in thetry
block, the code inside theexcept
block will be executed to handle the exception.
Here's an example:
try:
x = 10 / 0 # This will raise a ZeroDivisionError
except ZeroDivisionError as e:
print("Error:", e)
In this example, a ZeroDivisionError
exception is raised when trying to divide by zero. The code inside the except
block is executed, printing an error message.
Handling Multiple Exceptions:
You can handle multiple exceptions by specifying multiple except
blocks, each with a different exception type.
try:
value = int("abc") # This will raise a ValueError
except ValueError as ve:
print("ValueError:", ve)
except ZeroDivisionError as ze:
print("ZeroDivisionError:", ze)
In this example, we handle both ValueError
and ZeroDivisionError
separately.
Handling All Exceptions:
You can also use a generic except
block without specifying a particular exception type. However, this is generally discouraged because it can catch unexpected errors that you might not be aware of.
try:
result = 10 / 0 # This will raise a ZeroDivisionError
except Exception as e:
print("An error occurred:", e)
The else
Block:
You can include an else
block to execute code when no exceptions are raised within the try
block.
try:
x = 10 / 2
except ZeroDivisionError as e:
print("Error:", e)
else:
print("No exceptions were raised. Result:", x)
In this example, since no exceptions were raised, the code inside the else
block is executed.
The finally
Block:
The finally
block is used to specify code that must be executed regardless of whether an exception was raised or not. It's often used for cleanup operations like closing files or releasing resources.
try:
file = open("example.txt", "r")
content = file.read()
except FileNotFoundError:
print("File not found.")
else:
print("File contents:", content)
finally:
if file:
file.close()
In this example, the finally
block ensures that the file is closed regardless of whether an exception was raised or not.
Raising Exceptions:
You can explicitly raise exceptions using the raise
statement. This is useful when you want to signal an error condition in your code.
def divide(a, b):
if b == 0:
raise ZeroDivisionError("Cannot divide by zero.")
return a / b
try:
result = divide(10, 0)
except ZeroDivisionError as e:
print("Error:", e)
In this example, the divide
function raises a ZeroDivisionError
if the second argument is zero.
Custom Exceptions:
You can create your own custom exception classes by inheriting from the built-in Exception
class or one of its subclasses. This allows you to define your own error types for your specific application.
class MyCustomError(Exception):
pass
try:
raise MyCustomError("This is a custom error.")
except MyCustomError as e:
print("Custom Error:", e)
In this example, we define a custom exception class MyCustomError
and then raise an instance of it.
Exception Chaining (Python 3.3+):
Python 3.3 introduced exception chaining, allowing you to capture and re-raise an exception while preserving the original exception's traceback.
try:
x = 10 / 0
except ZeroDivisionError as e1:
raise ValueError("An error occurred") from e1
In this example, a ValueError
exception is raised with the original ZeroDivisionError
as its cause.
Handling Exceptions in a Function:
When writing functions, it's often a good practice to handle exceptions within the function and provide meaningful error messages or return values.
def divide(a, b):
try:
result = a / b
except ZeroDivisionError:
return "Division by zero is not allowed."
return result
This function handles the ZeroDivisionError
exception and returns a meaningful message instead of crashing the program.
Summary:
Exception handling in Python is crucial for dealing with unexpected errors and ensuring that your code remains robust and user-friendly. It allows you to gracefully handle errors, provide meaningful error messages, and perform cleanup operations. By using try
, except
, else
, and finally
blocks, along with raising custom exceptions, you can create code that is more resilient to errors and easier to debug.
File Handling
File handling in Python is a fundamental operation that allows you to read from and write to files on your computer. Python provides built-in functions and methods for file input and output, making it easy to work with text and binary files. Here's an overview of file handling in Python:
Opening and Closing Files:
Before you can read from or write to a file, you need to open it using the open()
function. The open()
function takes two arguments: the file's path and the mode in which you want to open it (read, write, append, etc.). After you're done with a file, it's essential to close it using the close()
method to free up system resources and ensure that changes are saved.
# Opening a file for reading
file = open("example.txt", "r")
# Reading the file
content = file.read()
print(content)
# Closing the file
file.close()
Reading from Files:
There are several methods for reading from files:
read()
: Reads the entire file's content as a single string.readline()
: Reads one line from the file at a time.readlines()
: Reads all lines from the file and returns them as a list of strings.
Here's an example using readlines()
:
file = open("example.txt", "r")
lines = file.readlines()
file.close()
for line in lines:
print(line.strip()) # Strips newline characters
Writing to Files:
To write to a file, you need to open it in write mode ("w"). Be careful when opening a file in write mode, as it will overwrite the existing content if the file already exists. To append content to an existing file, open it in append mode ("a").
# Writing to a file
file = open("output.txt", "w")
file.write("Hello, world!")
file.close()
# Appending to a file
file = open("output.txt", "a")
file.write("\nAppending more text.")
file.close()
Using with
Statements (Context Managers):
A better way to handle files is by using with
statements, which act as context managers. They automatically close the file when you're done with it, even if an exception occurs.
with open("example.txt", "r") as file:
content = file.read()
# Work with the file content
# The file is automatically closed outside the 'with' block
Binary File Handling:
In addition to text files, Python can handle binary files. When working with binary files, open the file in binary mode ("rb" for reading, "wb" for writing, "ab" for appending, etc.).
# Reading a binary file
with open("image.png", "rb") as binary_file:
binary_data = binary_file.read()
# Process binary data
# Writing to a binary file
with open("output.bin", "wb") as binary_file:
binary_file.write(b"\x48\x65\x6c\x6c\x6f") # Binary data
Exception Handling for File Operations:
File operations, like opening and closing files, can raise exceptions. It's essential to handle exceptions to ensure that your code behaves correctly, especially when dealing with file I/O.
try:
with open("example.txt", "r") as file:
content = file.read()
except FileNotFoundError:
print("File not found.")
except IOError as e:
print("An error occurred:", e)
Copy in file handling
In file handling, "copy" refers to the process of duplicating the content of one file and creating a new file with that copied content. Python provides several methods to copy the content of a file, allowing you to duplicate text or binary data from one file to another. Here, we'll explore two common approaches for copying files in Python:
1. Copying Text Files:
To copy the content of one text file to another, you can open both files and read from the source file while writing to the destination file. Here's a basic example:
# Open the source file in read mode
with open("source.txt", "r") as source_file:
# Read the content of the source file
content = source_file.read()
# Open the destination file in write mode
with open("destination.txt", "w") as destination_file:
# Write the content to the destination file
destination_file.write(content)
In this example, we first open the source file ("source.txt") in read mode and read its content. Then, we open the destination file ("destination.txt") in write mode and write the content to it.
2. Copying Binary Files:
To copy binary files, such as images or executables, you can use binary mode ("rb" for reading and "wb" for writing). Here's an example of copying a binary file:
# Open the source binary file in read mode
with open("source.bin", "rb") as source_file:
# Read binary data from the source file
binary_data = source_file.read()
# Open the destination binary file in write mode
with open("destination.bin", "wb") as destination_file:
# Write the binary data to the destination file
destination_file.write(binary_data)
In this example, we open the source binary file ("source.bin") in read mode and read its binary data. Then, we open the destination binary file ("destination.bin") in write mode and write the binary data to it.
Using shutil
for File Copying:
Python's shutil
module provides a convenient way to copy files and directories. You can use the shutil.copy()
function to copy a file. It handles various file operations and ensures that file attributes and permissions are preserved.
import shutil
# Copy a file from source to destination
shutil.copy("source.txt", "destination.txt")
The shutil.copy()
function is a high-level approach that simplifies file copying tasks and is often recommended when working with files.
Handling File Existence:
When copying files, you may need to handle cases where the destination file already exists. Depending on your requirements, you can either overwrite the existing file or raise an exception if it exists. Python's shutil
module provides functions like shutil.copy()
and shutil.copy2()
that offer options for handling file existence.
import shutil
# Copy a file, overwriting the destination if it already exists
shutil.copy("source.txt", "destination.txt")
# Copy a file, preserving metadata, and raising an error if the destination exists
shutil.copy2("source.txt", "destination.txt")
File and Folder removal
In Python, you can delete files and folders using various methods and modules. Two commonly used modules for this purpose are os
and shutil
. Here's how you can delete files and folders using these modules:
Deleting Files:
To delete a file in Python, you can use the os.remove()
function or os.unlink()
function (both functions are essentially the same).
import os
# Specify the file path
file_path = "file_to_delete.txt"
try:
# Attempt to delete the file
os.remove(file_path)
print(f"{file_path} has been deleted.")
except FileNotFoundError:
print(f"{file_path} not found.")
except Exception as e:
print(f"An error occurred while deleting {file_path}: {e}")
In this example, we try to delete a file named "file_to_delete.txt" using os.remove()
. If the file exists, it will be deleted, and if it doesn't exist, a FileNotFoundError
will be caught.
Deleting Folders (Directories):
To delete a folder (directory) in Python, you can use the os.rmdir()
function to delete an empty directory or the shutil.rmtree()
function to delete a directory and its contents (including subdirectories and files).
Deleting an Empty Directory:
import os
# Specify the empty directory path
directory_path = "empty_directory_to_delete"
try:
# Attempt to delete the empty directory
os.rmdir(directory_path)
print(f"{directory_path} has been deleted.")
except FileNotFoundError:
print(f"{directory_path} not found.")
except Exception as e:
print(f"An error occurred while deleting {directory_path}: {e}")
In this example, we try to delete an empty directory named "empty_directory_to_delete" using os.rmdir()
.
Deleting a Directory and Its Contents:
import shutil
# Specify the directory path to delete
directory_path = "directory_to_delete"
try:
# Attempt to delete the directory and its contents
shutil.rmtree(directory_path)
print(f"{directory_path} has been deleted, along with its contents.")
except FileNotFoundError:
print(f"{directory_path} not found.")
except Exception as e:
print(f"An error occurred while deleting {directory_path}: {e}")
In this example, we use shutil.rmtree()
to delete a directory named "directory_to_delete" and all its contents. This is a more comprehensive way to remove directories, as it handles subdirectories and files within the specified directory.
Remember to be cautious when deleting files and folders, especially when using shutil.rmtree()
, as it irreversibly removes data. Ensure that you have appropriate backups and confirm that you want to delete the data before executing the deletion operation in your code.
Modules
In Python, a module is a file containing Python code. The code can include variables, functions, and classes. Modules are used to organize and structure Python code into reusable components. They facilitate code management, promote code reusability, and enhance maintainability. In this detailed explanation of Python modules, we will cover various aspects:
Creating and Importing Modules:
To create a module, you simply write Python code in a .py
file. For example, if you create a file named my_module.py
with the following content:
# my_module.py
def greet(name):
return f"Hello, {name}!"
person = {
"name": "Atharv",
"age": 20
}
You can import and use this module in another Python script:
import my_module
message = my_module.greet(my_module.person["name"])
print(message)
Here, we import the my_module
module using the import
statement and use its functions and variables.
Module Search Path:
Python has a predefined search path to locate modules. When you import a module, Python searches for it in the following order:
The current directory.
Directories defined in the
PYTHONPATH
environment variable.Standard library directories.
Third-party library directories.
The
sys.path
list, which can be modified at runtime.
You can view the list of directories in the module search path by importing the sys
module and printing sys.path
.
Module Aliases:
You can give a module a shorter alias when importing it to make the code more concise:
import my_module as mm
message = mm.greet(mm.person["name"])
print(message)
Here, we import my_module
as mm
and use the alias to access its elements.
Importing Specific Elements:
Instead of importing the whole module, you can import specific functions, variables, or classes from a module:
from my_module import greet
message = greet("kaytr nav")
print(message)
This approach can make your code more readable and avoid potential naming conflicts.
Built-in Modules:
Python comes with a vast standard library that includes numerous built-in modules for various purposes. These modules provide functions and classes to perform a wide range of tasks, from working with file systems (os
and shutil
) to handling dates and times (datetime
) to performing mathematical operations (math
).
You can import and use these modules just like any other Python module.
Creating Your Own Packages:
Packages are a way to organize related modules into directories. A package is a directory containing an __init__.py
file (which can be empty) and one or more module files. You can create packages to structure your code logically and hierarchically.
For example, you can create a package called my_package
with the following structure:
my_package/
__init__.py
module1.py
module2.py
You can then import modules from the package like this:
from my_package import module1
result = module1.some_function()
The if __name__ == "__main__"
Block:
When a module is imported, all its code is executed, including function and variable definitions. To differentiate between a module being run as the main program and being imported as a module, you can use the if __name__ == "__main__":
block. The code inside this block only runs when the module is executed as the main program.
# my_module.py
def greet(name):
return f"Hello, {name}!"
if __name__ == "__main__":
print(greet("nav athvana"))
Distributing Your Modules and Packages:
You can share your Python modules and packages with others by packaging them and distributing them through the Python Package Index (PyPI). Tools like setuptools
and pip
facilitates the packaging and distribution process.
Random Module in python
The random
module in Python is a built-in module that provides functions for generating random numbers and performing random operations. It's a powerful tool for tasks such as generating random data, shuffling sequences, or simulating random events. Here are some of the key functions and features of the random
module:
Importing the random
Module:
You can import the random
module using the following import statement:
import random
Once imported, you can use the functions and methods provided by the module.
Generating Random Numbers:
random.random()
: This function returns a random floating-point number in the range [0.0, 1.0).random_number = random.random()
random.uniform(a, b)
: This function returns a random floating-point number in the range[a, b)
.random_number = random.uniform(1, 10)
random.randint(a, b)
: This function returns a random integer in the range[a, b]
, including botha
andb
.random_integer = random.randint(1, 6)
Generating Random Sequences:
random.choice(seq)
: This function returns a random element from the given sequence (list, tuple, or string).fruits = ["apple", "banana", "cherry"] random_fruit = random.choice(fruits)
random.shuffle(seq)
: This function shuffles the elements of a sequence in-place.numbers = [1, 2, 3, 4, 5] random.shuffle(numbers)
random.sample(seq, k)
: This function returns a list ofk
unique random elements from the given sequence without replacement.deck = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"] random_hand = random.sample(deck, 5)
Setting the Random Seed:
You can use the random.seed(seed)
function to set the seed for the random number generator. Setting the seed ensures that you get reproducible results when using random functions.
random.seed(42)
random_number = random.random() # This will always produce the same result
Random Integer Ranges:
If you need to generate random integers within a specific range, you can use the following functions:
random.randrange(start, stop[, step])
: This function returns a random integer chosen from the specified range[start, stop)
with an optional step.random_number = random.randrange(1, 11) # Random integer between 1 and 10 (inclusive)
Random Choices with Weights:
The random.choices(seq, weights=None, k=1)
function allows you to make random selections from a sequence with specified weights. This is useful for creating weighted random choices.
colors = ["red", "green", "blue"]
weights = [3, 1, 2] # Weights for the corresponding colors
random_color = random.choices(colors, weights=weights, k=1)
Summary:
The random
module in Python provides a wide range of functions for generating random numbers and performing random operations. It's a versatile tool that is commonly used in tasks involving simulations, games, data sampling, and more. Understanding how to use the functions in this module allows you to introduce randomness and variability into your Python programs.
Object Oriented Programming
Object-oriented programming (OOP) is a programming paradigm that uses objects and classes to structure and organize code. Python is a versatile and powerful language for implementing OOP concepts. Here, we'll provide a detailed explanation of OOP in Python, covering key concepts such as classes, objects, inheritance, encapsulation, and polymorphism.
Classes and Objects:
Classes are the blueprints or templates for creating objects. A class defines attributes (data) and methods (functions) that represent the properties and behaviors of objects. In Python, you define a class using the class
keyword.
Objects are instances of classes. They are created based on the class definition and represent real-world entities in your code. You create objects by calling the class as if it were a function.
class Dog:
def __init__(self, name, age):
self.name = name
self.age = age
def bark(self):
print(f"{self.name} says Woof!")
# Creating objects (instances) of the Dog class
dog1 = Dog("tula pahije te ghe", 3)
dog2 = Dog("hith pan", 5)
# Accessing attributes and calling methods of objects
print(dog1.name) # Output: "tula pahije te ghe"
dog2.bark() # Output: "higt pan says Woof!"
Constructors and self
:
The __init__
method is a special method called a constructor. It initializes the object's attributes when the object is created. The self
parameter refers to the object itself and is used to access its attributes and methods within the class.
Class Variables and Instance Variables:
Class Variables: These are shared among all instances of a class and are defined within the class but outside any method using the
class
keyword. Class variables are accessed using the class name.Instance Variables: These are specific to each instance and are created and accessed using
self
within methods.
class Circle:
pi = 3.14159265 # Class variable
def __init__(self, radius):
self.radius = radius # Instance variable
def area(self):
return self.pi * self.radius * self.radius
circle1 = Circle(5)
circle2 = Circle(7)
print(circle1.area()) # Output: 78.53981625
print(circle2.area()) # Output: 153.93804025
Inheritance:
Inheritance is a fundamental OOP concept that allows you to create a new class (subclass or derived class) that inherits properties and methods from an existing class (base class or parent class). Python supports single inheritance, meaning a subclass can inherit from one parent class.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
pass
class Dog(Animal):
def speak(self):
return f"{self.name} says Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name} says Meow!"
dog = Dog("kutra")
cat = Cat("manjar")
print(dog.speak()) # Output: "kutra says Woof!"
print(cat.speak()) # Output: "manjar says Meow!"
Encapsulation:
Encapsulation is the principle of hiding the internal details of an object and providing a public interface to interact with it. In Python, you can achieve encapsulation by using private and protected attributes and methods.
Private attributes/methods: Start with a double underscore (e.g.,
__private_var
).Protected attributes/methods: Start with a single underscore (e.g.,
_protected_var
).
class Student:
def __init__(self, name, roll_number):
self.name = name
self._roll_number = roll_number # Protected attribute
def get_roll_number(self):
return self._roll_number # Protected method
student = Student("Atharv",123456+)
print(student.name) # Accessing public attribute
print(student.get_roll_number()) # Accessing protected method
Polymorphism:
Polymorphism allows objects of different classes to be treated as objects of a common superclass. It promotes code reusability and flexibility. Python supports polymorphism through method overriding.
class Shape:
def area(self):
pass
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159265 * self.radius * self.radius
class Rectangle(Shape):
def __init__(self, length, width):
self.length = length
self.width = width
def area(self):
return self.length * self.width
shapes = [Circle(5), Rectangle(4, 6)]
for shape in shapes:
print(f"Area: {shape.area()}")
In this example, both Circle
and Rectangle
are subclasses of Shape
, and they override the area
method.
Types of Inheritance
Inheritance is a fundamental concept in object-oriented programming (OOP) that allows you to create new classes (subclasses or derived classes) based on existing classes (base classes or parent classes). Different types of inheritance describe how classes inherit and reuse properties and behaviors from their parent classes. In Python, you can implement various types of inheritance:
1. Single Inheritance:
Single inheritance is the simplest form of inheritance, where a subclass inherits from a single-parent class. In other words, a class can have only one immediate superclass.
class Animal:
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "Woof!"
class Cat(Animal):
def speak(self):
return "Meow!"
In this example, Dog
and Cat
are subclasses of Animal
, demonstrating single inheritance.
2. Multiple Inheritance:
Multiple inheritance allows a class to inherit from more than one parent class. In Python, you can achieve multiple inheritance by specifying multiple parent classes in the class definition.
class Bird:
def fly(self):
return "Flying"
class Fish:
def swim(self):
return "Swimming"
class FlyingFish(Bird, Fish):
pass
In this example, FlyingFish
inherits from both Bird
and Fish
, enabling instances of FlyingFish
to use methods from both parent classes.
3. Multilevel Inheritance:
In multilevel inheritance, a class inherits from another class, which, in turn, inherits from another class. This creates a chain of inheritance.
class Grandparent:
def method1(self):
pass
class Parent(Grandparent):
def method2(self):
pass
class Child(Parent):
def method3(self):
pass
Here, Child
inherits from Parent
, which, in turn, inherits from Grandparent
, creating a multilevel inheritance relationship.
4. Hierarchical Inheritance:
In hierarchical inheritance, multiple subclasses inherit from a single parent class. Each subclass can have its own methods and attributes while sharing common features from the parent class.
class Shape:
def area(self):
pass
class Circle(Shape):
def area(self):
return 3.14159265 * self.radius * self.radius
class Rectangle(Shape):
def area(self):
return self.length * self.width
Both Circle
and Rectangle
inherit from the common Shape
class, demonstrating hierarchical inheritance.
5. Hybrid Inheritance:
Hybrid inheritance is a combination of two or more types of inheritance. It often involves multiple and multilevel inheritance. Complex class hierarchies can be formed using hybrid inheritance.
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
In this example, D
inherits from both B
and C
, demonstrating a hybrid inheritance scenario.
6. Multiple Inheritance with Method Resolution Order (MRO):
In Python, when a class inherits from multiple parent classes, it follows a specific method resolution order (MRO) to determine which method to call when there are name conflicts. You can check the MRO using the mro()
method or the super()
function.
class A:
def method(self):
return "A"
class B:
def method(self):
return "B"
class C(A, B):
pass
c = C()
print(c.method()) # Output: "A" (follows MRO, so it uses A's method)
In this example, C
inherits from both A
and B
, and the MRO dictates that it uses A
's method.
Method Chaining
Before we dive into the concepts of method chaining it is important to know that a method can return a object which is responsible for calling the function itself
In object-oriented programming, a method can return an object that is responsible for calling the function itself by returning a reference to itself. This is commonly seen in the context of the "Builder" design pattern. The Builder pattern is used to construct a complex object step by step, allowing for a more readable and maintainable code.
Here's an example in Python without using method chaining:
class PizzaBuilder:
def __init__(self):
self.size = None
self.toppings = []
def set_size(self, size):
self.size = size
return self # Return the builder object itself
def add_topping(self, topping):
self.toppings.append(topping)
return self # Return the builder object itself
def build(self):
return Pizza(self.size, self.toppings)
class Pizza:
def __init__(self, size, toppings):
self.size = size
self.toppings = toppings
def __str__(self):
return f"A {self.size}-inch pizza with {', '.join(self.toppings)} toppings."
# Usage
pizza_builder = PizzaBuilder()
pizza = pizza_builder.set_size(12).add_topping("Pepperoni").add_topping("Mushrooms").build()
print(pizza)
In this example, we have a PizzaBuilder
class responsible for building a Pizza
object. The set_size
and add_topping
methods return the PizzaBuilder
object itself (return self
), allowing you to chain method calls if desired. The build
method creates and returns a Pizza
object based on the configured parameters.
By returning self
in each method, you can chain method calls on the same object, which provides a more fluent and expressive way of constructing objects.
Now we are familiar with the method returning the object we can discuss about method chaining
Method chaining in Python is a programming style where multiple method calls are invoked sequentially on the same object. This style is prevalent in Object-Oriented Programming (OOP) languages where methods are created inside classes and are invoked or called by objects of the class.
The advantage of method chaining is that it reduces the need for intermediate variables and makes the code more readable. In method chaining, each method performs an action on the same object and then returns the object to the next call, thus removing the need for assigning variables at each intermediate step. It also enhances the readability of the code since methods are invoked sequentially.
For example, consider a class CalculatorFunctions
with methods sum()
, difference()
, product()
, and quotient()
. Instead of calling these methods one after the other, we can chain them and call them in a single line as shown below:
class CalculatorFunctions():
def sum(self):
print("Sum called")
return self
def difference(self):
print("Difference called")
return self
def product(self):
print("Product called")
return self
def quotient(self):
print("Quotient called")
return self
if __name__ == "__main__":
calculator = CalculatorFunctions()
# Chaining all methods of CalculatorFunctions
calculator.sum().difference().product().quotient()
Here, each method is performing an action (printing a message in this case) and returning the same object (using return self
), which allows the next method in the chain to be called on the same object.
Method chaining is not limited to user-defined classes and methods. It can also be used with built-in methods and functions in Python. For instance, consider the following example where string and list methods are chained together:
days_of_week = "Monday Tuesday Wednesday Thursday Friday Saturday,Sunday"
weekend_days = days_of_week.split()[-1].split(',')
print(weekend_days) # Output: ['Saturday', 'Sunday']
In the above code, the method split()
is called on the string days_of_week
to split it into a list of days. Then, the last element of the list (which is "Saturday,Sunday") is accessed using [-1]
and split again using split(',')
to get the individual weekend days.
Super
The super()
function in Python is a built-in function that is used to call a method from a parent class, also known as a superclass. This function is most commonly used in the context of inheritance, which is a fundamental concept in object-oriented programming (OOP). Inheritance allows a class (known as a subclass) to inherit attributes and methods from another class (known as a superclass or parent class).
In Python, the super()
function provides a way to call methods from the superclass. This can be particularly useful when the subclass has a method with the same name as a method in the superclass (a situation known as method overriding). In such cases, you can use super()
to call the method from the superclass, and thus avoid having to rewrite the method in the subclass.
The syntax of super()
is as follows:
super().method_name(args)
Here, method_name
is the name of the method that you want to call from the superclass, and args
are any arguments that the method takes.
Let's consider an example. Suppose we have a superclass Person
and a subclass Student
. The Person
class has a method show_name()
, and the Student
class has a method with the same name. If we create an instance of the Student
class and call show_name()
, the method from the Student
class will be executed. However, if we want to call the show_name()
method from the Person
class, we can use super()
as follows:
class Person:
def show_name(self):
print("This is the Person's show_name method")
class Student(Person):
def show_name(self):
print("This is the Student's show_name method")
super().show_name()
student = Student()
student.show_name()
In this code, super().show_name()
inside the Student
class's show_name()
method calls the show_name()
method from the Person
class.
The super()
function can also be used with two parameters: the first is the subclass, and the second parameter is an object that is an instance of that subclass. This can be useful in more complex scenarios, such as multiple inheritance or when you need to call a method from a specific superclass.
super(subclass, instance).method(args)
Here, subclass
is the subclass that super()
is being called from, instance
is an instance of that subclass, method
is the method from the superclass that you want to call, and args
are any arguments that the method takes.
For example, consider the following code:
class Rectangle:
def area(self, length, width):
return length * width
class Square(Rectangle):
def area(self, length):
return super(Square, self).area(length, length)
In the Square
class's area()
method, super(Square, self).area(length, length)
is used to call the area()
method from the Rectangle
class.
One of the main advantages of using super()
is that it enhances code reusability and modularity. You don't need to rewrite the entire function in the subclass and it doesn't require you to specify the parent class name to access its methods. This can be particularly beneficial in larger codebases or when working with complex class hierarchies.
However, it's important to note that the super()
function returns a temporary object of the superclass, which allows access to its methods. This means that you can only use super()
to call methods that exist in the superclass. If the superclass does not have a method with the specified name, calling super().method_name(args)
will raise an AttributeError
.
Abstract
In Python, an abstract class serves as a blueprint for other classes, allowing the definition of methods that must be created within any child classes built from the abstract class. A class that contains one or more abstract methods is known as an abstract class. An abstract method is a method that has a declaration but does not have an implementation. In the process of designing large functional units, abstract classes are used. When there's a need to provide a common interface for different implementations of a component, abstract classes are also used.
Python supports abstract classes through its Abstract Base Class (ABC) module. This module provides a way to define abstract classes and enforce their interface on their subclasses. An abstract class in Python is a class designed to be inherited by other classes. It cannot be instantiated on its own and its primary purpose is to provide a template for other classes to build upon.
Here is an example of an abstract class in Python:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
r = Rectangle(5, 6)
print(r.area()) # Output: 30
In this example, Shape
is an abstract base class that defines an abstract method area
. Rectangle
is a concrete class that inherits from Shape
and implements the area
method.
Abstract classes in Python are classes that cannot be instantiated and are meant to be inherited by other classes. They are useful for defining common methods and properties that should be implemented by their concrete subclasses. One way to implement abstract classes in Python is to use abstract base classes (ABCs) from the abc module. An ABC is a special type of class that cannot be instantiated directly, but can be subclassed. ABCs define one or more abstract methods, which are methods that must be implemented by any concrete subclass.
Abstract classes give us a standard way of developing our code even if multiple developers are working on a project. They are useful because they provide a common interface for all the classes that inherit from them. This allows you to create code that is more flexible and easier to maintain.
It's important to note that abstract classes cannot be instantiated. This means that you can't create an object from an abstract class. Abstract classes are only meant to be used as base classes for other classes. The subclasses that inherit from an abstract class must provide an implementation for all the abstract methods defined in the abstract class.
One of the main advantages of using abstract classes is that they can be used to enforce a certain interface contract on classes, without specifying their implementation details. This can be particularly beneficial when working with plugins or when working in a large team or with a large codebase where keeping all classes in your mind is difficult or not possible.
In conclusion, abstract classes in Python provide a way to define a template for other classes to build on. They cannot be instantiated on their own and they provide a way to ensure that subclasses implement specific methods. The abc
module provides a way to define abstract base classes in Python.
Passing object as argument
At its core, passing objects to methods means invoking a method on an object, where the method performs some actions on the object itself. This allows for dynamic and modular interactions with objects, making your code more readable and maintainable.
Let's dive into an illustrative example. We'll define two classes, A
and B
, and a global function, global_function
, that accepts an object and an optional color parameter. The function sets the color attribute of the object, creating it if it doesn't exist. This simple example showcases the concept effectively:
class A:
color = None
class B:
pass
def global_function(object_of_any_class, color="White"):
# If the object does not have a color attribute,
# this function will create a color attribute for the passed object
object_of_any_class.color = color
a1 = A()
global_function(a1, "black")
print(a1.color) # Output: "black"
b1 = B()
global_function(b1, "red")
print(b1.color) # Output: "red"
b2 = B()
global_function(b2)
print(b2.color) # Output: "White"
In this example:
We define two classes,
A
andB
, with different attributes.The
global_function
method sets or modifies thecolor
attribute of the passed object, providing a default value of "White" if not specified.We create instances of both classes and use
global_function
to modify their color attributes.
Duck Typing
Duck typing is a concept in programming languages, particularly in dynamically typed languages like Python, that focuses on the behavior of objects rather than their specific types or classes. The term "duck typing" comes from the saying, "If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck." In Python, this means that the type or class of an object is determined by its behavior rather than its explicit declaration.
In other words, when you use duck typing in Python, you don't check the type of an object explicitly; instead, you rely on whether the object supports the operations or methods you intend to perform on it. If it does, Python allows you to perform those operations, regardless of the object's specific type.
Here's an example to illustrate duck typing in Python:
def add(a, b):
return a + b
result = add(5, 10) # Adding two integers
print(result) # Output: 15
result = add("Hello, ", "world!") # Concatenating two strings
print(result) # Output: Hello, world!
In this example, the add
function doesn't specify the types of a
and b
. It simply performs the +
operation on them. Python allows this because it checks whether the objects provided as arguments support the +
operation, and if they do, it performs the operation. This is duck typing in action – Python "looks at" the behavior of the objects, not their explicit types.
However, if you pass objects that don't support the +
operation, you'll get a runtime error (e.g., TypeError
), indicating that the operation is not supported on those objects. Duck typing assumes that if an object behaves like it can be used in a particular way, it can be used that way, and it's up to the programmer to ensure that the behavior is consistent with their expectations.
Duck typing can make Python code more flexible and allow for more concise and generic functions. It encourages a focus on what an object can do rather than what it is, which can lead to more versatile and reusable code.
Advanced Functional Programming
Walrus Operator
The walrus operator :=
is used for assignment within larger expressions. It allows you to both assign a value to a variable and use that value in an expression simultaneously. This can lead to more concise and readable code, especially in scenarios where you want to use the result of an expression and also keep track of that result.
In your example, you have two while
loops that take user input and continue executing until the user enters 0
. Let's break down both loops to understand how the walrus operator is used:
- Traditional
while
loop with separate assignment:
while True:
choice = int(input("Enter the choice --> "))
if choice == 0:
break
print("do something")
In this loop, you first take user input and assign it to the choice
variable using the =
assignment operator. Then, you check if choice
is equal to 0
, and if it is, you break out of the loop.
while
loop using the walrus operator:
while choice := int(input("Enter the choice --> ")) != 0:
print("do something")
In this loop, you use the walrus operator :=
to simultaneously assign the result of int(input("Enter the choice --> "))
to the choice
variable and check if the value is not equal to 0
. Here's how it works step by step:
The user is prompted to enter a choice.
The
int(input("Enter the choice --> "))
expression takes user input, converts it to an integer, and assigns the result tochoice
while checking if it's not equal to0
.If the user enters a value other than
0
, the loop continues executing, and "do something" is printed.If the user enters
0
, the loop terminates because the conditionchoice := int(input("Enter the choice --> ")) != 0
evaluates toFalse
.
The second loop using the walrus operator achieves the same functionality as the first loop but in a more concise and inline manner. It assigns the user's input to choice
and checks if it's not equal to 0
all in one line, making the code more compact and readable.
Higher order functions
Higher-order functions are a fundamental concept in functional programming and are supported in Python as well. A higher-order function is a function that can take one or more functions as arguments and/or return a function as its result. In Python, functions can be treated just like any other data type such as integers or strings.
Passing Functions as Arguments:
Higher-order functions can take other functions as arguments. This allows you to abstract behavior and make your code more reusable. For example:
def apply(func, x): return func(x) def square(x): return x ** 2 def double(x): return x * 2 result1 = apply(square, 5) # Pass the square function as an argument result2 = apply(double, 5) # Pass the double function as an argument print(result1) # Output: 25 print(result2) # Output: 10
Returning Functions:
Higher-order functions can also return functions as their result. This allows you to create functions dynamically based on some conditions or parameters. For example:
def create_multiplier(factor): def multiplier(x): return x * factor return multiplier double = create_multiplier(2) triple = create_multiplier(3) result1 = double(5) # Returns 5 * 2 = 10 result2 = triple(5) # Returns 5 * 3 = 15
Lambda function
A lambda function, also known as an anonymous function or a lambda expression, is a concise way to define small, unnamed functions in Python. Lambda functions are typically used for simple operations and can be defined in a single line. They are created using the lambda
keyword, followed by a list of arguments, a colon (:
), and an expression that is evaluated and returned as the function's result.
The syntax of lambda functions is:
# variable_name `lambda` arguments: return_statment
Now let's take an example for understanding
def multiply(a, b):
return a * b
This is a regular function named multiply
that takes two arguments, a
and b
, and returns their product.
Now, let's define the same functionality using a lambda function:
multiply_lambda = lambda a, b: a * b
Here, we've created a lambda function assigned to the variable multiply_lambda
. It takes two arguments, a
and b
, separated by a comma, and returns their product, which is expressed as a * b
. This lambda function is equivalent in functionality to the multiply
function.
You can call this lambda function just like any other function:
print(multiply_lambda(2, 3))
This will print the result of multiplying 2 and 3, which is 6.
check_age = lambda age: True if age > 17 else False
Here, we've defined a lambda function named check_age
that takes a single argument, age
. It checks if age
is greater than 17 and returns True
if it is, otherwise, it returns False
. This lambda function is essentially a concise way of writing a conditional statement.
You can call this lambda function with different values of age
:
print(check_age(10)) # This will print False
print(check_age(19)) # This will print True
In this way, lambda functions are useful for creating simple, one-line functions without the need to define a full function using the def
keyword.
Inbuilt functions
Sorted Function in Python
The sorted()
function is a built-in Python function that takes an iterable and returns a new sorted list from the elements in the iterable. It can sort different types of iterables such as lists, tuples, and dictionaries.
Syntax
The syntax of the sorted()
function is as follows:
sorted(iterable, key=None, reverse=False)
iterable
: This is the sequence (list, tuple, string) or collection (dictionary, set, frozenset) or any other iterator that needs to be sorted.key
(optional): This is a function that serves as a key or a basis of sort comparison.reverse
(optional): If set to True, then the iterable would be sorted in reverse (descending) order.
Basic Usage
Here is an example of using the sorted()
function to sort a list of numbers in ascending order:
list_1 = [1, 5, 3, 62, 6, 2, 27]
sorted_list = sorted(list_1)
print(sorted_list) # Output: [1, 2, 3, 5, 6, 27, 62]
The sorted()
function can also be used on tuples. However, it's important to note that it returns a new sorted list, not a tuple. To get a sorted tuple, you can convert the sorted list to a tuple as shown below:
tuple_1 = (5, 2, 9, 7)
sorted_tuple = sorted(tuple_1)
print(sorted_tuple) # Output: [2, 5, 7, 9]
Sorting With a Key Function
The key
parameter of the sorted()
function can be used to specify a function of one argument that is used to extract a comparison key from each input element. The function is applied to each item on the iterable.
Here's an example of sorting a list of tuples based on the first element of each tuple:
list_2 = [(1, 4, 2), (2, 5, 7), (7, 2, 9), (9, 6, 1), (6, 1, 6)]
list_2.sort()
for i in list_2:
print(i)
You can also use a lambda function as the key function. Here's an example of sorting a list of lists based on the second item in each list:
list_3 = [[1, 2, 3], [2, 1, 6], [4, 3, 2], [6, -1, 4]]
list_3.sort(key=lambda x: x[1])
for i in list_3:
print(i)
In this case, the lambda function lambda x: x[1]
is used to extract the second item from each list as the key for sorting.
Sorting in Descending Order
By default, the sorted()
function sorts in ascending order. If you want to sort in descending order, you can set the reverse
parameter to True
:
list_1 = [1, 5, 3, 62, 6, 2, 27]
sorted_list = sorted(list_1, reverse=True)
print(sorted_list) # Output: [62, 27, 6, 5, 3, 2, 1]
Sorting Strings
The sorted()
function can also be used to sort strings. When applied to a string, it treats the string as an iterable of characters and returns a new list that contains the sorted characters:
str_1 = "hello"
sorted_str = sorted(str_1)
print(sorted_str) # Output: ['e', 'h', 'l', 'l', 'o']
However, it's important to note that the sorted()
function returns a list, not a string. To get a sorted string, you can join the sorted characters back into a string as shown below:
str_1 = "hello"
sorted_str = ''.join(sorted(str_1))
print(sorted_str) # Output: "ehllo"