Python questions Flashcards
List vs Tuple
List:
- Mutable
- Worse performance
- More built-in methods
Tuple:
- Immutable
- Better performance (faster iterations, less memory used)
- Less built-in methods
What is __init__() ?
It is a method which is class constructor
What is list comprehension?
It’s a loop statement condensed to one line, usually used to create a new, altered list based on other list eg:
odd_powers_of_two = [2**x for x in range(0, 11) if ( x % 2 ) == 1]
Are python classess fully mutable?
Yes, to the point of programmer being able to replace classess methods during runtime of the program (monkeypatching)
Exceptions - full statement
class XdError(Exception):
pass
try:
raise XdError
1/”xd”
except ZeroDivisionError:
print(“Nie dziel przez zero”)
except TypeError:
print(“Nie dziel przez c”)
except XdError:
print(“Nie dziel przez xd”)
except:
print (“other exception”)
else:
print (“else”)
finally:
print (“finally”)
What is decorator?
Decorator is a function which can modify behavior of other function (creates and returns other function, which adds some behavior around the base function)
How to use decorator function without @decorator statement?
def example_decorator ( func ):
def wrapper(args, **kwargs):
print(“Decorator Start”)
result = func(args, **kwargs)
print(“Decorator End”)
return result return wrapper
def print_xd():
print(“xd”)
print_xd = example_decorator ( print_xd )
print_xd()
What is del keyword used for?
del is used to delete objects; everything in Python is an object, so everything can be deleted
Create class with one private attribute; create getter and setter to the attribute
class Person:
def \_\_init\_\_(self, name: str) -> None: self._name: str = name @property def name(self): return self._name @name.setter def name(self, new_name): self._name = new_name
p1 = Person(“Anna”)
print(p1.name)
p1.name = “Janina”
print(p1.name)
What is set?
set - unordered collection of unique items (items can be added or popped, but not changed)
What is *args?
args is a dictionary
It’s argument for unknown number of arguments in function:
def example(*args):
#args is a tuple
for word in args:
print(word)
What is @staticmethod
It is a decorator for creating a static method, which means that the method can be called by refering both to class and instance of the class:
class Class:
@staticmethod
def static_method(a, b):
…
both are ok:
Class.static_method(1, 2)
Class().static_method(1,2)
What is a dictionary?
It’s an ordered (since Py3.7), changeable, unique* collection of pairs key:value
- only keys need to be unique
What is @classmethod
It is a decorator allowing for creation of function, refering to class, not to instance of it:
class Class
@classmethod
def class_method(cls):
…
What is **kwargs
It’s argument for unknown number of keyworded arguments in function:
def example(**kwargs):
#kwargs is a dictionary
for key, value in kwargs.items():
print(f”{key}: {value}”)
What are hashing algorithms used for?
They are used for creating signatures for messages and files in order to assure they haven’t been changed e.g. during transmission
What are examples of hashing algorithms?
MD-5
SHA-1
SHA-256
When to use THREADS ?
For concurrent tasks, which need to syncronise access to data
When to use ASYNCIO ?
For tasks with waiting times, which can be carried out concurrently, but without need to share data
When to use PROCESSES ?
When performance is the key; when you need to run and syncronise multiple applications
Using asyncio make concurrent program
import asyncio
async def fetch_data(time_s):
await asyncio.sleep(time_s)
print(f”Awaited: {time_s} seconds”)
async def main():
print(“start main”)
task1 = asyncio.create_task(fetch_data(3)) task2 = asyncio.create_task(fetch_data(1)) await task1 await task2 task3 = asyncio.create_task(fetch_data(2)) await task3 print("end main")
asyncio.run(main())
What is lambda?
it’s one line, anonymous function:
x = lambda a, b : a+b
What is with statement used for?
It’sa context manager used for exception management in cases like openeing files - it guards access to resources, which should not be access after end of with block
Name 4 Python namespaces
Built-in
Global
Enclosed
Local
t1 = (1, [1, 2, 3], “str”)
t1[1][0] = 0
Why this operation is legal?
Because list is mutable, and even though the tuple is immutable, it (tuple) is only interested with “reference” to list, not in its insights
What is scope?
It’s a part of program where variable is accesible
What are built-int mutable types in python? (3)
list
set
dict
What is the difference between these two operations? (ID and value of x)
lst = [1,2,3]
x = lst
ivar = 1
x = ivar
There is no difference, in boh cases x will have the same ID and value as the original variable
What is the difference between these two operations? (id?)
lst = [1,2,3]
x = lst
lst.append(4)
ivar = 1
y = ivar
ivar += 4
lst and x still have the same ID, before and after change
ivar has new ID, y’s ID remains the same
What 3 fields has structure PyObject?
Type (eg. string, int, etc)
Reference count
Value (eg. “xd”, 1)
What 4 fields has structure PyVarObject?
Type (container types eg. list, tuple, dict, etc)
Reference count
Value (eg. “xd”, 1)
Size
What are generators?
Generators are a special kind of functions returning lazy iterators.
Lazy iterators are iterable objects like lists, but unlike lists they don’t store their contents in memory, just return next value when prompted
What are pros and cons of using generators over lists?
Generators use less memory while working on big datasets, but lists tend to be faster
Use following generator with next() method:
def cLike_for_loop(start: int, end: int, delta: int = 1):
count:int = start
while count < end:
yield count
count += delta
ticks = 0
loop_gen = cLike_for_loop(10, 56, delta=2)
while ticks < 5:
value = next(loop_gen) print(value) ticks +=1
What are advanced methods to use with generator?
generator.send(value) - send data back to generator
generator.throw(ExceptionType) - throw exception at generator
generator.close() - stop generator
What is iterator?
Iterator is an object that allows you to iterate over collections of data (list, tuple, dict, set). It decouples iteration process from the data structure itself.
Which magic methods each iterator has to have implemented? What should they return?
__iter__(self) - typically returns self
__next__(self) - must return next item or raise a StopIteration exception
Name 3 types of iterators
- Classic - return/yield original data
- Transforming original data
- Generating new data set from starting conditions
What are:
fibonacci_generator
fib_gen
def fibonacci_generator(stop_idx=2):
if stop_idx < 2:
stop_idx = 2
index = 0 prev = [1, 1] while index < stop_idx: if index < 2: yield 1 else: new_value = sum(prev) yield new_value prev[1] = prev[0] prev[0] = new_value index += 1
fib_gen = fibonacci_generator(10)
fibonacci_generator - generator function
fib_gen - generator interator
How are iterators/generators more efficient than functions and collections?
Iterators and generators are lazy - they only store one, last value of dataset in memory
What does it mean that iterator is exhausted?
It means that iterator has been used to iterate over entire set, and will always raise StopIteration exception if used again; it cannot be resetted
What is an iterable object? What makes an iterable?
Iterable object contains data and can be iterated over. In order for a collection to become an iterable it must implement the Iterable Protocol or Sequence Protocol
(Iterable Protocol - implementation of __iter__)
(Sequence Protocol - __getitem__ and __len__)
What does it mean that type implements the iterable protocol?
Type implements __iter__ method, returning an Iterator (or Iterator generator)
OR
Type implements __getitem__ and __len__ methods (sequence protocol) - python is able to create implementation of __iter__ method automatically if Sequence Protocol is implemented
Create and use CubeIterator with:
input: list of int
output: these values to the power of three
class CubeIterator:
def __init__(self, sequence: list[int]) -> None:
self._index: int = 0
self._sequence: list[int] = sequence
def \_\_next\_\_(self) -> int | StopIteration: if self._index < len(self._sequence): self._index += 1 return (self._sequence[self._index - 1])**3 else: raise StopIteration def \_\_iter\_\_(self): return self
for cube in CubeIterator([1, 2, 5, 10]):
try:
print(cube)
except:
break
Create GenericIterator and use it WITHOUT for loop (use iter() and next())
class GenericIterator:
def __init__(self, sequence: list[int]) -> None:
self._index: int = 0
self._sequence: list[int] = sequence
def \_\_next\_\_(self) -> int | StopIteration: if self._index < len(self._sequence): self._index += 1 return self._sequence[self._index - 1] else: raise StopIteration def \_\_iter\_\_(self): return self
iterator = iter(GenericIterator([1, 2, 3, 10]))
while True:
try:
x = next(iterator)
print(x)
except:
break
What are 2 mechanism of preventing memory leaks/garbage collection in CPython?
- reference counting (deleting objects with 0 references)
- cyclic garbage collection (deleting unreachable objects)
Describe python memory leak with following code:
class Money:
name = ‘’
symbols = [] # This is the dangerous line here
def set_name(self, name): self.name = name def add_symbol(self, symbol): self.symbols.append(symbol)
Mutable type in class scope - each time we append to symbols list (even when done locally, actually the symbols in class scope is appended and never cleared
What is stored in _ variable in an interactive interpreter session?
The result of last expression
What does it mean that functios are first-class objects?
It means that functions can be passed around as arguments like any other object
What will be printed? How to “save” original name of the save_to_file function?
def manage_file(foo):
def wrapper(*args): print("Open file") ret_val = foo(*args) print("Close file") return ret_val return wrapper
@manage_file
def save_to_file(message):
print(f”Message ‘{message}’ saved to file!”)
print(save_to_file.__name__)
out: wrapper
import functools
def manage_file(foo):
@functools.wraps(foo)
def wrapper(args):
print(“Open file”)
ret_val = foo(args)
print(“Close file”)
return ret_val
return wrapper
Create decorator taking one argument
import functools
def manage_file(filename):
def manage_file_decorator(foo):
@functools.wraps(foo)
def manage_file_wrapper(args):
print(f”Open file {filename}”)
ret_val = foo(args)
print(“Close file”)
return ret_val
return manage_file_wrapper return manage_file_decorator
@manage_file(“doc.txt”)
def save_to_file(message):
print(f”Message ‘{message}’ saved to file!”)
save_to_file(“xd.txt”)
save_to_file(“Random message”)
Create decorator counting and remembering how many time a method was called
import functools
def call_counter(foo):
call_counter.count = 0
@functools.wraps(foo) def call_counter_wrapper(*args, **kwargs): call_counter.count += 1 ret_val = foo(*args, **kwargs) print(f"This method has been called {call_counter.count} time(s)") return ret_val call_counter.count = 0 return call_counter_wrapper
@call_counter
def save_to_file(message):
print(f”Message ‘{message}’ saved to file!”)
@call_counter
def nuffin():
pass
save_to_file(“Danger!”)
save_to_file(“High Voltage!!!”)
nuffin()
Create decorator based on CALLABLE CLASS, counting and remembering how many time a method was called
from typing import Any
class CallCounter:
def __init__(self, foo) -> None:
self.foo = foo
self.counter = 0
def \_\_call\_\_(self, *args: Any, **kwds: Any) -> Any: ret_val = self.foo(*args, **kwds) self.counter += 1 print(f"function '{self.foo.\_\_name\_\_}' has been called {self.counter} time(s)") return ret_val
@CallCounter
def func(value):
print(f”value: {value}”)
@CallCounter
def xd():
print(“xdxdxd”)
func(11)
func(11)
xd()
func(11)
func(11)
xd()
How many times is function sum_retvals called?
How many times is function sum_retvals_wrapper called?
import functools
def sum_retvals(foo):
@functools.wraps(foo) def sum_retvals_wrapper(*args, **kwargs): ret_val = foo(*args, **kwargs) sum_retvals_wrapper.sum += ret_val print(f"Sum of retvals for function {sum_retvals_wrapper.\_\_name\_\_} = {sum_retvals_wrapper.sum}") return ret_val sum_retvals_wrapper.sum = 0 return sum_retvals_wrapper
@sum_retvals
def multiply(a, b):
return a*b
@sum_retvals
def divide(a, b):
return a/b
_in = [1, 5]
multiply(_in)
multiply(_in)
_in=[2, 1]
multiply(_in)
divide(_in)
How many times is function sum_retvals called? - Twice, once per usage of decorator
How many times is function sum_retvals_wrapper called? - four times, each time divide and multiply functions are called
What is closure?
Closure - inner function, which is dynamically created and returned by its enclosing function. Their main feature is that they have full access to the variables and names defined in the local namespace where the closure was created, even though the enclosing function has returned and finished executing
Create closure factory function, which creates pointers to functions, raising input value to power chosen in moment of definition, usecase.:
p_2 = your_function(2)
print( p_2(5) )
def set_exponent(n):
def power(x):
return x**n
return power
power_of_2 = set_exponent(2)
power_of_3 = set_exponent(3)
power_of_4 = set_exponent(4)
for r in range(0, 11):
print(power_of_2(r))
print(power_of_3(r))
print(power_of_4(r), end=”\n\n”)
What is a functor?
Functor is an object containing:
- value
- (functional) mechanism of using the value
Create and use a simple functor
from __future__ import annotations
from typing import Any, Callable
Functor - object containig value and mechanism using this value
class Functor:
def __init__(self, value: Any) -> None:
self.value = value
def map(self, func: Callable): return Functor(func(self.value))
def add_one(value):
return value + 1
def multiply_by_4(value):
return value*4
print( Functor(2).map(add_one).map(multiply_by_4).map(add_one).value )
What is a difference between functor and endofunctor?
Endofunctor always returns object of its own type, Functor can return object of different type
What are Context Managers used for?
They are used for automatization of setup and teardown phases when dealing with external resources or other operations which require those phases
What is Context Manager Protocol?
It’s a protocol, which must be implemented by object in order for with statement to work with it.
What are methods in Context Manager Protocol, what do they do and what do they return?
__enter__(self) - handles setup logic, return value is target variable (after as keyword)
__exit__(self, except_type, except_value, except_traceback) - handles the teardown logic
Create a simple context manager class, taking one argument (name), saying hi at setup and goodbye at teardown.
class FirstContextManager:
def __init__(self, name: str) -> None:
self._name: str = name
def \_\_enter\_\_(self)->str: print(f"Hi {self._name}") return f"Hi {self._name}" def \_\_exit\_\_(self, e_type, e_value, e_traceback): print(f"Goodbye {self._name}") print(e_type) print(e_value) print(e_traceback)
with FirstContextManager(“Mark”) as yo:
print(yo)
What is Concurrency?
It’s an Ability to execute multiple task in overlapping manner; They can share one core or run pararelly
Are Concurrent processes executed pararelly?
Not always;
Concurrency does not always is Pararellism, but Pararellism always is Concurrent
What is a Coroutine?
Coroutine is a repurposed generator function, which can suspend its execution and pass control to other coroutine
What is keyword async used for?
async def - define a Native Coroutine or an Asynchronous Generator
async with -
async for -
What is keyword await used for?
await passes control from a Coroutine back to the Event Loop