Python Flashcards

1
Q

Explain the Global Interpreter Lock (GIL) in Python. How does it affect multithreading?

A

The GIL is a mutex that protects access to Python objects, preventing multiple native threads from executing Python bytecodes at once. This lock is necessary because Python’s memory management isn’t thread-safe. The GIL allows only one thread to execute Python code at a time, even if on multi-core systems.

This affects CPU-bound operations because even in multi-threaded programs, only one thread can run Python code at a time. I/O-bound tasks, like file operations or network communication, benefit from multithreading as these tasks release the GIL during I/O operations.

import threading

def cpu_bound_task():
    x = 0
    for i in range(10**7):
        x += i

Running two threads
t1 = threading.Thread(target=cpu_bound_task)
t2 = threading.Thread(target=cpu_bound_task)

t1.start()
t2.start()

t1.join()
t2.join()

In this case, despite using threads, only one thread executes at a time because of the GIL.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

What are Python’s memory management features?

A

Python uses reference counting and a garbage collector to manage memory. When an object’s reference count drops to zero, it is automatically deallocated. Python’s garbage collector also handles cyclic references using a generational garbage collection mechanism.

import sys

a = []
b = a
print(sys.getrefcount(a)) # Output: 3, since there is one reference for 'a', 'b', and internal reference in `getrefcount()`

When the reference count of a drops to zero, it is collected by the garbage collector.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Explain Python’s Method Resolution Order (MRO).

A

MRO determines the order in which base classes are searched when calling a method. Python uses the C3 linearization algorithm to determine this order. The MRO can be checked using the __mro__ attribute or mro() method of a class.

class A:
    def process(self):
        print("A")

class B(A):
    def process(self):
        print("B")

class C(A):
    def process(self):
        print("C")

class D(B, C):
    pass

print(D.mro())  # [D, B, C, A, object]

d = D()
d.process()  # Output: B
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

What is the difference between deepcopy and copy?

A

copy.copy() creates a shallow copy of an object, meaning it only copies the object itself, not nested objects.

copy.deepcopy() creates a deep copy, meaning it copies the object and all objects it contains.

import copy

a = [1, [2, 3], 4]
b = copy.copy(a)
c = copy.deepcopy(a)

a[1][0] = 99
print(b)  # Shallow copy: [1, [99, 3], 4]
print(c)  # Deep copy: [1, [2, 3], 4]
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Describe how Python’s asyncio library works.

A

asyncio provides a framework for writing asynchronous (non-blocking) code using async and await. It’s useful for I/O-bound tasks like file reading, network requests, and database queries.

import asyncio

async def fetch_data():
    print('Start fetching...')
    await asyncio.sleep(2)
    print('Done fetching')
    return 'data'

async def main():
    result = await fetch_data()
    print(result)

Run the event loop
asyncio.run(main())

Here, the await keyword pauses the execution of the fetch_data() coroutine and allows other tasks to run while waiting.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

What are metaclasses in Python?

A

Metaclasses are classes of classes; they define how classes behave. A class in Python is an instance of a metaclass. Normally, type is the metaclass used to create classes.

class Meta(type):
    def \_\_new\_\_(cls, name, bases, dct):
        print(f'Creating class {name}')
        return super().\_\_new\_\_(cls, name, bases, dct)

class MyClass(metaclass=Meta):
    pass

Output: Creating class MyClass
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

What is the difference between __new__ and __init__?

A

Output: Creating instance

  • __new__ is responsible for creating a new instance of a class. It’s called before __init__ and is typically used when subclassing immutable types.
  • __init__ initializes the instance created by __new__.
class MyClass:
    def \_\_new\_\_(cls):
        print("Creating instance")
        return super().\_\_new\_\_(cls)

    def \_\_init\_\_(self):
        print("Initializing instance")

obj = MyClass()
# Output: Creating instance
#         Initializing instance
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

How are Python decorators implemented?

A

Decorators are functions that modify the behavior of another function. They take a function as input and return a new function.

def my_decorator(func):
    def wrapper():
        print("Something before the function")
        func()
        print("Something after the function")
    return wrapper

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

say_hello()
# Output: Something before the function
#         Hello!
#         Something after the function
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Explain how Python handles exceptions.

A

Python uses try-except blocks for exception handling. You can catch multiple exceptions, use the finally block for cleanup, and raise custom exceptions.

try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"Caught an exception: {e}")
else:
    # Code to run if there was no exception
finally:
    print("This block is always executed")
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

What is the purpose of the @dataclass decorator in Python?

A

The @dataclass decorator automatically generates special methods like __init__(), __repr__(), and __eq__() for a class. It reduces boilerplate for simple data structures.

from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

p1 = Point(1, 2)
print(p1)  # Output: Point(x=1, y=2)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

Explain how list comprehensions and generator expressions work.

A

List comprehensions are a concise way to create lists. Generator expressions work similarly but return an iterator instead of a list, which is more memory-efficient.

# List comprehension
squares = [x**2 for x in range(10)]

Generator expression
squares_gen = (x**2 for x in range(10))

print(next(squares_gen))  # Output: 0
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

What is the difference between is and ==?

A
  • is checks for identity (whether two references point to the same object).
  • == checks for equality (whether the values of two objects are the same).
a = [1, 2, 3]
b = a
c = [1, 2, 3]

print(a is b)  # True
print(a == c)  # True
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

What are Python’s magic methods?

A

Magic methods are special methods that start and end with double underscores. They allow customization of Python’s built-in operations for objects.

class MyNumber:
    def \_\_init\_\_(self, value):
        self.value = value
    
    def \_\_add\_\_(self, other):
        return MyNumber(self.value + other.value)
    
    def \_\_repr\_\_(self):
        return f"MyNumber({self.value})"

num1 = MyNumber(10)
num2 = MyNumber(20)
print(num1 + num2)  # Output: MyNumber(30)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Explain the difference between __getattr__ and __getattribute__.

A

__getattr__ is called when an attribute is not found in an object.

__getattribute__ is called every time an attribute is accessed (even if it exists).

class MyClass:
    def \_\_getattr\_\_(self, name):
        return f"{name} not found"

    def \_\_getattribute\_\_(self, name):
        print(f"Accessing {name}")
        return object.\_\_getattribute\_\_(self, name)

obj = MyClass()
print(obj.existing_attr)  # Calls \_\_getattribute\_\_
print(obj.non_existent_attr)  # Calls \_\_getattr\_\_
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

How does Python handle dynamic typing?

A

Python is a dynamically typed language, meaning that the type of a variable is determined at runtime rather than at compile time. This approach provides significant flexibility and ease of use but also requires developers to be mindful of potential type-related errors that can emerge during execution.

x = 5  # x is an integer
x = "hello"  # x is now a string

To enforce stricter typing, Python 3.5+ supports type hints:

def add(a: int, b: int) -> int:
    return a + b
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

What are slots in Python?

A

__slots__ limit the attributes a class can have, improving memory efficiency by preventing the creation of __dict__ for each instance.

class MyClass:
    \_\_slots\_\_ = ['x', 'y']

obj = MyClass()
obj.x = 10
obj.y = 20
17
Q

Explain the difference between mutable and immutable objects.

A

b[0] = 10 # This would raise an error, as tuples are immutable.

  • Mutable objects (like lists, dicts) can be changed after creation.
  • Immutable objects (like strings, tuples) cannot be changed.
a = [1, 2, 3]
a[0] = 10  # Mutable

b = (1, 2, 3)
# b[0] = 10  # This would raise an error, as tuples are immutable.
18
Q

How does Python’s itertools library work?

A

itertools provides efficient looping tools. Common functions include chain(), combinations(), and groupby().

from itertools import chain, combinations

Chain: Flatten multiple iterables
for i in chain([1, 2], [3, 4]):
    print(i)  # Output: 1 2 3 4

Combinations
print(list(combinations([1, 2, 3], 2)))  # Output: [(1, 2), (1, 3), (2, 3)]
19
Q

What is monkey patching?

A

Monkey patching is the dynamic modification of a class or module at runtime. It can be useful but is generally considered bad practice because it can make code harder to understand and maintain.

class MyClass:
    def greet(self):
        return "Hello"

Monkey patching
def new_greet():
    return "Hi"

MyClass.greet = new_greet
obj = MyClass()
print(obj.greet())  # Output: Hi
20
Q

Discuss the heapq library.

A

heapq implements a binary heap, which is useful for priority queues. The smallest element is always at the root.

import heapq

nums = [1, 8, 3, 5, 4]
heapq.heapify(nums)
print(heapq.heappop(nums))  # Output: 1 (smallest element)
21
Q

Explain Python’s multiprocessing library.

A

multiprocessing allows parallel execution of code by using separate processes. It avoids the GIL limitation present in threading by creating new processes.

import multiprocessing

def worker():
    print("Worker process")

if \_\_name\_\_ == "\_\_main\_\_":
    p = multiprocessing.Process(target=worker)
    p.start()
    p.join()
21
Q

What are context managers in Python?

A

Context managers manage resources (like file I/O). The with statement ensures proper acquisition and release of resources.

with open('file.txt', 'w') as f:
    f.write('Hello, World!')
# File is automatically closed after exiting the with block.
22
Q

What is the difference between yield and return?

A
  • return exits the function and sends a value back to the caller.
  • yield produces a value and suspends the function’s state, allowing it to resume later.
def my_generator():
    yield 1
    yield 2
    yield 3

gen = my_generator()
print(next(gen))  # Output: 1
print(next(gen))  # Output: 2
22
Q

Explain the collections module.

A

The collections module provides specialized data structures such as defaultdict, namedtuple, and deque.

from collections import defaultdict, namedtuple, deque

defaultdict
dd = defaultdict(int)
dd['a'] += 1
print(dd['a'])  # Output: 1

namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(1, 2)
print(p.x, p.y)  # Output: 1 2

deque
d = deque([1, 2, 3])
d.appendleft(0)
print(d)  # Output: deque([0, 1, 2, 3])
23
Q

Explain staticmethod vs. classmethod.

A
  • staticmethod is bound to a class but doesn’t require class or instance references. It behaves like a regular function.
  • classmethod receives the class as the first argument, allowing it to access or modify the class state.
class MyClass:
    @staticmethod
    def static_method():
        return "Static method"

    @classmethod
    def class_method(cls):
        return f"Class method called from {cls}"

print(MyClass.static_method())  # Output: Static method
print(MyClass.class_method())   # Output: Class method called from <class '\_\_main\_\_.MyClass'>
24
Q
A