├── Inheritance.png ├── Object Creation.py ├── ExpandDicts.py ├── EggDrop.py └── README.md /Inheritance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/baliyanvinay/Python-Interview-Preparation/HEAD/Inheritance.png -------------------------------------------------------------------------------- /Object Creation.py: -------------------------------------------------------------------------------- 1 | class Car_Wheel: 2 | total_wheels = 0 3 | def __new__(self, value): 4 | # new method is called first for object creation 5 | if Car_Wheel.total_wheels < 5: 6 | Car_Wheel.total_wheels += 1 7 | return super().__new__(self) 8 | else: 9 | raise Exception('Only five car wheels are allowed!!') 10 | 11 | def __init__(self, side): 12 | # Initiliazer is called for initializing values 13 | self.side = side 14 | print(f"{self.side} created") 15 | 16 | 17 | if __name__ == '__main__': 18 | wheel_01 = Car_Wheel('Front Left') 19 | wheel_02 = Car_Wheel('Front Right') 20 | wheel_03 = Car_Wheel('Rear Left') 21 | wheel_04 = Car_Wheel('Rear Right') 22 | wheel_05 = Car_Wheel('Puncture wheel') 23 | 24 | try: 25 | print('Trying creating extra wheel') 26 | wheel_06 = Car_Wheel('No side') 27 | except Exception as msg: 28 | print(msg) 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /ExpandDicts.py: -------------------------------------------------------------------------------- 1 | # From a given nested dict, normalize it into dict. 2 | 3 | def normalize_dict(input_dict): 4 | ''' 5 | Expanding nested dicts into normalized dict using recursion 6 | ''' 7 | result = {} 8 | for key, val in input_dict.items(): 9 | if isinstance(val, dict): 10 | result.update(normalize_dict(val)) 11 | else: 12 | result[key] = val 13 | 14 | return result 15 | 16 | 17 | sample_dict = { 18 | "key1": "val1", 19 | "key2": { 20 | "key2_1": "val2_1", 21 | "key2_2": { 22 | "key2_2_1": "val2_2_1", 23 | "key2_2_2": "val2_2_2", 24 | }, 25 | "key2_3": "val2_3" 26 | }, 27 | "key3": "val3", 28 | "key4": "val4" 29 | } 30 | 31 | output_dict = normalize_dict(sample_dict) 32 | print(output_dict) 33 | 34 | ## Expected Output 35 | # output_dict = { 36 | # "key1": "val1", 37 | # "key2_1": "val2_1", 38 | # "key2_2_1": "val2_2_1", 39 | # "key2_2_2": "val2_2_2", 40 | # "key2_3": "val2_3", 41 | # "key3": "val3", 42 | # "key4": "val4" 43 | # } -------------------------------------------------------------------------------- /EggDrop.py: -------------------------------------------------------------------------------- 1 | # Provided that you have two eggs, find out the how many steps are required to get the floor that breaks the egg 2 | # Given: Two eggs and hidden floor number that breaks the egg 3 | 4 | def egg_threshold(total_floor, egg_break_floor): 5 | ''' 6 | Algorithm in action: Binary traversal and Linear tranversal 7 | Time Complexity: 8 | ''' 9 | steps = 0 10 | ground_floor = 0 11 | 12 | # First Egg | Broke test with binary triversal 13 | while True: 14 | steps += 1 15 | drop_floor = (ground_floor+total_floor)//2 # Mid Floor 16 | print(f"Step {steps} at floor {drop_floor}") 17 | 18 | if egg_break_floor == drop_floor: 19 | break 20 | elif drop_floor > egg_break_floor: # Egg broke: Drop floor is greater than egg_break_floor 21 | print(f"------------Egg 01 broke at floor {drop_floor}-------------\n") 22 | drop_floor = ground_floor 23 | break 24 | else: 25 | ground_floor = drop_floor 26 | 27 | # Second Egg | Linear triversal for the second egg 28 | while drop_floor != egg_break_floor: 29 | steps += 1 30 | drop_floor += 1 31 | print(f"Step {steps} at floor {drop_floor}") 32 | print(f"------------Egg 02 broke at floor {drop_floor}-------------\n") 33 | 34 | return steps 35 | 36 | total_floor, break_floor = 30, 18 37 | print("Egg Break Floor estimated in {} steps".format(egg_threshold(total_floor, break_floor))) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python Interview Preparation 2 | 3 | ## 1. Explain 'Everything in Python is an object'. 4 | What's an object? An object in a object-oriented programming language is an entity that contains data along with some methods for operations related to that data.
5 | Everything from numbers, lists, strings, functions and classes are python objects. 6 | ```python 7 | >>> a = 10.5 8 | >>> a.is_integer() # Float type has is_integer() method cause a is an object of float class 9 | False 10 | >>> type(a) 11 | 12 | >>> def func() 13 | .... pass 14 | >>> type(func) 15 | 16 | >>> # like functions, classes are also objects of 'type' class 17 | ``` 18 | Look at the below example 19 | ```python 20 | >>> var = 'Tom' # Object 'Tom' is created in memory and name 'var' is binded to it. 21 | >>> var = 'Harry' # Another object is created however note that name 'var' is now binded to 'Harry' but 'Tom' is still somewhere in memory and is unaffected. 22 | ``` 23 | Ref: [Nina Zakharenko - Memory Management in Python - The Basics - PyCon 2016](https://www.youtube.com/watch?v=F6u5rhUQ6dU) 24 | 25 | ## 2. What is mutable and immutable objects/data types in Python? 26 | Mutation generally refers to 'change'. So when we say that an object is mutable or immutable we meant to say that the value of object can/cannot change.
27 | When an object is created in Python, it is assigned a type and an id. An object/data type is mutable if with the same id, the value of the object changes after the object is created. 28 | ## 29 | Mutable objects in Python 30 | -- Objects that can change after creation. Lists, byte arrays, sets, and dictionaries. 31 | ```python 32 | >>> list_var = [17, 10] 33 | >>> list_var 34 | [17, 10] 35 | >>> id(list_var) 36 | 2289772854208 37 | >>> list_var += [17] 38 | >>> list_var 39 | [17, 10, 17] 40 | >>> id(list_var) # ID of the object didn't change. 41 | 2289772854208 42 | ``` 43 | 44 | ## 45 | Immutable objects in Python 46 | -- Numeric data types, strings, bytes, frozen sets, and tuples. 47 | ```python 48 | >>> # Example of tuples 49 | >>> tuple_var = (17,) 50 | >>> tuple_var 51 | (17,) 52 | >>> id(tuple_var) 53 | 1753146091504 54 | >>> tuple_var += (10,) 55 | >>> tuple_var 56 | (17,10) 57 | >>> id(tuple_var) # ID changes when made changes in object. 58 | 1753153466880 59 | ``` 60 | ## 61 | Mutable objects and functon arguments 62 | ```python 63 | def sample_func(sample_arg): 64 | sample_agr.append(10) 65 | # No need to return the obj since it is utilizing the same memory block 66 | 67 | sample_list = [7, 8, 9] 68 | sample_func(sample_list) 69 | print(sample_list) # [7, 8, 9, 10] 70 | ``` 71 | 72 | ## 3. What is the difference between list and tuples in Python? 73 | 74 | | Parameter | List | Tuples | 75 | | :-------------:|:-------------:| :-------------:| 76 | | Syntax | Square brackets or list keyword | Round brackets/parenthesis or tuple keyword | 77 | | Nature | Mutable | Immutable | 78 | | Item Assignment | Possible | Not Possible | 79 | | Reusablity | Copied | Not Copied | 80 | | Performance | Relatively slower | Relatively faster | 81 | | Memory | Large-Extra than the element size | Fixed to element size | 82 | 83 | Note: It is not required for tuples to have parenthesis, one can also define tuple ```python a = 2, 3, 4 ``` 84 | 85 | ### Memory Allocation of Tuple and List 86 | Tuple does not allot any extra memory during construction because it will be immutable so does not have to worry about addition of elements. 87 | ```python 88 | >>> tuple_var = tuple() 89 | >>> tuple_var.__sizeof__() # take 24 bytes for empty tuple 90 | 24 91 | >>> tuple_var = (1,2) # additional 8 bytes for each integer element 92 | >>> tuple_var.__sizeof__() 93 | 40 94 | ``` 95 | List over-allocates memory otherwise list.append would be an O(n) operation. 96 | ```python 97 | >>> list_var = list() 98 | >>> list_var.__sizeof__() # take 40 bytes for empty list 99 | 40 100 | >>> list_var.append(1) 101 | >>> list_var.__sizeof__() # append operation allots extra memory size considering future appends 102 | 72 103 | >>> list_var 104 | [1] 105 | >>> list_var.append(2) 106 | >>> list_var.__sizeof__() # size remains same since list has space available 107 | 72 108 | >>> list_var 109 | [1,2] 110 | ``` 111 | ### Reusablity 112 | Tuple literally assigns the same object to the new variable while list basically copies all the elements of the existing list. 113 | ```python 114 | >>> # List vs Tuples | Reused vs. Copied 115 | >>> old_list = [1,2] 116 | >>> old_list.append(3) 117 | >>> old_list 118 | [1, 2, 3] 119 | >>> id(old_list) 120 | 2594206915456 121 | >>> old_list.__sizeof__() 122 | 88 123 | 124 | >>> # Copying list 125 | >>> new_list = list(old_list) 126 | >>> new_list 127 | [1, 2, 3] 128 | >>> id(new_list) # new id so new list is created 129 | 2594207110976 130 | >>> new_list.__sizeof__() # size is also not same as old_list 131 | 64 132 | 133 | >>> Tuple Copy 134 | >>> old_tuple = (1,2) 135 | >>> id(old_tuple) 136 | 2594206778048 137 | >>> old_tuple.__sizeof__() 138 | 40 139 | >>> new_tuple = tuple(old_tuple) 140 | >>> id(new_tuple) # same id as old_tuple 141 | 2594206778048 142 | >>> new_tuple.__sizeof__() # also same size as old_tuple since it is refering to old_tuple 143 | 40 144 | ``` 145 | 146 | ### Performance-Speed 147 | Tuples and List takes almost same time in indexing, but for construction, tuple destroys list. See example, 'List vs Tuple'. 148 | 149 | ## 4. How is memory managed in Python? 150 | Unlike other programming languages, python stores references to an object after it is created. For example, an integer object 17 might have two names(variables are called names in python) a and b. The memory manager in python keeps track of the reference count of each object, this would be 2 for object 17. Once the object reference count reaches 0, object is removed from memory.
151 | The reference count 152 | - increases if an object is assigned a new name or is placed in a container, like tuple or dictionary. 153 | - decreases when the object's reference goes out of scope or when name is assigned to another object. 154 | Python's garbage collector handles the job of removing objects & a programmer need not to worry about allocating/de-allocating memory like it is done in C. 155 | ```python 156 | >>> import sys 157 | >>> sys.getrefcount(17) 158 | >>> 11 159 | >>> a = 17 160 | >>> b = 17 161 | >>> a is b 162 | >>> True 163 | >>> sys.getrefcount(17) 164 | >>> 13 # addition of two 165 | ``` 166 | 167 | ## 5. Explain exception handling in Python. 168 | Exception handling is the way by which a programmer can control an error within the program without breaking out the flow of execution. 169 | ```python 170 | try: 171 | # Part which might cause an error 172 | except TypeError: 173 | # What happens when error occurs | In this case what happens what a TypeError occurs 174 | else: 175 | # what happens if there is no exception | Optional 176 | finally: 177 | # Executed after try and except| always executed | Optional 178 | ``` 179 | Examples :- TypeError, ValueError, ImportError, KeyError, IndexError, NameError, PermissionError, EOFError, ZeroDivisionError, StopIteration 180 | 181 | ## 6. Explain some changes in Python 3.8 182 | Positional arguments representation 183 | ```python 184 | def sum(a,b,/,c=10): 185 | return a+b+c 186 | sum(10,12,c=12) 187 | ``` 188 | F String can also do operations 189 | ```python 190 | a,b = 10, 12 191 | f"Sum of a and b is {a+b}" 192 | f"Value of c is {(c := a+b)}" 193 | ``` 194 | 195 | ## 7. Explain Python Design Patterns. 196 | Ref: https://www.youtube.com/watch?v=o1FZ_Bd4DSM 197 | 198 | ## 8. How would you load large data file in Python? 199 | The best way is to load data in chunks, Pandas provides option to define chunk size while loading any file. 200 | ```python 201 | for chunk in pd.read_csv(file.csv, chunksize=1000): 202 | process(chunk) 203 | 204 | pd.read_csv('file.csv', sep='\t', iterator=True, chunksize=1000) 205 | ``` 206 | Another way is to use context manager. 207 | ```python 208 | with open("log.txt") as infile: 209 | for line in infile: 210 | do_something_with(line) 211 | ``` 212 | 213 | ## 9. Explain Generators and use case of it. 214 | A function or method which uses the yield statement is called a generator function. Such a function, when called, always returns an iterator object which can be used to execute the body of the function: calling the iterator’s iterator.\__next__()method will cause the function to execute until it provides a value using the yield statement.
215 | When the function executes a return statement or falls off the end, a StopIteration exception is raised and the iterator will have reached the end of the set of values to be returned. Note that a generator can have n numbers of yield statements 216 | #### Use Case 217 | Generators are good for calculating large sets of results where you don't know if you are going to need all results, or where you don't want to allocate the memory for all results at the same time. 218 | 219 | ```python 220 | # Search function as generator, effective for returning some set as result with functionality like 'Load 10 more items' 221 | def search_result(keyword): 222 | while keyword in dataset: 223 | yield matched_data 224 | 225 | search_object = search_result('keyword') 226 | # type(search_function) --> 227 | 228 | search_object.__next__() 229 | ``` 230 | Note: You can only iterate over a generator once, if you try to loop over it second time it will return nothing. Generators also do not store all the values in memory, they generate the values on the fly 231 | ```python 232 | mygenerator = (x*x for x in range(3)) 233 | ``` 234 | When you call a generator function, the code you have written in the function body does not run. The function only returns the generator object. 235 | Example: https://github.com/baliyanvinay/Python-Advanced/blob/main/Generator.py 236 | 237 | ## 10. Is there a sequence in defining exceptions in except block for exception handling? 238 | Yes can be defined in a tuple. From left to right will be executed based on the exception raised. 239 | ```python 240 | try: 241 | pass 242 | except (TypeError, IndexError, RuntimeError) as error: 243 | pass 244 | ``` 245 | 246 | ## 11. Explain Closures in Python 247 | A closure is a functionality in Python where some data is memorized in the memory for lazy execution. Decorators heavily rely on the concept of closure.
248 | To create a closure in python:- 249 | 1. There must be a nested function(function within a enclosing/outer function) 250 | 2. The nested function must refer to a value defined in the enclosing function 251 | 3. The enclosing function must return(not call it) the nested function. 252 | ```python 253 | def enclosing_function(defined_value): 254 | def nested_function(): 255 | return defined_value+some_operation 256 | return nested_function 257 | 258 | closure_function = enclosing_function(20) 259 | closure_function() # returns 20+some_operation 260 | ``` 261 | Objects are data with methods attached, closures are functions with data attached. 262 | 263 | ## 12. How to make a chain of function decorators? 264 | ```python 265 | def make_bold(fn): 266 | def wrapped(): 267 | return "" + fn() + "" 268 | return wrapped 269 | 270 | def make_italic(fn): 271 | def wrapped(): 272 | return "" + fn() + "" 273 | return wrapped 274 | 275 | @make_bold 276 | @make_italic 277 | def index(): 278 | return "hello world" 279 | 280 | print(index()) 281 | ## returns "hello world" 282 | 283 | ``` 284 | 285 | ## 13. Three different ways to fetch every 3rd item of a list 286 | Using index jump 287 | ```python 288 | >>> example_list = [0,1,2,3,4,5,6] 289 | >>> example_list = [::3] # returns [0,3,6] 290 | ``` 291 | Using list comphrehension 292 | ```python 293 | >>> [x for x in example_list if example_list.index(x)%3==0] 294 | >>> [0,3,6] 295 | ``` 296 | Using while loop 297 | ```python 298 | i = 0 299 | while i < len(example_list): 300 | print(example_list[i]) 301 | i += 3 302 | ``` 303 | 304 | ## 14. What is MRO in Python? How does it work? 305 | Method Resolution Order (MRO) is the order in which Python looks for a method in a hierarchy of classes. Especially it plays vital role in the context of multiple inheritance as single method may be found in multiple super classes. 306 | ```python 307 | class A: 308 | def process(self): 309 | print('A') 310 | 311 | class B(A): 312 | pass 313 | 314 | class C(A): 315 | def process(self): 316 | print('C') 317 | 318 | class D(B,C): 319 | pass 320 | 321 | obj = D() 322 | obj.process() 323 | # D -> B -> C -> A -> object 324 | ``` 325 | Note: a class can't be called before its superclass in resolving MRO. Super Class has to be called after derived class 326 | 327 | ## 15. What is monkey patching? How to use it in Python? 328 | Monkey patching refers to the practice of dynamically modifying or extending code at runtime, typically to change the behavior of existing classes, modules, or functions. 329 | 330 | Monkey patching can be useful in several scenarios: 331 | 1. Testing: It allows to replace parts of code during testing with mock objects or functions. 332 | 2. Hotfixes: In situations where you can't immediately deploy a fix to production code, monkey patching can be used as a temporary solution to address critical issues without waiting for a formal release cycle. 333 | 3. Extending functionality: It enables to add new features or alter the behavior of existing code without modifying the original source. This can be useful when working with third-party libraries or legacy code that you can't modify directly. 334 | 335 | ```python 336 | # Original module 337 | class OriginalClass: 338 | def method(self): 339 | return "Original method" 340 | 341 | # Monkey patching 342 | def new_method(self): 343 | return "Patched method" 344 | 345 | # Patching the class method 346 | OriginalClass.method = new_method 347 | # Using the patched code 348 | obj = OriginalClass() 349 | print(obj.method()) # Output: "Patched method" 350 | ``` 351 | 352 | ## 16. What is the difference between staticmethod and classmethod? 353 | | Parameter | Class Method | Static Method | 354 | | :-------------:|:-------------:| :-------------:| 355 | | Decorator | @classmethod | @staticmethod | 356 | | Use Case| More widely used as a factory method to class | Acts as utility functions | 357 | | Scope | Bound to the classs and not objects | Also bound to class and not objects | 358 | | Behaviour | Can modify the state of the class | Can't access class state | 359 | | Parameter | Takes cls as first parameter | No specific parameter | 360 | ```python 361 | class Circle: 362 | no_of_circles = 0 363 | def __init__(self, radius): 364 | self.radius = radius 365 | Circle.no_of_circles += 1 366 | 367 | @staticmethod 368 | def square(num): 369 | return num**2 370 | 371 | @classmethod 372 | def getCircleCount(cls): 373 | return cls.no_of_circles 374 | ``` 375 | 376 | ## 17. What is the purpose of the single underscore “_” variable in Python? 377 | Python automatically stores the value of last expression in interpreter in single underscore. 378 | ```python 379 | >>> 78 + 89 380 | 167 381 | >>> _ 382 | 167 383 | ``` 384 | Single underscore used in unpacking to ignore value(s). 385 | ```python 386 | >>> a, _, b = (1, 12, 2) 387 | >>> a, *_, b = (1, 12, 13, 14, 15, 16, 2) # only want first and last value 388 | >>> a, *x, b = (1, 12, 13, 14, 15, 16, 2) # note that any other name can also be used 389 | >>> _ # returns a list of elements 390 | [12, 13, 14, 15, 16] 391 | ``` 392 | It is most commonly used in loops where we are not interested in the value returned by the iterator. 393 | ```python 394 | for _ in range(5): 395 | print('Some operations') 396 | ``` 397 | Note that it is entirely the convention to use single underscore in all these ways to avoid having defined extra variable for such operations. 398 | 399 | ## 18. What's the difference between a Python module and a Python package? 400 | #### Module 401 | The module is a Python file that contains collections of functions and global variables and with having a .py extension file. 402 | ### Package 403 | The package is a directory having collections of modules. This directory contains Python modules and also having __init__.py file by which the interpreter interprets it as a Package. 404 | 405 | ## 19. What is a global interpreter lock (GIL)? 406 | Ref: https://www.geeksforgeeks.org/what-is-the-python-global-interpreter-lock-gil/ 407 | 408 | ## 20. Which is faster, list comprehension or for loop? 409 | List comprehensions are generally faster than 'a for loop' because of the implementation of both. One key difference is that 'for loop' generally rounds up more than one statement/operation as compared to 'list comprehension' which has to perform single operation on all the elements. For example, creating a list or update in an existing list is faster when done using list comprehension. 410 | 411 | ## 21. Explain Singleton class and its uses? 412 | Refer to [Python Advanced : Design Patterns](https://github.com/baliyanvinay/Python-Advanced/tree/main/Design%20Patterns) 413 | 414 | ## 22. Explain Meta Classes in Python. 415 | In Python everything is an object, even a class is an object. As a result, a class also must have a type. All classes in Python are of 'type' type. Even the class of 'type' is 'type'. So 'type' is the meta class in Python and to create custom meta class, you would need to inherit from 'type'.
416 | ### Use Case of Meta Class 417 | A meta class is the class of a class. A class is an instance of a metaclass. A metaclass is most commonly used as a class-factory. When you create an object by calling the class, Python creates a new class (when it executes the 'class' statement) by calling the metaclass.
418 | ```python 419 | >>> type(17) # 420 | >>> type(int) # 421 | >>> str.__class__ # 422 | >>> type.__class__ # 423 | ``` 424 | ### Meta Class call 425 | The metaclass is called with the 426 | - name: name of the class, 427 | - bases: tuple of the parent class (for inheritance, can be empty) and 428 | - attributes: dictionary containing attributes names and values. 429 | ```python 430 | def init(self, make): 431 | self.make = make 432 | 433 | # type(name, bases, attrs) 434 | >>> Car = type('Car', (object,), {'__init__': init, '__repr__': lambda self: self.make, 'wheels': 4}) 435 | >>> seltos = Car('Kia') 436 | >>> seltos # Kia 437 | ``` 438 | Ref: [Stack Overflow : Meta Classes](https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python) 439 | 440 | ## 23. Best way to concatenate n number of strings together into one. 441 | The best way of appending a string to a string variable is to use + or +=. This is because it's readable and fast. However in most of the codebases we see use of append and join when joining strings together, this is done for readablity and cleaner code. Sometimes it is more important to have code readablity to actually understand the operation. 442 | ```python 443 | first_name = 'Max ' 444 | last_name = 'Verstappen' 445 | full_name = first_name + last_name 446 | # using join string method 447 | full_name = ''.join( (first_name, last_name) ) # takes in tuple of string in case of multiple values 448 | ``` 449 | 450 | ## 24. Explain briefly about map() and lambda() functions. 451 | ### map(function, iterable) 452 | Map function returns an iterator that applies function to every item in the iterable. In case multiple iterables are passed, the iterator stops when the shortest iterable is exhausted. 453 | ```python 454 | def custom_power(x, y): 455 | return x**y 456 | 457 | values = [1,2,3,4] 458 | powers = [2,1,2] 459 | map_iterator = map(custom_power, values, powers) # will skip the power of 4 460 | print(list(map_iterator)) # [1,2,9] 461 | ``` 462 | ### lambda parameters: expression 463 | Lambda expression yields an anonymous function object. Note that functions created with lambda expressions cannot contain statements or annotations. For example, can't assign variables in lambda definition. 464 | ```python 465 | >>> # lambda [parameter list] : expression 466 | >>> list(map(lambda x: x+10, [1,2,3])) # [11,12,13] 467 | >>> func = lambda x: x+10 # at 0x7fdb99e9c310> 468 | >>> func(25) # returns 35 469 | >>> lambda : 'hello' # with no parameter 470 | ``` 471 | 472 | ## 25. Explain Abstract Classes and its uses. 473 | An abstract class can be considered as a blueprint for other classes. It allows you to create a set of methods that must be created within any child classes built from the abstract class. Or in more general terms, a class which contains one or more abstract methods is called an abstract class.
474 | By default Python does not provide abstract class, ABC module of Python can be used to define an abstract class. 475 | ### Abstract Method 476 | Abstract method is a method that has a declaration but does not have an implementation. This ensures that any class built from this class will have to implement the method. 477 | ```python 478 | from abc import ABC, abstractmethod 479 | class DB_PLugin(ABC): 480 | @abstractmethod 481 | def add_source(self): 482 | pass 483 | ``` 484 | 485 | ## 26. Explain object creation process in detail. Which method is called first? 486 | When an object of a class is created or a class is instantiated, the \__new__() method of class is called. This particular method is resposible for returning a new class object. It can be overriden to implement object creational restrictions on class.
487 | - The constructor of the class is \__new__() and 488 | - the initializer of the class is \__init__().
489 | Initializer is called right after the constructor, if the constructor has not returned a class object, the initializer call is useless.
490 | Note that the reason \__init__() could use class object(self) to initialize is because when the code flow reaches \__init__() the object of the class is already created. 491 | 492 | ## 27. Difference between a class variable and instance variable. 493 | | Parameter | Class Variable | Instance Variable | 494 | | :-------------:|:-------------:| :-------------:| 495 | | Declaration | Inside class definition but outside of any instance methods | Inside constructor method i.e., \__init__ | 496 | | Scope | Shared across all objects | Tied to the object instance | 497 | | Behaviour | Any change is reflected across all instances | Change limited to instances only | 498 | | Access | cls.variable_name | self.variable_name | 499 | 500 | ```python 501 | class Car: 502 | total_cars, wheels = 0, 4 503 | def __init__(self, engine_power): 504 | self.engine_power = engine_power 505 | Car.total_cars += 1 # incremented anytime a new car is added. 506 | 507 | kia_sonet = Car(120) 508 | print(kia_sonet.wheels) # returns 4 509 | kia_sonet.wheels += 1 # extra wheel 510 | print(kia_sonet.wheels) # returns 5 511 | 512 | print(Car.total_cars) # returns 1 513 | print(Car.wheels) # returns 4 not 5 514 | Car.wheel = 6 # two extra wheels 515 | 516 | print(kia_sonet.wheels) # returns 6 now 517 | ``` 518 | Example: https://github.com/baliyanvinay/Python-Advanced/blob/main/Class%20Variables.py 519 | 520 | ## 28. Explain the concept behind dictionary in Python 521 | - A dictionary consists of a collection of key-value pairs. Each key-value pair maps the key to its associated value. 522 | - A key can appear in a dictionary only once. Duplicate keys are not allowed 523 | - Using a key twice in initial dict definition will override the first entry 524 | - Key must be of a type that is immutable. Values can be anything 525 | 526 | ```python 527 | >>> dict_sample_01 = {1: 12, 2: 14, 1: 16} 528 | >>> dict_sample_02 # {1: 16, 2: 14} 529 | >>> dict_sample_02 = dict.fromkeys('123') 530 | >>> dict_sample_02 # {'1': None, '2': None, '3': None} 531 | ``` 532 | 533 | ## 29. Difference between an expression and a statement in Python 534 | A statement is a complete line of code that performs some action, while an expression is any section of the code that evaluates to a value. An expression is also a statement. Note that lambda function in Python only accepts expressions. 535 | 536 | ## 30. Explain threading in Python 537 | ## 31. Can set have lists as elements? 538 | You can't add a list to a set because lists are mutable, meaning that you can change the contents of the list after adding it to the set. You can however add tuples to the set, because you cannot change the contents of a tuple.
539 | The objects have to be hashable so that finding, adding and removing elements can be done faster than looking at each individual element every time you perform these operations.
540 | Some unhashable datatypes: 541 | - list: use tuple instead 542 | - set: use frozenset instead 543 | 544 | ## 32. Is method overloading possible in Python? 545 | Yes method overloading is possible in Python. It can be achived using different number of arguments. 546 | ```python 547 | def increment(value, by=1): 548 | return value+by 549 | 550 | # calling function 551 | increment(5) # returns 6 552 | increment(5, 2) # return 7 553 | ``` 554 | 555 | ## 33. Explain inheritance in Python. 556 | ![Inheritance in Python](https://github.com/baliyanvinay/Python-Interview-Preparation/blob/main/Inheritance.png) 557 | 558 | ## 34. Explain method resolution order for multiple inheritance 559 | ## 35. What can be used as keys in dictionary? 560 | Any immutable object type can be used as dictionary key even functions and classes can also be used as dictionary keys. 561 | #### Why can't list or another dict(mutable object) be used as key in dictionary? 562 | Dict implementation reduces the average complexity of dictionary lookups to O(1) by requiring that key objects provide a "hash" function. Such a hash function takes the information in a key object and uses it to produce an integer, called a hash value. This hash value is then used to determine which "bucket" this (key, value) pair should be placed into. Mutuable objects like list or dict cannot provide a valid /__hash__ method. 563 | 564 | ## 36. Explain shallow and deep copy in Python 565 | For collections that are mutable or contain mutable items, a copy is sometimes needed so one can change one copy without changing the other.
566 | Python follows a pattern of compiling the original source to byte codes, then interpreting the byte codes on a virtual machine. The .pyc file generated contains byte code. 567 | ```python 568 | >>> import copy 569 | >>> sample_1 = [1,2,3] 570 | >>> id(sample_1) 571 | 139865052152768 572 | >>> sample_2 = sample_1 573 | >>> id(sample_2) 574 | 139865052152768 575 | >>> sample_3 = copy.copy(sample_1) 576 | >>> id(sample_3) 577 | 139865052236736 578 | ``` 579 | - 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. 580 | - A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original. 581 | 582 | Ref: [Python Docs Copy](https://docs.python.org/3/library/copy.html) 583 | 584 | ## 37. Why Python generates a .pyc file even when it is an interpreted language? 585 | .pyc files are created by the Python interpreter when a .py file is imported, and they contain the "compiled bytecode" of the imported module/program, the idea being that the "translation" from source code to bytecode (which only needs to be done once) can be skipped on subsequent imports if the .pyc is newer than the corresponding .py file, thus speeding startup a little. But it's still interpreted. 586 | 587 | ## 38. How private varibles are declared in Python? 588 | Python does not have anything called private member however by convention two underscore before a variable or function makes it private. 589 | ```python 590 | class XSpecial: 591 | normal_var = 10 592 | __private_var = 17 593 | 594 | >>> special_obj = XSpecial() 595 | >>> special_obj.normal_var 596 | >>> special_obj.__private_var # AttributeError 597 | ``` 598 | ## 39. Difference between an array and list 599 | | List | Array | 600 | | :-------------:|:-------------:| 601 | | Can contain elements of different data types | Contains homogeneous elements only i.e. same data type | 602 | | No need to import | Need to import via numpy or array | 603 | | Preferred for short sequence of data items | Preferred for large sequence of data items i.e., data analysis | 604 | | Can't perform airthmetic operations on whole list | Great for airthmetic operations | 605 | 606 | ## 40. What is an iterator? How is iterator is different from a generator? 607 | An iterator is an object that implements /__next__, which is expected to return the next element of the iterable object, and raise a StopIteration exception when no more elements are available. 608 | ### Differnce between iterator and generator 609 | | Iterator | Generator | 610 | | :-------------:|:-------------:| 611 | | Class is used to implement an iterator | Function is used to implement a generator | 612 | | Iterator uses iter() and next() functions | Generator uses yield keyword | 613 | | Every iterator is not a generator | Every generator is an iterator | 614 | | Saves the states of local variables every time ‘yield’ pauses execution | An iterator does not make use of local variables | 615 | | Memory efficient | More memory allocated than iterator | 616 | 617 | ## 41. How do you define a dict where several keys has same value? 618 | ```python 619 | products = {} 620 | products.update( 621 | dict.keys(['Apple','Mango','Oranges'], 20) 622 | ) 623 | products.update( 624 | dict.keys(['Pizza','Kind Pizza','Bad Pizza'], 30) 625 | ) 626 | ``` 627 | ## 42. What are different types of namespaces in Python? 628 | Namespace is a way to implement scope. In Python, each package, module, class, function and method function owns a "namespace" in which variable names are resolved. Plus there's a global namespace that's used if the name isn't in the local namespace.
629 | Each variable name is checked in the local namespace (the body of the function, the module, etc.), and then checked in the global namespace. 630 | ### Types of Namespaces 631 | - Local Namespace: The local namespace for a function is created when the function is called, and deleted when the function returns or raises an exception that is not handled within the function. 632 | - Global Namespace: The global namespace for a module is created when the module definition is read in; normally, module namespaces also last until the interpreter quits. 633 | - Built-in Namespace: The namespace containing the built-in names is created when the Python interpreter starts up, and is never deleted. 634 | 635 | ## 43. How can you access attribute of parent class bypassing the attribute with the same name in derived class? 636 | ```python 637 | class Parent: 638 | variable = 12 639 | 640 | class Derived(Parent): 641 | variable = 10 642 | 643 | Parent.variable # returns 12 644 | ``` 645 | 646 | ## 44. Evaulation of boolean expressions 647 | - The expression x and y first evaluates x; if x is false, its value is returned; otherwise, y is evaluated and the resulting value is returned. 648 | - The expression x or y first evaluates x; if x is true, its value is returned; otherwise, y is evaluated and the resulting value is returned. 649 | ```python 650 | x = 'Some Value' 651 | y = 24 652 | z = False 653 | x or y # returns x 654 | z or y # returns y 655 | x and y # returns y 656 | z and x # returns z 657 | ``` 658 | 659 | ## 45. Difference between multiprocessing and multithreading 660 | The threading module uses threads, the multiprocessing module uses processes. The difference is that threads run in the same memory space, while processes have separate memory. This makes it a bit harder to share objects between processes with multiprocessing. Since threads use the same memory, precautions have to be taken or two threads will write to the same memory at the same time. 661 | - Multithreading is concurrent and is used for IO-bound tasks 662 | - Multiprocessing achieves true parallelism and is used for CPU-bound tasks 663 | Use Multithreading if most of your task involves waiting on API-calls, because why not start up another request in another thread while you wait, rather than have your CPU sit idly by. 664 | 665 | ## 46. How to merge two dictionaries together? 666 | ```python 667 | first_dict = {'name': 'Tom', 'age': 44} 668 | second_dict = {'occupation': 'actor', 'nationality': 'British'} 669 | # merging 670 | final_dict = {**first_dict, **second_dict} 671 | ``` 672 | In case any key is repeated in both dictionaries, the second key will hold supremacy. 673 | 674 | ## 47. How do you overload a python constructor? 675 | 676 | ## 48. Explain the below code 677 | ```python 678 | def func(sample_list=[]): 679 | sample_list.append(12) 680 | # print(id(sample_list)) 681 | return sample_list 682 | 683 | print(func()) # [12] 684 | print(func()) # [12,12] 685 | ``` 686 | Since list is mutualable type of data structure, the first time func is called, the list is empty, but when the same function is called twice, the list already has an item. We cans sure of the that by printing the id of the sample_list used in the first, on each subsquent call to the function, the id will return the same value. 687 | 688 | ## 49. Example filter with lambda expression. 689 | ### filter 690 | filter(function, iterable) # function must return True or False 691 | ```python 692 | input_list = ['Delhi', 'Mumbai', 'Noida', 'Gurugram'] 693 | to_match = 'Gurugram' 694 | 695 | matched_list = list(filter(lambda item: item == to_match, input_list)) 696 | matched_list # ['Gurugram'] 697 | ``` 698 | For every single item in the input_list, the condition is checked in the lambda function which returns either True or False. 699 | 700 | ## 50. Explain context managers. 701 | - A context manager in Python is a protocol implemented using the with statement. 702 | - It allows you to perform setup and teardown operations around a block of code. 703 | - Commonly used to manage resources like files, database connections, locks, etc. 704 | 705 | ```python 706 | class MyContextManager: 707 | def __enter__(self): 708 | # Perform setup operations 709 | return self 710 | 711 | def __exit__(self, exc_type, exc_value, traceback): 712 | # Perform cleanup operations 713 | 714 | # Using the context manager 715 | with MyContextManager() as cm: 716 | # Code inside the context 717 | ``` 718 | 719 | 720 | 721 | ## Coding Question 722 | ```python 723 | # Transpose a square matrix of n rows and columns 724 | # Write a function that will place even elements of an array at even indexes and odd at odd indexes. 725 | # Write a function that checks if an array is a subsequence of first array 726 | # Write a function that unzips nested dicts into expanded dicts. 727 | # What is the output of (0,1,2,3,(4,5,6),7,8)[::3] 728 | # Output of a=[[]]*5, a[0].append(1) 729 | # Of a given string, make it an accept python variable name 730 | # from a given list, return the duplicate elements only in sorted order 731 | ``` 732 | --------------------------------------------------------------------------------