What is use of __slots__?

By default, each python object has a __dict__ atttribute which is a dictionary containing all other attributes. You can imagine using a dictionary to store attribute takes some extra space & time for accessing it. But advantage is you can dynamically add attributes to objects of classes.

When you use __slots__, any object created for that class won’t have a __dict__ attribute. Instead, all attribute access is done directly via pointers. It prevents the dynamic creation of attributes. So object instances has faster attribute access and space savings in memory.

If you are going to use thousands or millions of objects of the same class __slots__ is recommended for good performance.

How do you write ternary operator without using if else keywords?

Using and and or conditions:

<condition> and <expression_on_true> or <expression_on_false>

Using tuple, dictionary or lambda expressions:

(<expression_on_false>,<expression_on_true>)[condition]

{True:<expression_on_true>, False: <expression_on_false>}[condition]
# using Lambda
result = (lambda: "Odd", lambda: "Even")[n % 2 == 0]()
print(result)

For more details with examples, read article: Python: Ternary Conditional Operator

What are differences among “foo == True”, “foo is True” and “if foo”?

To check foo really is a boolean and of value True, then is operator is used.

To check true-like value like 1, == is used.

in case of if context: 0, False, None, Empty String, Empty containers (List, Tuple, Dictionaries..etc.) are considered as False.

1 == True
True

1 is True
False

2 == True
False

2 is True
False

True if 2 else False
True

lst = ['item']
lst == True
False

lst is True
False

lst == False
False

True if lst else False
True

0 == False
True

None == False
False

True if None else False
False

'' == False
False

True if '' else False
False

As a rule of thumb, you should always use is with the built-in constants True, False and None.

What are Iterators and Generators? What is use of yield keyword?

In Python List, you can read item one by one means iterate items. So List is iterable. Python iterator object must implement two special methods, __iter__() and __next__(), collectively called the iterator protocol. Most of built-in containers in Python like: list, tuple, string etc. are iterables.

A Generator is a function that returns an object (iterator) which we can iterate over, but one value at a time.

 
>>> my_list = [x**2 for x in range(3)]
>>> my_list
[0, 1, 4]

>>> my_generator = (x**2 for x in range(3))
>>> my_generator
<generator object <genexpr> at 0x7f05636a0570>

>>> for i in my_generator:
...    print(i)
0
1
4

To create a generator function in Python, yield keyword is used instead of return statement in normal function. Generator function contains one or more yield statement which returns an object (iterator) but does not start execution immediately.

 
>>> def square():
...     for i in range(5):
...         yield i ** 2
>>> square_gen = square()

it can be iterated using next() function or used with for in loop which internally calls next function.

 
>>> next(square_gen)
0
>>> next(square_gen)
1
>>> for i in square_gen: # to get rest of the elements
...     print(i)
    
4
9
16

Python executes code in generator function until it comes to a yield statement, then pauses and delivers the object. When you extract another object, Python resumes just after the yield and continues until it reaches another yield. It continues till end. As it only produces one item at a time. So it is memory efficient and used to represent Infinite stream.

What is the difference between a deep copy and a shallow copy?

A shallow copy creates a new object but doesn’t create a copy of nested objects, instead it just copies the reference of nested objects.

A deep copy creates a new object and recursively adds the copies of nested objects present in the original elements. It means that any changes made to a copy of object do not reflect in the original object.

copy.copy() function is used for shallow copy and copy.deepcopy() function is used for deep copy.

>>> import copy

>>> org_list = [{'id': 1}, {'id':2}, {'id':3}]
>>> shallow_list = copy.copy(org_list)

let’s modify shallow list nested object:

>>> shallow_list[1]['name'] = 'two'

>>> shallow_list
[{'id': 1}, {'id': 2, 'name': 'two'}, {'id': 3}]

>>> org_list
[{'id': 1}, {'id': 2, 'name': 'two'}, {'id': 3}]

You can see the change is reflected on original list also. Let’s add new item:

>>> shallow_list.append({'id' : 4})

>>> shallow_list
[{'id': 1}, {'id': 2, 'name': 'two'}, {'id': 3}, {'id': 4}]

>>> org_list
[{'id': 1}, {'id': 2, 'name': 'two'}, {'id': 3}]

You can see it is NOT reflected in original list. So any change in referenced nested elements only will be reflected on both lists.

Let’s reset original list and look into deep copy case

>>> org_list = [{'id': 1}, {'id':2}, {'id':3}]
>>> deep_list = copy.deepcopy(org_list)
>>> deep_list[1]['name'] = 'two'
>>> deep_list.append({'id' : 4})

>>> deep_list
[{'id': 1}, {'id': 2, 'name': 'two'}, {'id': 3}, {'id': 4}]

>>> org_list
[{'id': 1}, {'id': 2}, {'id': 3}]

You can see original list is independent from deep copied list.

How do you get unique values from a python list of objects?

Use a set comprehension. Sets are unordered collections of unique elements, means any duplicates will be removed.

Example:

Suppose class Order has product_id, qty, amount attributes. To get collection of unique product_id:

orders = [ ... ]
products = { order.product_id for order in orders}

If you want to transform a list into collection of unique elements, use set method.

a = ['a', 'b', 'c', 'd', 'b']
b = set(a)
print(b)

Output:

{'b', 'c', 'd', 'a'}

What is Pickling and Unpickling?

Pickling: The process whereby a Python object hierarchy is converted into a byte stream.

Unpickling: Inverse operation of Pickling, whereby a byte stream is converted back into an object hierarchy.

Both are done using the pickle module.

import pickle
obj_orders = [{'product_id': '1233', 'amount': 108}, {'product_id': '1277', 'amount': 639 }]

# pickling
b_stream = pickle.dumps(obj_orders)   

# Unpickling
orders =  pickle.loads(b_stream) 
print(orders)

Output:

[{'product_id': '1233', 'amount': 108}, {'product_id': '1277', 'amount': 639}]

How do you merge two dictionaries in a single expression?

dict_a = {1: 'Ram', 2: "Krishna", 3:"Paramhansh"}
dict_b  = {2: 'Swami', 4: "Vivek"}

merged_dict = {**dict_a, **dict_b}
print(merged_dict)

Output:

{1: 'Ram', 2: 'Swami', 3: 'Paramhansh', 4: 'Vivek'}

What is meaning of and usage of *args, **kwargs?

*args: Used when not sure the number of arguments to be passed to a function. e.g. to pass a stored list or tuple of arguments

def show_args(*args):  
    for arg in args:  
        print (arg) 
    
show_args('Hello', 'World', 'Python')  

Output:

Hello
World
Python

**kwargs: Used when not sure the number of keyword arguments to be passed to a function. e.g. to pass the values of a dictionary as keyword arguments

def show_kwargs(**kwargs):  
    for key, value in kwargs.items(): 
        print (f"{key}: {value}")

show_kwargs(first='1', second='2', third='3')

Output:

first: 1
second: 2
third: 3

How do you get index in ‘for…in’ loop?

To keep track of the indices of list item, use enumerate() function:

list_colors = ['Red','Green','Blue']
for index, color in enumerate(list_colors): 
    print(f"Index: [{index}], Color: {color}")

Output:

Index: [0], Color: Red
Index: [1], Color: Green
Index: [2], Color: Blue

What is use of // and ** operators in Python?

// is a Floor Division operator used for dividing two operands with the result as quotient showing only digits before the decimal point. i.e. 7//5 = 1 and 7.0//5.0 = 1.0.

** is exponentiation operator i.e. 2**3 = 8 and 3**2 = 9

What is lambda function?

Lambda function is an anonymous function that can have any number of parameters but, can have just one statement.

exp = lambda x,y : x**y
print(exp(3,2))

Output: 9