Logging is a common practice in software development. It is used to record the events that occur in the system. It is useful for debugging and monitoring the system. In Python, thelogging
module is used to log messages.
Key components of thelogging
module are:
importlogging# Configure logginglogging.basicConfig(level=logging.INFO,format="%(asctime)s -%(name)s -%(levelname)s -%(message)s")# Create a loggerlogger=logging.getLogger(__name__)# Log messageslogger.info("This is an info message")
2024-02-22 12:42:32,865 - __main__ - INFO - This is an info message
Thelogging
module provides several logging levels. The following are the most commonly used logging levels:
DEBUG
: Detailed information, typically of interest only when diagnosing problems.INFO
: Confirmation that things are working as expected.WARNING
: An indication that something unexpected happened, or indicative of some problem in the near future (e.g. ‘disk space low’). The software is still working as expected.ERROR
: Due to a more serious problem, the software has not been able to perform some function.CRITICAL
: A serious error, indicating that the program itself may be unable to continue running.importlogging# Set up basic configuration for logginglogging.basicConfig(level=logging.DEBUG)# Examples of logging messages at different levelslogging.debug("This is a debug message")logging.info("This is an info message")logging.warning("This is a warning message")logging.error("This is an error message")logging.critical("This is a critical message")# Depending on the level set in basicConfig, some of these messages may not be displayed.
2024-02-22 12:42:32,870 - root - INFO - This is an info message2024-02-22 12:42:32,870 - root - WARNING - This is a warning message2024-02-22 12:42:32,870 - root - ERROR - This is an error message2024-02-22 12:42:32,871 - root - CRITICAL - This is a critical message
In Python'slogging
framework, handlers are responsible for dispatching the log messages to specific destinations. Each logger can have multiple handlers, allowing it to send logs to multiple outlets, such as the console, files, HTTP servers, or even more complex targets like log management systems.
Commonly used handlers are:
StreamHandler
: Sends log messages to a stream, typicallysys.stderr
.FileHandler
: Sends log messages to a file.RotatingFileHandler
: Sends log messages to a file, and creates a new file when the current file reaches a certain size.TimedRotatingFileHandler
: Sends log messages to a file, and creates a new file at certain intervals.SMTPHandler
: Sends log messages to an email address.SysLogHandler
: Sends log messages to a Unix syslog daemon.HTTPHandler
: Sends log messages to a web server.SocketHandler
: Sends log messages to a network socket.importlogging# Create a loggerlogger=logging.getLogger(__name__)logger.setLevel(logging.DEBUG)# Set minimum log level# Create a file handler that logs even debug messagesfile_handler=logging.FileHandler("../examples/debug.log")file_handler.setLevel(logging.DEBUG)# Create a console handler with a higher log levelconsole_handler=logging.StreamHandler()console_handler.setLevel(logging.ERROR)# Create a formatter and set it for both handlersformatter=logging.Formatter("%(asctime)s -%(name)s -%(levelname)s -%(message)s")file_handler.setFormatter(formatter)console_handler.setFormatter(formatter)# Add handlers to the loggerlogger.addHandler(file_handler)logger.addHandler(console_handler)# Log messageslogger.debug("This is a debug message")logger.error("This is an error message")# This setup logs debug and higher level messages to 'debug.log',# but only error and critical messages will appear in the console.
2024-02-22 12:42:32,875 - __main__ - DEBUG - This is a debug message2024-02-22 12:42:32,877 - __main__ - ERROR - This is an error message2024-02-22 12:42:32,877 - __main__ - ERROR - This is an error message
Logging formatters in Python's logging framework are responsible for converting a log message into a specific format before it is output by a handler. The formatter specifies the layout of log messages, allowing developers to include information such as the time of the log message, the log level, the message itself, and other details like the logger's name and the source line number where the log call was made.
Common Elements in Log Formats:
%(asctime)s
: The human-readable time at which the log record was created.%(name)s
: The name of the logger used to log the call.%(levelname)s
: The textual representation of the logging level (e.g., "DEBUG", "INFO").%(message)s
: The logged message.%(filename)s
,%(lineno)d
, and%(funcName)s
can include the source file name, line number, and function name, respectively.importlogging# Create a loggerlogger=logging.getLogger("my_logger")logger.setLevel(logging.DEBUG)# Create a handlerstream_handler=logging.StreamHandler()# Create a formatter and set it to the handlerformatter=logging.Formatter("%(asctime)s -%(name)s -%(levelname)s -%(message)s")stream_handler.setFormatter(formatter)# Add the handler to the loggerlogger.addHandler(stream_handler)# Log some messageslogger.debug("This is a debug message")logger.info("This is an info message")
2024-02-22 12:42:32,882 - my_logger - DEBUG - This is a debug message2024-02-22 12:42:32,882 - my_logger - DEBUG - This is a debug message2024-02-22 12:42:32,885 - my_logger - INFO - This is an info message2024-02-22 12:42:32,885 - my_logger - INFO - This is an info message
Code style is a set of conventions for writing code. It is important to follow a consistent code style to make the code readable and maintainable. Python has a set of conventions for writing code, which is defined in PEP 8. PEP 8 is the official style guide for Python code.
%%script false --no-raise-error # This cell is not executed because it is just an example# Import standard libraries firstimport osimport sys# Followed by third-party librariesimport requests# Constants are typically named in all uppercaseMAX_TIMEOUT = 60class NetworkClient: """Represents a simple network client.""" def __init__(self, server_url): self.server_url = server_url def fetch_data(self, endpoint): """Fetch data from a specific endpoint.""" url = f"{self.server_url}/{endpoint}" response = requests.get(url, timeout=MAX_TIMEOUT) return response.json()# Use meaningful names and follow PEP 8 spacing conventionsdef main(): client = NetworkClient("https://api.example.com") data = client.fetch_data("data") print(data)if __name__ == "__main__": main()
PEP 8, or Python Enhancement Proposal 8, is the style guide for Python code, establishing a set of rules and best practices for formatting Python code. Its primary goal is to improve the readability and consistency of Python code across the vast Python ecosystem. Since Python places a significant emphasis on readability and simplicity, adhering to PEP 8 can make your code more accessible to other Python developers.
Key Highlights of PEP 8:
CamelCase
for class names.Docstrings, or documentation strings, are literal strings used to document Python modules, functions, classes, and methods. They are enclosed in triple quotes ("""Docstring here"""
) and are placed immediately after the definition of a function, class, module, or method. Docstrings are a key part of Python code documentation and can be accessed through the.__doc__
attribute of the object they document or via the built-inhelp()
function.
defcalculate_area(base,height):""" Calculate the area of a triangle. Parameters: - base (float): The base of the triangle. - height (float): The height of the triangle. Returns: float: The area of the triangle calculated using (base * height) / 2. """return0.5*base*heightclassGeometryCalculator:""" A class used to perform various geometry calculations. Attributes: unit (str): The measurement unit for calculations (default is 'metric'). Methods: convert_unit(value, to_unit): Converts a measurement from one unit to another. """def__init__(self,unit="metric"):self.unit=unitdefconvert_unit(self,value,to_unit):"""Convert a measurement value from one unit to another."""# Conversion logic herepass
Type annotations in Python provide a way to explicitly state the expected data types of variables, function parameters, and return values. Type annotations are not enforced at runtime but can be used by third-party tools, IDEs, and linters for type checking, improving code readability, and aiding in debugging.
Purpose of Type Annotations:
mypy
to verify that the code adheres to the specified types at compile time, catching type errors before runtime.How to Use Type Annotations:
->
) syntax respectively.typing
module to specify more complex types, such as lists, dictionaries, and custom classes.fromtypingimportDict,List,Optional,Tupledefgreet(name:str)->str:returnf"Hello,{name}!"defprocess_data(data:List[int])->Tuple[int,int]:total=sum(data)count=len(data)returntotal,countdeffind_person(id:int,directory:Dict[int,str])->Optional[str]:returndirectory.get(id,None)# Variable annotationscores:List[int]=[90,95,88]# Optional variable annotation, useful when a variable can be Noneage:Optional[int]=None
Linting is the process of running a program that will analyze code for potential errors. Linters are tools that perform static analysis on source code to find programming errors, bugs, stylistic errors, and suspicious constructs. Linting tools can help enforce code style, improve code quality, and catch bugs early in the development process.
Common Python Linters:
pycodestyle
,pyflakes
, andmccabe
to check for PEP 8 violations, syntax errors, and cyclomatic complexity.Code formatters are tools that automatically format source code to adhere to a specific style guide or set of conventions. They can be used to enforce a consistent code style across a codebase, making the code more readable and maintainable.
Common Python Code Formatters:
Environment management in Python is the practice of managing multiple sets of Python and library versions to ensure compatibility and isolation between different projects. It's crucial for avoiding conflicts between project dependencies and for replicating production environments locally to debug issues accurately.
Avirtual environment is a self-contained directory that contains a Python installation for a particular version of Python, plus a number of additional packages. It allows you to work on a specific project without affecting other projects or the system-wide Python installation. Virtual environments are a best practice for Python development, as they help manage dependencies and isolate project-specific packages.
Common Tools for Managing Virtual Environments:
pip
andvirtualenv
to provide an easy way to manage project dependencies and virtual environments.# Create a new virtual environment using venvpython3 -m venv myenv# Activate the virtual environmentsource myenv/bin/activate
Dependency management in Python involves managing the libraries and packages that a project depends on. It includes installing, updating, and removing dependencies, as well as ensuring that the project's dependencies are compatible with each other and with the Python version being used.
Virtual Environments:
venv
,virtualenv
, or others to isolate project dependencies. This prevents conflicts between projects and makes it easier to manage project-specific dependencies.Requirements Files:
requirements.txt
file lists all of your project's dependencies, often with specified versions, in a format thatpip
can use to install them.requirements.txt
file for your project by runningpip freeze > requirements.txt
in your virtual environment.Package Managers and Dependency Resolvers:
Pipfile
andPipfile.lock
to manage dependencies, aiming for reproducibility and clearer dependency declaration.pyproject.toml
file for dependency declaration and dependency resolution, and creates a lock file to ensure consistent installs across environments.Dependency Locking:
Pipfile.lock
for Pipenv orpoetry.lock
for Poetry).Regular Updates and Security Checks:
pip-audit
for security vulnerabilities scanning, and dependabot on GitHub, can help keep your dependencies up-to-date and secure.# Install a package using pippip install requests# Install packages from a requirements filepip install -r requirements.txt# Update a package to the latest versionpip install --upgrade requests
Environment variables are key-value pairs stored in the operating system's environment that can affect the way running processes will behave on a computer. In Python development, environment variables are often used to manage application settings, configurations, sensitive information (like API keys and database passwords), and to differentiate between development, testing, and production environments without hardcoding such data into source code.
Python's standard library includes theos
module, which provides a way to access environment variables usingos.environ
. This acts like a Python dictionary, so you can use methods to get environment variable values, set default values if they don't exist, and check for their presence.
%%script false --no-raise-error # This cell is not executedimport os# Accessing an environment variable (returns None if not found)api_key = os.environ.get('API_KEY')# Accessing an environment variable with a default value if not founddb_host = os.environ.get('DB_HOST', 'localhost')# Setting an environment variable in Python (affects only the current process environment)os.environ['NEW_VARIABLE'] = 'value'
Thepython-dotenv
package is a popular tool for managing environment variables in Python projects. It allows you to define environment variables in a.env
file and load them into your Python environment. This is useful for keeping sensitive information out of your codebase and for managing different configurations across different environments.
%%script false --no-raise-error # This cell is not executed# Using python-dotenv to load variables from a .env filefrom dotenv import load_dotenvload_dotenv() # This loads the environment variables from a .env file if presentapi_key = os.environ.get('API_KEY')db_host = os.environ.get('DB_HOST', 'localhost')
Profiling is the practice of measuring the performance of a program to identify its most time-consuming parts and to optimize them. Profiling can help you understand how your code is performing, identify bottlenecks, and make informed decisions about where to focus optimization efforts.
Time profiling (orPerformance Profiling) is a type of profiling that measures the time taken by different parts of a program. It helps identify which parts of the code are consuming the most time and can be used to optimize the performance of the program.
Time Profiling importance:
Python's standard library includes thecProfile
module, which can be used to profile Python code. ThecProfile
module provides a way to profile the time taken by different functions and methods in a program.
importcProfileimportredefexample_function():returnre.compile("foo|bar").match("bar")# Run the profiler on the example_functioncProfile.run("example_function()")
8 function calls in 0.000 seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.000 0.000 952846612.py:5(example_function) 1 0.000 0.000 0.000 0.000 <string>:1(<module>) 1 0.000 0.000 0.000 0.000 __init__.py:225(compile) 1 0.000 0.000 0.000 0.000 __init__.py:272(_compile) 1 0.000 0.000 0.000 0.000 {built-in method builtins.exec} 1 0.000 0.000 0.000 0.000 {built-in method builtins.isinstance} 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} 1 0.000 0.000 0.000 0.000 {method 'match' of 're.Pattern' objects}
Memory profiling is the process of analyzing a program's memory usage over time to identify parts of the code that use excessive amounts of memory, have memory leaks, or could be optimized to use memory more efficiently. Understanding memory usage is crucial for developing efficient, scalable applications, especially in environments with limited resources or when dealing with large data sets.
Importance of Memory Profiling:
Python's standard library includes thetracemalloc
module, which can be used to profile memory usage in Python programs. Thetracemalloc
module provides a way to trace memory allocations and deallocations in a program.
importtracemalloc# Start tracing memory allocationstracemalloc.start()# Run some codea=[iforiinrange(100000)]b=[iforiinrange(100000,200000)]# Take a snapshot of the current memory allocationsnapshot=tracemalloc.take_snapshot()# Stop tracing memory allocationstracemalloc.stop()# Print out the statisticsforstatinsnapshot.statistics("lineno")[:3]:print(stat)
/var/folders/6r/27vxsf6512zffvpt__9jvmcc0000gn/T/ipykernel_44698/960184309.py:8: size=3907 KiB, count=100001, average=40 B/var/folders/6r/27vxsf6512zffvpt__9jvmcc0000gn/T/ipykernel_44698/960184309.py:7: size=3899 KiB, count=99744, average=40 B/opt/homebrew/Cellar/python@3.11/3.11.7_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/codeop.py:125: size=357 B, count=3, average=119 B
There are several tools available for profiling Python code, including both performance profiling and memory profiling tools.
The following are some commonly used performance profiling tools for Python:
For memory profiling, the following tools are commonly used:
Code review is a process in software development where developers review each other's code to find and fix bugs, improve code quality, and ensure that the code meets the project's requirements. Code reviews are an essential part of the software development process and can help identify issues early, improve code maintainability, and share knowledge among team members.
Benefits of Code Review:
Code review best practices are guidelines and recommendations for conducting effective code reviews. They help ensure that the code review process is productive, constructive, and beneficial for the development team.
ACode Review Tool is a software application that facilitates the code review process by providing features such as code diffing, commenting, and discussion tracking.
Common Code Review Tools: