Python Flashcards
What is Garbage Collection in Python
Garbage collection in Python is an automatic memory management process that helps reclaim memory occupied by objects that are no longer in use. The primary mechanism used for garbage collection in Python is called reference counting.
In Python, every object has a reference count associated with it. The reference count keeps track of the number of references to an object. When the reference count of an object reaches zero, it means that no references exist to that object, indicating that it is no longer needed.
When an object’s reference count becomes zero, the garbage collector identifies and collects that object. The garbage collector frees the memory occupied by the object and updates any references to that object elsewhere in the program.
However, Python’s garbage collector also employs additional techniques like cyclic garbage collection to handle more complex cases where objects form reference cycles, meaning they reference each other in a way that no external reference exists.
The garbage collector periodically runs in the background, identifying and collecting objects that are no longer reachable, thus freeing up memory and ensuring efficient memory usage.
It’s important to note that as a developer, you generally don’t need to manually manage memory or explicitly invoke garbage collection in Python. The garbage collector automatically takes care of memory management for you.
What are Lambda Functions
Lambda functions, also known as anonymous functions, are small, anonymous functions in Python that can be defined without a name using the lambda keyword. Lambda functions are typically used when you need a simple function for a short and specific operation and don’t want to define a separate named function.
It’s important to note that lambda functions are best suited for simple operations. For more complex logic, it’s generally recommended to define a regular named function for better readability and maintainability.
What is the difference between a syntax error and a runtime error in Python?
A syntax error occurs when the code violates the grammar rules of the programming language. It indicates that the code is not written correctly and cannot be interpreted by the Python interpreter. Common syntax errors include missing parentheses, incorrect indentation, misspelled keywords, or improper use of operators.
a runtime error occurs when the code is syntactically correct, but an error occurs during the execution of the program. These errors are also known as exceptions. Runtime errors often happen due to logical mistakes, unexpected inputs, or issues with data types.
What is the difference between a NameError and an AttributeError in Python?
NameError: Occurs when you try to use a name or variable that hasn’t been defined or is not accessible in the current scope.
AttributeError: Occurs when you try to access an attribute or method that doesn’t exist for a given object.
What is the difference between a KeyError and an IndexError in Python? Can you provide examples of each?
A KeyError occurs when you try to access a key in a dictionary that does not exist. It means that the key you are trying to access is not present in the dictionary.
An IndexError occurs when you try to access a list or sequence using an index value that is outside the range of valid indices. It means that the index you are trying to access does not exist in the list.
What are decorators
In Python, decorators are a way to modify the behavior of functions or classes by wrapping them with additional functionality. They allow you to extend or enhance the functionality of existing code without modifying its original implementation. Decorators are implemented using the @decorator_name syntax, placed before the function or class definition.
At a high level, decorators are functions that take another function (or a class) as input, perform some processing or modification on it, and return a new function (or class) that incorporates the desired changes. This can involve adding functionality, modifying arguments, altering the return value, or any other custom behavior.
Decorators are commonly used for cross-cutting concerns such as logging, caching, input validation, authentication, and more. They provide a clean and modular way to apply such functionalities to multiple functions or classes in a concise manner.
Can you explain the concept of generators in Python and provide an example of how they can be used?
Absolutely! In Python, generators are a type of iterator that allows you to generate a sequence of values on-the-fly, without storing them all in memory at once. They are defined using a special kind of function called a generator function, which uses the yield keyword instead of return.
A generator function behaves like a regular function, but when it encounters a yield statement, it suspends its execution, saves its internal state, and returns a value to the caller. The next time the generator is called, it resumes from where it left off, using the saved state. This allows generators to produce a sequence of values lazily, one at a time, instead of computing and returning all the values at once.
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib_gen = fibonacci()
for _ in range(10):
print(next(fib_gen))
Generator functions in Python are used in several scenarios, such as:
Handling Large Data Sets: When working with a large data set that cannot fit into memory, using a list may not be practical. A generator function is an excellent solution in this case because it generates one item at a time rather than storing all items in memory at once.
Streaming Data: Similar to large data sets, when you’re working with streaming data (like reading from a file or database, receiving from a network, etc.), you might want to process items one at a time. Generator functions allow you to process streams of data in a memory-efficient manner.
Infinite Sequences: Sometimes, you need to create an infinite sequence (like the sequence of all even numbers, or all Fibonacci numbers). This is simply impossible with a Python list, but quite easy with a generator function.
Resource Intensive Computations: When each item requires a significant amount of computation to produce, it might be advantageous to produce items one at a time so that computation is spread out over time. This also means the first item is available sooner, which can be beneficial in cases where you don’t necessarily know in advance how many items you need.
Pipelining: You can chain generators together to form an efficient data processing pipeline. Each stage of the pipeline is a generator that takes input from the previous stage, processes it, and sends it on to the next stage. This is a very memory-efficient way of processing data, and allows you to easily construct complex data processing sequences.
Differentiate between instance and class variables?
Describe compile-time and run-time code checking in Python
Compile-time code checking: This refers to the analysis and validation of the code that occurs before the actual execution of the program. During the compilation phase, Python checks the syntax and structure of the code to ensure it adheres to the language rules. The compiler examines the code for potential errors, such as syntax errors, typos, incorrect indentation, and missing or misused language constructs.
If any errors are found during compile-time, the Python interpreter raises a compilation error or a syntax error, preventing the program from running. These errors need to be resolved before the code can be executed.
Compile-time code checking helps identify issues early in the development process, allowing developers to catch and fix errors before the program is executed. It contributes to writing code that is syntactically correct and follows the language’s rules and guidelines.
Run-time code checking: This occurs during the execution of a program when Python evaluates statements and runs the code line by line. Run-time code checking involves verifying the logical correctness and integrity of the code while it is running.
During run-time, Python performs various checks, including type checking, name resolution, and error handling. It ensures that variables are used correctly, function calls are made with the correct number and types of arguments, and operations are performed on appropriate data types.
If any run-time errors occur, such as division by zero, accessing an undefined variable, or calling a method on an incompatible object, Python raises exceptions, such as ZeroDivisionError or NameError. These exceptions can be caught and handled using appropriate error handling techniques like try-except blocks.
Run-time code checking helps identify logical errors and exceptions that may occur while executing the program. By raising exceptions, Python provides a way to handle and gracefully recover from errors during program execution.
What is pickling
In Python, pickling refers to the process of serializing and deserializing Python objects. Serialization is the process of converting an object’s state into a byte stream, while deserialization is the process of reconstructing the object from the byte stream.
The pickle module in Python provides a convenient way to pickle and unpickle objects. Pickling allows you to save Python objects to a file or transfer them over a network, and then later restore them back into memory.
Dynamically Typed Language
A dynamically typed language is a programming language where variables are not explicitly assigned a type during declaration. Instead, the type of a variable is determined at runtime based on the value assigned to it.
In a dynamically typed language, variables can be reassigned to different types throughout the program execution. The type of a variable can change as it is assigned different values during runtime, without any requirement to explicitly declare or convert the variable’s type.
Interpreted language
An interpreted language is a programming language where the source code is executed directly without the need for a separate compilation step. In an interpreted language, the code is read and executed line by line by an interpreter, which translates and executes the instructions in real-time.
Here are some key characteristics of interpreted languages:
No explicit compilation: In contrast to compiled languages, interpreted languages do not require the code to be compiled into machine code before execution. The interpreter reads the source code directly and executes it. Line-by-line execution: The interpreter processes the code line by line, translating each line into machine instructions or bytecode and executing it immediately. Dynamic typing: Interpreted languages often support dynamic typing, where the type of a variable is determined at runtime based on the value assigned to it. This allows for flexibility but may require careful handling to ensure type compatibility. Interactive shell: Many interpreted languages provide an interactive shell or REPL (Read-Eval-Print Loop), allowing developers to execute code snippets or individual statements interactively. Portability: Interpreted languages are often designed to be portable, allowing the same code to be executed on different platforms without the need for recompilation.
Explain how Python is executed.
Python is an interpreted language, which means that the source code is executed line by line by a Python interpreter. The execution process involves several steps, including tokenization, parsing, compilation, and interpretation. Here’s a high-level overview of how Python code is executed:
Tokenization: The first step is tokenization, where the Python interpreter breaks down the source code into individual tokens. Tokens are the smallest units of code, such as keywords, identifiers, operators, and literals. This process helps the interpreter identify the structure and meaning of the code.
Parsing: Once the code is tokenized, the interpreter parses it to create an abstract syntax tree (AST). The AST represents the hierarchical structure of the code, capturing the relationships between different elements like expressions, statements, and function definitions.
Compilation: In this step, the AST is converted into bytecode. Bytecode is a low-level, platform-independent representation of the code that can be executed by the Python interpreter. The compilation process involves transforming the AST into a series of bytecode instructions that the interpreter can understand.
Interpretation: After the compilation stage, the Python interpreter executes the bytecode instructions. The interpreter reads the bytecode line by line, interpreting and executing the corresponding operations. This includes evaluating expressions, executing statements, and calling functions.
During interpretation, the interpreter interacts with the underlying runtime environment, which provides services like memory management, exception handling, and standard libraries. The interpreter performs the necessary operations to execute the code correctly and produce the desired results.
It’s worth noting that Python employs various optimization techniques to improve performance. For example, the interpreter may cache compiled bytecode for faster subsequent executions, perform just-in-time (JIT) compilation to convert certain parts of the code into machine code for better efficiency, or employ other runtime optimizations.
Python’s execution process combines elements of interpretation and compilation, making it an efficient and flexible language. The interpreted nature allows for dynamic features and interactive development, while compilation and optimization steps help improve performance.
PEP 8
PEP 8, also known as the Python Enhancement Proposal 8, is a set of guidelines and recommendations for writing Python code in a consistent and readable manner. It provides a style guide that outlines best practices for code layout, naming conventions, comments, and other aspects of Python programming. PEP 8 is important for several reasons:
Code Readability: PEP 8 emphasizes code readability and promotes a consistent style across Python projects. By following the guidelines, code becomes more understandable, not only for the original author but also for other developers who might read or maintain the code in the future. Readable code is easier to debug, modify, and collaborate on.
Maintainability: Following PEP 8 facilitates code maintenance. Consistent formatting and naming conventions make it easier for developers to understand and modify code, reducing the time and effort required for maintenance tasks. Codebases that adhere to PEP 8 are generally more maintainable and less prone to errors caused by confusion or inconsistency.
Code Collaboration: When multiple developers work on a project, adhering to PEP 8 ensures that everyone follows a common coding style. This promotes collaboration and reduces conflicts arising from differing coding practices. It allows developers to focus on the code logic and functionality rather than getting caught up in stylistic differences.
Community Standards: PEP 8 represents the consensus of the Python community on coding conventions. By following PEP 8, developers align their code with established standards and practices, making it easier for others to understand and contribute to their projects. It fosters a sense of familiarity and consistency across the Python ecosystem.
Tooling and Integration: PEP 8 is widely supported by various code editors, IDEs, linters, and other development tools. These tools can automatically analyze code for PEP 8 compliance and provide suggestions or warnings for non-compliant code. Adhering to PEP 8 allows developers to take advantage of such tools and benefit from the additional checks and improvements they offer.
While PEP 8 is not mandatory, it serves as a valuable resource to improve code quality, readability, and maintainability. Adhering to PEP 8 guidelines promotes consistency, facilitates collaboration, and aligns code with the larger Python community, ultimately leading to better code quality and a more enjoyable development experience.
What is monkey patching in Python?
Monkey patching in Python is a dynamic technique that can change the behavior of the code at run-time. In short, you can modify a class or module at run-time.
Example:
Let’s learn monkey patching with an example.
1) We have created a class monkey
with a patch()
function. We have also created a monk_p
function outside the class.
2) We will now replace the `patch` with the `monk_p` function by assigning `monkey.patch` to `monk_p`. 3) In the end, we will test the modification by creating the object using the `monkey` class and running the `patch()` function.