└── README.md /README.md: -------------------------------------------------------------------------------- 1 | # Python Interview Questions 2 | 3 | #### Interview questions for junior to intermediate programmers. 4 | 5 | ---------------------------- 6 | 7 | 1. What are lambda functions? Why are they used? 8 | A Lambda function, or an anonymous function, is a self-contained block of functionality that can be passed around and used in your code. They are syntactically restricted to a single expression. Semantically, they are just syntactic sugar for a normal function definition. 9 | - Lambda functions are syntactic sugar and provide a cleaner way to write easy/small functions. 10 | 11 | - Python supports the functional programming paradigm. It allows to supply a function as a parameter to a different function like map, filter, etc. In such cases, elegant one time functions can be created using lambda. 12 | 13 | [Why are python lambdas useful - Stack Overflow](https://stackoverflow.com/a/890188) 14 | 15 | ---------------------------- 16 | 17 | 2. What are the built-in data types that Python provides? Which of them are mutable, which are immutable? 18 | Numeric Types: `int`, `float`, `complex`, `bool` 19 | Sequence Types: `list`, `tuple`, `range`, `str` 20 | Mapping Type: `dict` 21 | Set Types: `set`, `frozenset` 22 | 23 | Mutable: `list`, `set`, `dict` 24 | Immutable: `str`, `tuple`, `range`, `bool`, `complex`, `int`, `float` 25 | 26 | ---------------------------- 27 | 28 | 3. What is the difference between list and tuple? 29 | [Tuples vs Lists - Stack Overflow](https://stackoverflow.com/a/1708538/5193334) 30 | [Tuples have structure, lists have order -Stack Overfloww](https://stackoverflow.com/a/626871/5193334) 31 | 32 | ---------------------------- 33 | 34 | 4. Why are tuples faster than lists? 35 | Tuples are faster than lists because: 36 | 37 | - Tuples can be constant folded. 38 | 39 | - Tuples can be reused instead of copied. 40 | 41 | - Tuples are compact and don't over-allocate. 42 | 43 | - Tuples directly reference their elements. 44 | Read the first two answers on this thread - 45 | [Tuples faster than lists - Stack Overflow](https://stackoverflow.com/questions/3340539/why-is-tuple-faster-than-list-in-python) 46 | 47 | ---------------------------- 48 | 49 | 5. What is the difference between `is` and `==`? 50 | - `==` is for value equality. Use it when you would like to know if two objects have the same value. 51 | 52 | - `is` is for reference equality. Use it when you would like to know if two references refer to the same object. 53 | In general, when you are comparing something to a simple type, you are usually checking for value equality, so you should use `==`. For example, for `x==2` the intention is probably to check whether x has a value equal to 2 (==), not whether x is literally referring to the same object as 2. 54 | 55 | - However, there is a catch to this as well. Please refer the example in the below link. 56 | 57 | [`==` vs `is` - Stack Overflow](https://stackoverflow.com/a/1085656/5193334) 58 | 59 | ---------------------------- 60 | 61 | 6. What could be the key in `dict`? 62 | Unlike sequences, which are indexed by a range of numbers, dictionaries are indexed by keys, which can be any immutable type; strings and numbers can always be keys. Tuples can be used as keys if they contain only strings, numbers, or tuples; if a tuple contains any mutable object either directly or indirectly, it cannot be used as a key. You can’t use lists as keys, since lists can be modified in place using index assignments, slice assignments, or methods like `append()` and `extend()`. 63 | 64 | ---------------------------- 65 | 66 | 7. Why can't lists be dictionary keys? 67 | If lists are hashed by their ids - 68 | 69 | - Looking up different lists with the same contents would produce different results, even though comparing lists with the same contents would indicate them as equivalent. 70 | 71 | - Using a list literal in a dictionary lookup would be pointless -- it would always produce a `KeyError`. 72 | 73 | If lists were hashed by their contents (as tuples are) then that would lead to problems explained below - 74 | 75 | ```python 76 | #Suppose that the hash() function returns x & y for the following list objects 77 | [1,2] -> x 78 | [1,2,3] -> y 79 | 80 | #Initiate a list l & dictionary d 81 | l = [1,2] 82 | d[l] = 42 83 | 84 | #The representation of the inside of a dictionary's collision list/bucket would look something like this (simplified) 85 | x -> ([1,2], 42) 86 | 87 | #Now append 3 to the list 88 | l.append(3) 89 | >> [1,2,3] 90 | 91 | #The representation of the inside of a dictionary's collision list/bucket would change to this (simplified) 92 | x -> ([1,2,3], 42) 93 | 94 | #Actual hash value was 95 | [1,2] -> x 96 | #According to the collision list 97 | [1,2,3] -> x 98 | #So this defies the correctness condition - for all i1, i2, if hash(i1) == hash(i2), then i1 == i2 99 | 100 | #Actual hash value was 101 | [1,2,3] -> y 102 | #According to the collision list 103 | [1,2,3] -> x 104 | #So this defies the correctness condition - for all i1, i2, if hash(i1) != hash(i2), then i1 != i2 105 | ``` 106 | 107 | Since the dictionary doesn't know when a key object is modified, such errors could only be produced at key lookup time, not at object modification time, which could make such errors quite hard to debug. 108 | 109 | [Why lists can't be keys - Python Documentation](https://wiki.python.org/moin/DictionaryKeys) 110 | 111 | ---------------------------- 112 | 113 | 8. What is `__init__.py` for? 114 | [`__init__.py` - Stack Overflow](https://stackoverflow.com/a/4116384/5193334) 115 | 116 | ---------------------------- 117 | 118 | 9. How can I swap values of variables in Python? 119 | `left, right = right, left` 120 | [Pythonic way to swap variables - Stack Overflow](https://stackoverflow.com/a/14836456/5193334) 121 | 122 | ---------------------------- 123 | 124 | 10. What is the difference between packages and modules in Python? 125 | [Module vs Package - Stack Overflow](https://stackoverflow.com/a/7948672/5193334) 126 | 127 | ---------------------------- 128 | 129 | 11. Can you write multithreading applications in Python? 130 | Yes, one can write multithreading applications in python for cases when a responsive UI is needed, I/O or network is the bottleneck. C extensions and C code can run in parallel in Python, however, only one active Python thread can run in Python. The GIL prevents making use of one CPU core or separate CPUs to run python threads in parallel. 131 | [Multithreading in Python - Stack Overflow](https://stackoverflow.com/a/20939442/5193334) 132 | 133 | ---------------------------- 134 | 135 | 12. Why is GIL needed? 136 | In order to make the dynamic memory management in CPython work correctly, the GIL prevents multiple threads from running Python code at the same time. This is because CPython's dynamic memory management is not thread-safe - it can have those same problems of multiple threads accessing (or worse, disposing) the same resource at the same time. The GIL was a compromise between the two extremes of not allowing multi-threaded code, and having the dynamic memory management be very bulky and slow. 137 | [Is multithreading a myth - Stack Overflow](https://stackoverflow.com/a/44793537/5193334) 138 | 139 | ---------------------------- 140 | 141 | 13. Multithreading vs Mulitprocessing? 142 | *Multiprocessing Pros -* 143 | 144 | - Separate memory space 145 | - Code is usually straightforward 146 | - Takes advantage of multiple CPUs & cores 147 | - Avoids GIL limitations for cPython 148 | - Eliminates most needs for synchronization primitives unless if you use - shared memory (instead, it's more of a communication model for IPC) 149 | - Child processes are interruptible/killable 150 | - Python `multiprocessing` module includes useful abstractions with an - interface much like `threading.Thread` 151 | - A must with cPython for CPU-bound processing 152 | 153 | *Multiprocessing Cons -* 154 | 155 | - IPC a little more complicated with more overhead (communication model vs. - shared memory/objects) 156 | - Larger memory footprint 157 | 158 | *Threading Pros -* 159 | 160 | - Lightweight - low memory footprint 161 | - Shared memory - makes access to state from another context easier 162 | - Allows you to easily make responsive UIs 163 | - cPython C extension modules that properly release the GIL will run in - parallel 164 | - Great option for I/O-bound applications 165 | 166 | *Threading Cons -* 167 | 168 | - cPython - subject to the GIL 169 | - Not interruptible/killable 170 | - If not following a command queue/message pump model (using the `Queue` module)- , then manual use of synchronization primitives become a necessity (decisions are needed for the granularity of locking) 171 | - Code is usually harder to understand and to get right - the potential for - race conditions increases dramatically 172 | 173 | [Difference between the modules - Stack Overflow](https://stackoverflow.com/a/18114882/5193334) 174 | 175 | ---------------------------- 176 | 177 | 14. What is the `__dict__` attribute of an object in Python? 178 | Basically it contains all the attributes which describe the object in question. It can be used to alter or read the attributes. Quoting from the documentation for `__dict__` 179 | 180 | > A dictionary or other mapping object used to store an object's (writable) attributes 181 | 182 | [`__dict__` attribute in python - Stack Overflow](https://stackoverflow.com/a/19907498/5193334) 183 | 184 | 15. What is `self` keyword used for in python? 185 | `self` represents the current instance of a class which is used to access the properties and methods of a class. 186 | [`self` explained to a beginner - Stack Overflow](https://stackoverflow.com/a/6990217/5193334) 187 | [The purpose of self -Stack Overfloww](https://stackoverflow.com/a/2709832/5193334) 188 | 189 | ---------------------------- 190 | 191 | 16. What is the `__init__` function used for? 192 | The `__init__` function is called a constructor, or initializer, and is automatically called when you create a new instance of a class. Within that function, the newly created object is assigned to the parameter self. 193 | [Why do we use `__init__` in python - Stack Overflow](https://stackoverflow.com/a/8609238/5193334) 194 | 195 | ---------------------------- 196 | 197 | 17. What is pickling and unpickling in python? 198 | “Pickling” is the process whereby a Python object hierarchy is converted into a byte stream, and “unpickling” is the inverse operation, whereby a byte stream (from a binary file or bytes-like object) is converted back into an object hierarchy. Pickling (and unpickling) is alternatively known as “serialization”, “marshalling,” or “flattening”; however, to avoid confusion, the terms used here are “pickling” and “unpickling”. 199 | [Code Example - Stack Overflow](https://stackoverflow.com/a/7502013/5193334) 200 | 201 | ---------------------------- 202 | 203 | 18. Pickling usecases? 204 | - Saving a program's state data to disk so that it can carry on where it left off when restarted (persistence) 205 | - Sending python data over a TCP connection in a multi-core or distributed system (marshalling) 206 | - Storing python objects in a database 207 | - Converting an arbitrary python object to a string so that it can be used as a dictionary key (e.g. for caching & memoization). 208 | [Common pickling usecases - Stack Overflow](https://stackoverflow.com/a/3439921/5193334) 209 | 210 | ---------------------------- 211 | 212 | 19. What is the output of `-12 % 10`? 213 | 214 | ```python 215 | >>> -12%10 216 | 8 217 | ``` 218 | 219 | ---------------------------- 220 | 221 | 20. What is the output of `-12 // 10`? 222 | 223 | ```python 224 | >>> -12//10 225 | -2 226 | ``` 227 | 228 | -1.2 is rounded off to -2. 229 | 230 | ---------------------------- 231 | 232 | 21. Why shouldn't you make the default arguments an empty list? 233 | A new list is created once when the function is defined, and the same list is used in each successive call. 234 | Python’s default arguments are evaluated once when the function is defined, not each time the function is called (like it is in say, Ruby). This means that if you use a mutable default argument and mutate it, you will have mutated that object for all future calls to the function as well. 235 | 236 | ```python 237 | def append_to_list(element, list_to_append=[]): 238 | list_to_append.append(element) 239 | return list_to_append 240 | 241 | >>> a = append_to_list(10) 242 | [10] 243 | >>> b = append_to_list(20) 244 | [10, 20] 245 | ``` 246 | 247 | [How to avoid the above issue - Stack Overflow](https://stackoverflow.com/questions/366422/what-is-the-pythonic-way-to-avoid-default-parameters-that-are-empty-lists) 248 | 249 | [Python Gotchas - Python Guide](https://docs.python-guide.org/writing/gotchas/#mutable-default-arguments) 250 | 251 | ---------------------------- 252 | 253 | 22. What is the `id()` function in Python? 254 | The id() function returns a unique id for the specified object. 255 | All objects in Python has its own unique id. 256 | The id is assigned to the object when it is created. 257 | The id is the object's memory address, and will be different for each time you run the program. (except for some object that has a constant unique id, like integers from -5 to 256) 258 | 259 | ---------------------------- 260 | 261 | 23. What is the `yield` keyword used for in Python? 262 | `yield ` is used to create generators. If a compiler detects the `yield` keyword inside a function, that function no longer returns via the `return` statement. Instead it immediately returns a generator object. The python function now maintains state which helps the function to resume where it left off. 263 | 264 | ---------------------------- 265 | 266 | 24. What is a generator? 267 | A generator is simply a function which returns an object on which you can call next, such that for every call it returns some value, until it raises a `StopIteration` exception, signaling that all values have been generated. Such an object is called an iterator. Generator is a subtype of `Iterator`. 268 | Normal functions return a single value using return, just like in Java. In Python, however, there is an alternative, called yield. Using yield anywhere in a function makes it a generator. Observe this code: 269 | 270 | ```python 271 | >>> def myGen(n): 272 | ... yield n 273 | ... yield n + 1 274 | ... 275 | >>> g = myGen(6) 276 | >>> next(g) 277 | 6 278 | >>> next(g) 279 | 7 280 | >>> next(g) 281 | Traceback (most recent call last): 282 | File "", line 1, in 283 | StopIteration 284 | ``` 285 | 286 | As you can see, myGen(n) is a function which yields n and n + 1. Every call to next yields a single value, until all values have been yielded. for loops call next in the background, thus: 287 | 288 | ```python 289 | >>> for n in myGen(6): 290 | ... print(n) 291 | ... 292 | 6 293 | 7 294 | ``` 295 | 296 | [Understanding generators in python - Stack Overflow](https://stackoverflow.com/a/1756156/5193334) 297 | 298 | ---------------------------- 299 | 300 | 25. Why use generators? 301 | - When you are calculating a large data set and are unsure whether you'll need the entire result. 302 | 303 | - When you don't want to allocate memory for the entire result at once (space). 304 | 305 | - When you don't want to wait for the entire result to be calculated (time). 306 | 307 | - Generators allow for a natural way to create infinite streams. 308 | 309 | ```python 310 | >>> def fib(): 311 | ... a, b = 0, 1 312 | ... while True: 313 | ... yield a 314 | ... a, b = b, a + b 315 | ... 316 | >>> import itertools 317 | >>> list(itertools.islice(fib(), 10)) 318 | [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] 319 | ``` 320 | 321 | [What can you use python generators for - Stack Overflow](https://stackoverflow.com/a/102632/5193334) 322 | [Understanding generators in python -Stack Overflow](https://stackoverflow.com/a/1756156/5193334) 323 | 324 | ---------------------------- 325 | 326 | 26. What are iterators in python? Write a custom iterator. 327 | Iterators are objects that allow you to traverse through all the elements of a collection and return one element at a time. More specifically, we say that an iterator is an object that implements the iterator protocol. 328 | An iterator protocol is nothing but a specific class in Python which further has the `__iter__()` and `__next__()` methods. 329 | [Python Iterators: A Step-By-Step Introduction – dbader.org](https://dbader.org/blog/python-iterators) 330 | 331 | [Step by Step Guide on Iterators](https://www.mygreatlearning.com/blog/iterator-in-python/) 332 | 333 | ```python 334 | # A custom fibonacci iterator 335 | class Fiborator: 336 | 337 | def __init__(self, stop=100) -> None: 338 | self.stop = stop 339 | self.start_a = 0 340 | self.start_b = 1 341 | 342 | def __iter__(self): 343 | return self 344 | 345 | def __next__(self): 346 | while self.start_a <= self.stop: 347 | result = self.start_a 348 | self.start_a, self.start_b = self.start_b, self.start_a + self.start_b 349 | return result 350 | raise StopIteration 351 | 352 | fib = Fiborator() 353 | 354 | for i in fib: 355 | print(i) 356 | ``` 357 | 358 | ---------------------------- 359 | 360 | 27. Iterable vs Iterator vs Iteration? 361 | An _iterable_ is an object that has an `__iter__` method which returns an iterator, or which defines a `__getitem__` method that can take sequential indexes starting from zero (and raises an `IndexError` when the indexes are no longer valid). So an iterable is an object that you can get an iterator from. 362 | An _iterator_ is an object with a `next` (Python 2) or `__next__` (Python 3) method. 363 | [Iterable vs Iterator vs Iteration - Stack Overflow](https://stackoverflow.com/questions/9884132/what-exactly-are-iterator-iterable-and-iteration) 364 | 365 | [Iterators vs Generators - Stack Overflow](https://stackoverflow.com/questions/2776829/difference-between-pythons-generators-and-iterators) 366 | 367 | ---------------------------- 368 | 369 | 28. What is the difference between `__iter__()` and `__next__()`? 370 | Iterables have an `__iter__` method which returns an iterator. Iterators have a `__next__` method which returns either their next value or raise a `StopIteration` 371 | 372 | ---------------------------- 373 | 374 | 29. What is a context manager? How are they different from `try ... finally`? 375 | Context managers allow you to allocate and release resources precisely when you want to. This exempts the developer from managing external resources such as files, network connections and locks. Sometimes a program will retain those resources forever. This kind of issue is called a memory leak because the available memory gets reduced every time you create and open a new instance without closing it. 376 | For example, a common problem that can arise when developers are working with databases is when a program keeps creating new connections without releasing or reusing them. In that case, the database backend can stop accepting new connections. This might require an admin to log in and manually kill those stale connections to make the database usable again. 377 | The `with` statement allows you to setup and teardown the instances in a safer, cleaner and reusable manner. Whereas in a `try ... finally` you still have to explicitly close the connection. 378 | 379 | The context manager object results from evaluating the `expression` after `with`. In other words, `expression` must return an object that implements the **context management protocol**. This protocol consists of two special methods: 380 | 381 | 1. [`.__enter__()`](https://docs.python.org/3/library/stdtypes.html#contextmanager.__enter__) is called by the `with` statement to enter the runtime context. 382 | 2. [`.__exit__()`](https://docs.python.org/3/library/stdtypes.html#contextmanager.__exit__) is called when the execution leaves the `with` code block. 383 | 384 | [Python With Statement - Real Python](https://realpython.com/python-with-statement/) 385 | 386 | ---------------------------- 387 | 388 | 30. How can you copy an object in Python? How to make a deep copy? 389 | One can use the `copy` module to create a true copy of the object. 390 | The difference between shallow and deep copying is only relevant for compound objects (objects that contain other objects, like lists or class instances): 391 | - A _shallow copy_ constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original. 392 | - A _deep copy_ constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original. 393 | 394 | ---------------------------- 395 | 396 | 31. How does garbage collection work in python? 397 | Reference Counting - 398 | The main garbage collection mechanism in CPython is Reference Counts. Whenever you create an object in Python, the underlying C Object has both a Python type and a reference count. 399 | At a very basic level, a Python object's reference count is incremented whenever the object is referenced, and it is decremented when an object is delinked. If the reference count for the object is 0, the memory for the object is released. 400 | A few ways to increase the reference count - 401 | 402 | - Assigning an object to a variable. 403 | - Adding an object to a data structure, such as appending to a list or adding as a property on a class instance. 404 | - Passing the object as an argument to a function. 405 | 406 | ```python 407 | >>> import sys 408 | >>> a = 'my-string' 409 | >>> sys.getrefcount(a) 410 | 2 411 | ``` 412 | 413 | Generational Garbage Collection - 414 | While reference counting is easy to implement and makes the life of a developer easier, it has its drawbacks. It will not clear objects with cyclical references. 415 | 416 | A reference cycle is when a object refers to itself. The simplest example would be - 417 | 418 | ```python 419 | >>> a = [1,2] 420 | >>> a.append(a) 421 | ``` 422 | 423 | In such cases, the generational collector runs after a certain threshold is reached for it's generation and clears all the self referencing objects that are not being referenced by any other object externally. 424 | 425 | ---------------------------- 426 | 427 | 32. What are decorators in Python? 428 | By definition, a decorator is a function that takes another function and extends the behavior of the latter function without explicitly modifying it. 429 | An excellent article on what are decorators - [Primer on Python Decorators - Real Python](https://realpython.com/primer-on-python-decorators/) 430 | 431 | ---------------------------- 432 | 433 | 33. Is python pass-by-value or pass-by-reference? 434 | [Amazing Explanation by Robert Heaton](https://robertheaton.com/2014/02/09/pythons-pass-by-object-reference-as-explained-by-philip-k-dick/) 435 | 436 | ---------------------------- 437 | 438 | 34. How are lists built internally in python? 439 | [Python List Implementation - Laurent Luce](http://www.laurentluce.com/posts/python-list-implementation/) 440 | 441 | [How is Python's List Implemented? - Stack Overflow](https://stackoverflow.com/questions/3917574/how-is-pythons-list-implemented) 442 | 443 | ---------------------------- 444 | 445 | 35. What does if `__name__ == "__main__":` do? 446 | 447 | [Answer and examples - Stack Overflow](https://stackoverflow.com/a/419185/5193334) 448 | 449 | ---------------------------- 450 | 451 | 35. Is Python interpreted, or compiled, or both? 452 | 453 | [Explanation - Stack Overflow](https://stackoverflow.com/a/6889798/5193334) 454 | 455 | --- 456 | 457 | 36. How is memory managed in python? 458 | 459 | There are two parts of memory: 460 | 461 | - stack memory 462 | - heap memory 463 | 464 | The methods/method calls and the references are stored in **stack memory** and all the values objects are stored in a **private heap**. 465 | 466 | ##### Work of Stack Memory 467 | 468 | The allocation happens on contiguous blocks of memory. We call it stack memory allocation because the allocation happens in the function call stack. The size of memory to be allocated is known to the compiler and whenever a function is called, its variables get memory allocated on the stack. 469 | 470 | It is the memory that is only needed inside a particular function or method call. When a function is called, it is added onto the program’s call stack. Any local memory assignments such as variable initializations inside the particular functions are stored temporarily on the function call stack, where it is deleted once the function returns, and the call stack moves on to the next task. This allocation onto a contiguous block of memory is handled by the compiler using predefined routines, and developers do not need to worry about it. 471 | 472 | **Example:** 473 | 474 | ```python 475 | def func(): 476 | 477 | # All these variables get memory 478 | # allocated on stack` 479 | 480 | a = 20 481 | b = [] 482 | c = "" 483 | ``` 484 | 485 | ##### Work of Heap Memory 486 | 487 | The memory is allocated during the execution of instructions written by programmers. Note that the name heap has nothing to do with the heap data structure. It is called heap because it is a pile of memory space available to programmers to allocated and de-allocate. The variables that are needed outside of method or function calls or are shared within multiple functions globally are stored in Heap memory. 488 | 489 | **Example:** 490 | 491 | ```python 492 | # This memory for 10 integers 493 | # is allocated on heap. 494 | a = [0]*10 495 | ``` 496 | 497 |         [Memory Management in Python - GeeksforGeeks](https://www.geeksforgeeks.org/memory-management-in-python/) 498 | 499 | --- 500 | 501 | 37. What is the difference between `range` and `xrange`? 502 | 503 | While `xrange` has been deprecated starting from python 3 (because range now does `xrange` used to do), the difference was that `range` returned a static list while `xrange` returned a generator object. Hence the objects were created on the fly. Leading to lesser time and memory consumption. 504 | 505 | --- 506 | 507 | 38. More such useful questions/answers shared by [InterviewBit](https://www.interviewbit.com/python-interview-questions/#what-is-python) 508 | 509 | --- 510 | 511 | ### Programming Questions 512 | 513 | 1. How can I reload a previously imported module? 514 | 515 | ```python 516 | from importlib import reload 517 | import foo 518 | 519 | if is_changed(foo): 520 | reload(foo) 521 | ``` 522 | 523 | 2. What will be the output of the following code? 524 | 525 | ```python 526 | >>> a = [[]]*3 527 | >>> a[1].append(1) 528 | >>> print(a) 529 | [[1], [1], [1]] 530 | ``` 531 | 532 | 3. Write a `timeit` decorator to measure the time of function execution - 533 | 534 | ```python 535 | import functools 536 | from datetime import datetime 537 | 538 | def timeit(func): 539 | @functools.wraps(func) 540 | def wrapper_timeit(*args, **kwargs): 541 | start = datetime.now() 542 | value = func(*args, **kwargs) 543 | end = datetime.now() - start 544 | print(f"Finished {func.__name__} in {end}") 545 | return value 546 | return wrapper_timeit 547 | 548 | @timeit 549 | def test_decorator(): 550 | for i in range(200000000): 551 | pass 552 | 553 | test_decorator() 554 | 555 | >>> Finished test_decorator in 0:00:03.730084 556 | ``` 557 | 558 | 4. Write a decorator function that will catch errors and repeat the function execution n number of times - 559 | 560 | ```python 561 | def retry(num_times=3): 562 | def decorator_retry(func): 563 | @functools.wraps(func) 564 | def wrapper_retry(*args, **kwargs): 565 | for i in range(num_times): 566 | try: 567 | return func(*args, **kwargs) 568 | except Exception as e: 569 | print("Retry number: ", i+1) 570 | if i+1 == num_times: 571 | print("Retry limit reached") 572 | raise 573 | 574 | return wrapper_retry 575 | return decorator_retry 576 | ``` 577 | 578 | 5. We have the following code with the unknown function f(). In f(), we do not want to use a return, instead, we may want to use a generator. 579 | 580 | ```python 581 | for x in f(5): 582 | print(x,) 583 | ``` 584 | 585 | The output looks like this: 586 | 587 | ```python 588 | 0 1 8 27 64 589 | ``` 590 | 591 | Answer - 592 | 593 | ```python 594 | def f(x): 595 |     for n in range(x): 596 |     yield n**3 597 | 598 | for i in f(5): 599 | print(i,) 600 | ``` 601 | 602 | 6. How to translate string containing binary code into a number? Write a function to do this - 603 | 604 | ```python 605 | def get_int(number): 606 | start = 2 ** (len(number) - 1) 607 | total = 0 608 | 609 | for i in number: 610 | total += int(i) * start 611 | start = start//2 612 | 613 | return total 614 | 615 | print(get_int('11111111')) 616 | ``` 617 | 618 | 7. Write a function that returns a string of numbers from 0 to 100, "0123456789101112..." 619 | 620 | ```python 621 | for i in range(0,100): 622 | print(i, end='') 623 | ``` 624 | 625 | 8. What will be the output of the following code? 626 | 627 | ```python 628 | class Person: 629 | def __init__(self, name): 630 | __name__= name 631 | 632 | def getAge(self): 633 | print(__name__) 634 | 635 | p = Person("John") 636 | p.getAge() 637 | 638 | # Output 639 | >>> __main__ 640 | ``` 641 | 642 |         While `__name__` is a built-in variable, since we are setting it inside a function, it's         scope is only limited to that function. So when we refer the variable outside of that         function, the value for `__name__` remains as `__main__`. 643 | 644 | 9. What is the output of the following code? 645 | 646 | ```python 647 | x = [[0], [1]] 648 | print(len(' '.join(list(map(str, x))))) 649 | 650 | #Output 651 | >>> x = [[0], [1]] 652 | >>> print(len(' '.join(list(map(str, x))))) 653 | 7 654 | #Simplifying 655 | >>> print(' '.join(list(map(str, x)))) 656 | [0] [1] 657 | >>> print(list(map(str, x))) 658 | ['[0]', '[1]'] 659 | ``` --------------------------------------------------------------------------------