Python Fundamentals: Packages, Generators, and Decorators
In this guide, we will explore advanced Python topics including packages, generators, and decorators. These concepts are essential for writing efficient and maintainable Python code. Let’s dive in!
1. Python Packages
Python packages are a way of organizing and structuring your code into reusable modules. Packages allow you to group related modules together, making it easier to manage and maintain your codebase. In this section, we will explore how to create and use Python packages.
1.1. Creating a Package
To create a Python package, you need to organize your code into a directory structure with a special file called __init__.py
. This file tells Python that the directory is a package and allows you to import modules from the package.
Example directory structure:
my_package/
│
├── __init__.py
├── module1.py
└── module2.py
1.2. Using a Package
To use a Python package, you can import modules from the package using the import
statement. You can import specific modules or the entire package.
Example:
# Importing a specific module
from my_package import module1
# Using a function from the imported module
module1.my_function()
# Importing the entire package
import my_package
# Using a function from a module within the package
my_package.module2.another_function()
#Importing Function from a module
from my_package.module1 import my_function
my_function()
Note : init.py file can be empty or contain initialization code for the package. It will automatically run when the package is imported.
1.3 Creating a Subpackage
You can create subpackages within a package to further organize your code. Subpackages are simply packages within packages, allowing you to create a hierarchical structure for your codebase.
Example directory structure:
my_package/
│
├── __init__.py
├── module1.py
└── subpackage/
├── __init__.py
├── module3.py
└── module4.py
1.4. Using a Subpackage
To use a subpackage, you can import modules from the subpackage using the dot notation.
Example:
# Importing a module from a subpackage
from my_package.subpackage import module3
# Using a function from the imported module
module3.another_function()
# Importing the entire subpackage
import my_package.subpackage
# Using a function from a module within the subpackage
my_package.subpackage.module4.some_function()
#Importing Function from a module
from my_package.subpackage.module3 import another_function
another_function()
1.5. name == “main”
We use the __name__
variable to check if a script is being run as the main program or being imported as a module. When a script is run as the main program, __name__
is set to "__main__"
. This allows you to include code that should only run when the script is executed directly.
For Example:
Structure:
Main Directory/
├── main.py
└── package/
├── __init__.py
└── module1.py
module1.py:
def my_function():
print("Hello from module1")
if __name__ == "__main__":
my_function()
When you run module1.py
directly, the my_function()
will be executed. However, if you import module1
into another script, the my_function()
will not be executed.
For Example:
from package import module1
print("Imported module1")
Output:
Imported module1
When use Dont use if __name__ == "__main__":
in module1.py:
def my_function():
print("Hello from module1")
my_function()
also,
from package import module1
print("Imported module1")
Output:
Hello from module1
Imported module1
2. Python Generators
Using generator we can instantly return all the values using the yield
keyword without storing them in memory. This is useful when working with large datasets or infinite sequences.
2.1. Creating a Generator
To create a generator in Python, you can use a function with the yield
keyword. When a function contains the yield
keyword, it becomes a generator function. The yield
keyword suspends the function’s execution and returns a value to the caller.
Example:
def my_generator():
yield 1
yield 2
yield 3
# Using the generator
gen = my_generator()
print(next(gen)) # Output: 1
print(next(gen)) # Output: 2
print(next(gen)) # Output: 3
Example:
#using generator in loop
def generate(n):
for i in range(n):
yield i
for i in generate(10):
print(i)
3. Python Decorators
Decorators are a powerful feature in Python that allows you to modify or extend the behavior of functions or methods. Decorators are functions that take another function as an argument and return a new function that extends the behavior of the original function.
3.1. Creating a Decorator
To create a decorator in Python, you can define a function that takes another function as an argument and returns a new function that extends the behavior of the original function.
Syntax:
def my_decorator(func):
def wrapper():
# Code to execute before calling the decorated function
func()
# Code to execute after
return wrapper
Example:
def my_decorator(func):
def wrapper():
print("Before function call")
func()
print("After function call")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello() #call to my_decorator(say_hello)() just because we use @my_decorator
Output:
Before function call
Hello!
After function call
Example For decorator :
def Add_Wrapper(func):
def wrapper(a,b,c):
print("Status code is",c)
return func(a,b)
return wrapper
@Add_Wrapper
def add(a,b):
return a+b
print(add(2,3,200))
Output:
Status code is 200
5
3.2. Decorator with Arguments
You can also create decorators that accept arguments by defining a decorator function that takes arguments and returns a decorator function.
Syntax:
def my_decorator_with_args(arg1, arg2):
def decorator(func):
def wrapper():
# Code to execute before calling the decorated function
func()
# Code to execute after
return wrapper
return decorator
Example:
def my_decorator_with_args(arg1, arg2):
def decorator(func):
def wrapper():
print(f"Decorator arguments: {arg1}, {arg2}")
func()
return wrapper
return decorator
@my_decorator_with_args("arg1", "arg2")
def say_hello():
print("Hello!")
say_hello()
Output:
Decorator arguments: arg1, arg2
Hello!