├── 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 | 
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 |
--------------------------------------------------------------------------------