1. What is Python?

Python is a high-level,
interpreted, general-purpose programming language. Being a general-purpose
language, it can be used to build almost any type of application with the right
tools/libraries. Additionally, python supports objects, modules, threads,
exception-handling and automatic memory management which help in modelling
real-world problems and building applications to solve these problems.

2. What are the benefits of using Python?

Python is a
general-purpose programming language that has simple, easy-to-learn syntax
which emphasizes readability and therefore reduces the cost of program
maintenance. Moreover, the language is capable of scripting, completely
open-source and supports third-party packages encouraging modularity and
Its high-level data structures, combined with dynamic typing and dynamic
binding, attract a huge community of developers for Rapid Application
Development and deployment.

3. What is a dynamically typed language?

Before we understand what
a dynamically typed language, we should learn about what typing is. Typing refers to type-checking in programming
languages. In a 
strongly-typed  language,
such as Python, “1” + 2 will result
in a type error, since these languages don’t allow for “type-coercion” (implicit conversion of
data types). On the other hand, a 
weakly-typed  language,
such as Javascript, will simply output “12” as

Type-checking can be done
at two stages –

Static – Data
Types are checked before execution.

Dynamic – Data
Types are checked during execution.

Python being an interpreted language, executes each
statement line by line and thus type-checking is done on the fly, during
execution. Hence, Python is a Dynamically Typed language.

4. What is an Interpreted language?

An Interpreted language executes its statements line
by line. Languages such as Python, Javascript, R, PHP and Ruby are prime
examples of Interpreted languages. Programs written in an interpreted language
runs directly from the source code, with no intermediary compilation step.

5. What is PEP 8 and why is it important?

PEP stands for Python Enhancement Proposal. A PEP is an official
design document providing information to the Python Community, or describing a
new feature for Python or its processes. PEP 8 is
especially important since it documents the style guidelines for Python Code.
Apparently contributing in the Python open-source community requires you to
follow these style guidelines sincerely and strictly.

6. How is memory managed in Python?

Memory management in Python
is handled by the Python Memory Manager. The memory
allocated by the manager is in form of a private heap space dedicated
for Python. All Python objects are stored in this heap and being private, it is
inaccessible to the programmer. Though, python does provide some core API
functions to work upon the private heap space.
Additionally, Python has an in-built garbage collection to recycle the unused
memory for the private heap space.

7. What are Python namespaces? Why are they used?

A namespace in Python
ensures that object names in a program are unique and can be used without any
conflict. Python implements these namespaces as dictionaries with
‘name as key’ mapped to a corresponding ‘object as value’. This allows for
multiple namespaces to use the same name and map it to a separate object. A few
examples of namespaces are as follows:

§  Local
 includes local names inside a function. the namespace is
temporarily created for a function call and gets cleared when the function

§  Global
 includes names from various imported packages/ modules
that is being used in the current project. This namespace is created when the
package is imported in the script and lasts until the execution of the script.

§  Built-in
 includes built-in functions of core Python and built-in
names for various types of exceptions.

Lifecycle of a namespace depends upon the scope of objects they are mapped to. If
the scope of an object ends, the lifecycle of that namespace comes to an end. Hence,
it isn’t possible to access inner namespace objects from an outer namespace.

4. What is Scope in Python?

Every object in Python
functions within a scope. A scope is a
block of code where an object in Python remains relevant. Namespaces uniquely
identify all the objects inside a program. However, these namespaces also have
a scope defined for them where you could use their objects without any prefix.
A few examples of scope created during code execution in Python are as follows:

local scope refers to the
local objects available in the current function.

global scope refers to the
objects available throught the code execution since their inception.

module-level scope refers to
the global objects of the current module accessible in the program.

An outermost scope refers to all
the built-in names callable in the program. The objects in this scope are
searched last to find the name referenced.

Note: Local scope objects can be synced with global scope
objects using keywords such as 

9. What is Scope Resolution in Python?

Sometimes objects within
the same scope have the same name but function differently. In such cases,
scope resolution comes into play in Python automatically. A few examples of
such behaviour are:

§  Python
modules namely ‘math’ and ‘cmath’ have a lot of functions that are common to
both of them – 
log10()acos()exp() etc.
To resolve this amiguity, it is necessary to prefix them with their respective
module, like 
math.exp() and cmath.exp().

§  Consider
the code below, an object temp has been initialized to 10 globally and then to
20 on function call. However, the function call didn’t change the value of the
temp globally. Here, we can observe that Python draws a clear line between
global and local variables treating both their namespaces as separate

temp = 10        # global-scope variable


def func():

= 20   # local-scope variable



print(temp)       # output => 10

func()            # output => 20

print(temp)       # output => 10

This behaviour can be
overriden using the 
global keyword inside the
function, as shown in the following example:

temp = 10        # global-scope variable


def func():

      global temp

= 20   # local-scope variable



print(temp)       # output => 10

func()            # output => 20

print(temp)       # output => 20

10. What are decorators in Python?

Decorators in
Python are essentially functions that add functionality to an existing function
in Python without changing the structure of the function itself. They are
represented by the 
@decorator_name in Python and are
called in bottom-up fashion. For example:

# decorator function to convert
to lowercase

def lowercase_decorator(function):

    def wrapper():

        func = function()

        string_lowercase = func.lower()

        return string_lowercase

    return wrapper


# decorator function to split

def splitter_decorator(function):

    def wrapper():

        func = function()

        string_split = func.split()

        return string_split

    return wrapper


@splitter_decorator      # this is executed next

@lowercase_decorator   # this is executed first

def hello():

    return ‘Hello World’


hello()  # output => [ ‘hello’ ,
‘world’ ]

The beauty of the
decorators lies in the fact that besides adding functionality to the output of
the method, they can even accept arguments for
functions and can further modify those arguments before passing it to the
function itself. The inner nested function,
i.e. ‘wrapper’ function, plays a significant role here. It is implemented to
enforce encapsulation and thus, keep itself hidden from
the global scope.

# decorator function to capitalize

def names_decorator(function):

    def wrapper(arg1, arg2):

        arg1 = arg1.capitalize()

        arg2 = arg2.capitalize()

        string_hello = function(arg1, arg2)

        return string_hello

    return wrapper



def say_hello(name1, name2):

    return ‘Hello ‘ + name1 + ‘! Hello ‘ + name2 + ‘!’


say_hello(‘sara’, ‘ansh’)  # output => ‘Hello Sara!
Hello Ansh!’

11. What are lists and tuples? What is the key difference
between the two?

Lists and Tuples are both sequence data types that
can store a collection of objects in Python. The objects stored in both
sequences can have different data types.
Lists are represented with square brackets 
[‘sara’, 6,
, while tuples are represented with parantheses (‘ansh’, 5, 0.97).
But what is the real difference between the two? The key difference between the
two is that while lists are mutabletuples on the other hand are immutable objects. This means that lists can
be modified, appended or sliced on-the-go but tuples remain constant and cannot
be modified in any manner. You can run the following example on Python IDLE to
confirm the difference:

my_tuple = (‘sara’, 6, 5, 0.97)

my_list = [‘sara’, 6, 5, 0.97]


print(my_tuple[0])     # output => ‘sara’

print(my_list[0])     # output => ‘sara’


my_tuple[0] = ‘ansh’    # modifying tuple => throws
an error

my_list[0] = ‘ansh’    # modifying list => list


print(my_tuple[0])     # output => ‘sara’

print(my_list[0])     # output => ‘ansh’

12. What are Dict and List comprehensions?

Python comprehensions,
like decorators, are syntactic sugar constructs
that help build altered and filtered lists,
dictionaries or sets from a given list, dictionary or set. Using
comprehensions, saves a lot of time and code that might be considerably more
verbose (containing more lines of code). Let’s check out some examples, where
comprehensions can be truly beneficial:

§  Performing
mathematical operations on the entire list

§  my_list = [2, 3, 5, 7, 11]


§  squared_list = [x**2 for x in my_list]    # list comprehension

§  # output => [4 , 9 , 25 , 49
, 121]


§  squared_dict = {x:x**2 for x in my_list}    # dict comprehension

§  # output => {11: 121, 2: 4 ,
3: 9 , 5: 25 , 7: 49}

§  Performing
conditional filtering operations on the entire list

§  my_list = [2, 3, 5, 7, 11]


§  squared_list = [x**2 for x in my_list if x%2 != 0]    # list comprehension

§  # output => [9 , 25 , 49 ,


§  squared_dict = {x:x**2 for x in my_list if x%2 != 0}    # dict comprehension

§  # output => {11: 121, 3: 9 ,
5: 25 , 7: 49}

§  Combining
multiple lists into one

Comprehensions allow for multiple iterators and hence, can be used to combine
multiple lists into one.

§  a = [1, 2, 3]

§  b = [7, 8, 9]


§  [(x + y) for (x,y) in zip(a,b)]  # parallel iterators

§  # output => [8, 10, 12]


§  [(x,y) for x in a for y in b]    # nested iterators

§  # output => [(1, 7), (1, 8),
(1, 9), (2, 7), (2, 8), (2, 9), (3, 7), (3, 8), (3, 9)]

§  Flattening
a multi-dimensional list

A similar approach of nested iterators (as above) can be applied to flatten a
multi-dimensional list or work upon its inner elements.

§  my_list = [[10,20,30],[40,50,60],[70,80,90]]


§  flattened = [x for temp in my_list for x in temp]

§  # output => [10, 20, 30, 40,
50, 60, 70, 80, 90]

Note: List
comprehensions have the same effect as the 
map method
in other languages. They follow the 
set builder notation
 rather than map and filter functions
in Python.