Most Frequently asked python-2.7 Interview Questions and Answers

author image Hirely
at 02 Jan, 2025

Question: What are decorators in Python 2.7 and how are they used?

Answer:

Decorators in Python 2.7 are a powerful and elegant way to modify or extend the behavior of functions or methods without changing their actual code. They allow you to “wrap” a function with additional functionality. Decorators are often used for tasks like logging, access control, caching, and measuring execution time, among others.

Decorators are applied to functions (or methods) using the @decorator_name syntax, which is a shorthand for the decorator(function) call.

How Decorators Work:

A decorator is essentially a function that takes another function (or method) as its argument, modifies or extends its behavior, and then returns a new function (usually a wrapper) that has the additional functionality.

Basic Syntax of a Decorator:

def decorator_function(original_function):
    def wrapper_function():
        # Additional functionality before or after the original function call
        print("Wrapper executed before {}".format(original_function.__name__))
        original_function()
        print("Wrapper executed after {}".format(original_function.__name__))
    return wrapper_function

This decorator_function takes an original_function as an argument, defines a wrapper_function that adds extra behavior, and returns wrapper_function.

To use the decorator, you apply it to a function using the @decorator_name syntax:

@decorator_function
def say_hello():
    print("Hello!")

This is equivalent to:

say_hello = decorator_function(say_hello)

When you call say_hello(), it will execute the wrapper_function inside the decorator, which in turn calls the original say_hello() function.

Example of a Simple Decorator:

# Define a simple decorator
def decorator_function(original_function):
    def wrapper_function():
        print("Something is happening before the function is called.")
        original_function()
        print("Something is happening after the function is called.")
    return wrapper_function

# Apply the decorator to the function using @
@decorator_function
def display_message():
    print("Hello from the original function!")

# Call the decorated function
display_message()

Output:

Something is happening before the function is called.
Hello from the original function!
Something is happening after the function is called.

In this example, the display_message function is wrapped by wrapper_function, which adds extra behavior before and after the original function call.

Passing Arguments to Decorators:

Most real-world decorators need to handle functions that take arguments. To achieve this, the wrapper_function inside the decorator should accept arbitrary arguments (*args) and keyword arguments (**kwargs) to pass to the original function.

def decorator_function(original_function):
    def wrapper_function(*args, **kwargs):
        print("Wrapper executed before the function")
        result = original_function(*args, **kwargs)  # Pass arguments to the original function
        print("Wrapper executed after the function")
        return result
    return wrapper_function

# Apply the decorator to a function that takes arguments
@decorator_function
def add_numbers(a, b):
    print("Adding {} and {}".format(a, b))
    return a + b

# Call the decorated function
print(add_numbers(5, 7))

Output:

Wrapper executed before the function
Adding 5 and 7
Wrapper executed after the function
12

In this case, *args and **kwargs allow the wrapper_function to pass any arguments to add_numbers and then return the result.

Chaining Multiple Decorators:

You can apply more than one decorator to a single function. In such cases, the decorators are applied in the order from bottom to top (or from inside to outside).

def decorator_one(func):
    def wrapper():
        print("Decorator One is executed")
        func()
    return wrapper

def decorator_two(func):
    def wrapper():
        print("Decorator Two is executed")
        func()
    return wrapper

@decorator_one
@decorator_two
def hello():
    print("Hello!")

hello()

Output:

Decorator One is executed
Decorator Two is executed
Hello!

Here, hello() is first wrapped by decorator_two, and then the result of that is wrapped by decorator_one.

Built-in Python 2.7 Decorators:

Python 2.7 comes with several built-in decorators:

  1. @staticmethod: Defines a static method, which does not need a reference to the instance (self) or the class (cls).

    class MyClass:
        @staticmethod
        def my_static_method():
            print("Static method called")
    
    MyClass.my_static_method()  # Output: Static method called
  2. @classmethod: Defines a class method, which takes a reference to the class (cls) as the first argument instead of the instance.

    class MyClass:
        @classmethod
        def my_class_method(cls):
            print("Class method called, reference to class:", cls)
    
    MyClass.my_class_method()  # Output: Class method called, reference to class: <class '__main__.MyClass'>
  3. @property: Turns a method into a read-only property, allowing you to access it like an attribute without calling it as a function.

    class MyClass:
        def __init__(self, value):
            self._value = value
    
        @property
        def value(self):
            return self._value
    
    obj = MyClass(42)
    print(obj.value)  # Output: 42 (accessed like an attribute)

Summary:

  • Decorators are a convenient way to modify or extend the behavior of functions or methods in Python without changing their actual code.
  • They are defined as functions that take another function as input and return a new function that adds extra functionality.
  • You can pass arguments to the function being decorated using *args and **kwargs.
  • Multiple decorators can be applied to the same function, and they will be executed from bottom to top.
  • Decorators are commonly used in Python for tasks like logging, validation, access control, and more.

In Python 2.7, decorators provide a simple way to add functionality to functions or methods while keeping the code clean and modular.

Question: Explain the difference between is and == in Python 2.7.

Answer:

In Python 2.7, both is and == are used to compare objects, but they compare in different ways. Understanding the distinction between these two operators is important for writing correct and efficient Python code.


1. == (Equality Operator):

The == operator checks for value equality. This means it compares the values of two objects to determine if they are the same.

  • It calls the __eq__() method of the objects being compared (if defined).
  • == evaluates whether the contents or the data of the two objects are equivalent.

Example of == (Equality) Operator:

a = [1, 2, 3]
b = [1, 2, 3]

print(a == b)  # Output: True

In this case, a and b are two different list objects, but they have the same values (i.e., [1, 2, 3]). Therefore, a == b returns True because their values are equal.


2. is (Identity Operator):

The is operator checks for object identity. This means it checks whether two objects are the same object in memory (i.e., whether they refer to the same location in memory).

  • It evaluates whether the two variables point to the same object (not just equivalent data).
  • is compares the memory addresses of the two objects, not their contents.

Example of is (Identity) Operator:

a = [1, 2, 3]
b = [1, 2, 3]

print(a is b)  # Output: False

In this case, a and b are two distinct list objects with the same contents. Since they are two different objects stored at different locations in memory, a is b returns False.

However, if we assign one variable to another (thus both referring to the same object), is will return True.

Example of is with Assignment:

a = [1, 2, 3]
b = a  # 'b' now refers to the same object as 'a'

print(a is b)  # Output: True

Here, both a and b refer to the same list object in memory, so a is b returns True.


Key Differences:

OperatorChecks ForComparison Type
==Value equalityCompares the contents of objects to see if they are the same
isIdentityChecks whether two variables point to the same object in memory (i.e., if they are the same object)

Common Pitfalls:

  • Mutable objects (like lists): is compares whether two variables point to the same object, which can be tricky when dealing with mutable objects. Even if two lists have the same values, they may not be the same object in memory, leading to confusion.

    a = [1, 2, 3]
    b = [1, 2, 3]
    
    print(a == b)  # Output: True (values are equal)
    print(a is b)  # Output: False (different objects in memory)
  • Immutable objects (like integers and strings): Python sometimes reuses immutable objects for optimization purposes. For example, small integers and strings (typically those within a specific range) may be stored in the same memory location, so is can return True even for variables that are not explicitly the same object.

    x = 256
    y = 256
    
    print(x == y)  # Output: True (values are equal)
    print(x is y)  # Output: True (same object in memory, due to internal optimization)
    
    x = 257
    y = 257
    
    print(x == y)  # Output: True (values are equal)
    print(x is y)  # Output: False (different objects in memory)

    In this case, Python uses interning for small integers (from -5 to 256), so x and y refer to the same object. However, for integers outside this range, is will return False even if the values are the same, as Python creates distinct objects for each assignment.


Summary:

  • Use == when you want to check if two objects have the same value.
  • Use is when you want to check if two objects are the same object (i.e., have the same memory address).

Choosing between is and == depends on whether you need to compare the data (use ==) or check if the objects are identical (use is).

Question: What is the use of the self keyword in Python 2.7?

Answer:

In Python 2.7, the self keyword is used to refer to the instance of the class. It is a conventional name for the first parameter of methods in a class, which represents the object on which the method is being called. When a method is invoked on an object, self allows access to the attributes and methods of the object, enabling the method to operate on the data contained within that object.

Key Points about self:

  1. Refers to the Instance:

    • self is a reference to the current instance of the class.
    • When a method is called on an object, Python automatically passes the object itself as the first argument to the method.
  2. Accessing Instance Variables:

    • self is used to refer to instance variables (attributes) that are tied to the specific object.
    • It allows you to access and modify the attributes of the instance.
  3. Accessing Other Methods:

    • self allows you to call other methods of the class from within a method.
  4. Not a Keyword, but a Convention:

    • While self is not a reserved keyword in Python, it is a strong convention and must be named self to ensure clarity and consistency. However, you could technically use any name, but it is highly discouraged to deviate from this naming convention.

Example of Using self:

class Dog:
    # The constructor method (__init__) initializes instance variables
    def __init__(self, name, age):
        self.name = name  # Assign name to the instance variable 'name'
        self.age = age    # Assign age to the instance variable 'age'

    # A method that uses 'self' to refer to instance variables
    def speak(self):
        print("{} says woof!".format(self.name))

    # A method that uses 'self' to modify an instance variable
    def have_birthday(self):
        self.age += 1  # Increase the age by 1

# Creating an instance of the Dog class
dog1 = Dog("Buddy", 3)

# Accessing instance variables and methods
print(dog1.name)  # Output: Buddy
print(dog1.age)   # Output: 3
dog1.speak()      # Output: Buddy says woof!

# Modifying an instance variable
dog1.have_birthday()
print(dog1.age)   # Output: 4

Explanation of the Example:

  • self.name and self.age are used to set and access the instance variables of the Dog class. These are unique to each instance of the class.
  • The method speak uses self.name to print a message with the dog’s name.
  • The have_birthday method modifies the self.age variable to reflect the dog aging by one year.

Why self is Important:

  • Without self, Python wouldn’t know which instance’s data or methods to access.
  • It distinguishes between local variables in a method and instance variables that belong to the object.
  • It helps in defining methods that can operate on the attributes of different instances of the class.

Summary:

  • self is a reference to the current instance of the class.
  • It is used to access instance variables and methods within the class.
  • It allows you to write instance-specific behavior in your methods and ensures that each object can maintain its own state.

Question: How do you handle exceptions in Python 2.7?

Answer:

In Python 2.7, exceptions are used to handle errors that arise during the execution of a program. Python provides a mechanism called exception handling to catch and respond to these errors in a controlled way, allowing the program to continue running without crashing.

Key Concepts of Exception Handling:

  1. try Block:

    • The try block contains code that might raise an exception. If an exception occurs in this block, the code execution jumps to the except block.
  2. except Block:

    • The except block defines how to handle specific exceptions. You can catch specific exceptions or catch all exceptions.
  3. else Block (optional):

    • The else block runs if no exceptions were raised in the try block. It is useful for code that should run only when the try block is successful (i.e., no exception occurred).
  4. finally Block (optional):

    • The finally block contains code that will run no matter what, whether an exception occurred or not. It’s commonly used for cleanup tasks, like closing files or releasing resources.

Basic Syntax:

try:
    # Code that may raise an exception
except ExceptionType1:
    # Code that runs if ExceptionType1 occurs
except ExceptionType2:
    # Code that runs if ExceptionType2 occurs
else:
    # Code that runs if no exceptions occurred
finally:
    # Code that runs regardless of whether an exception occurred or not

Example: Handling Exceptions in Python 2.7:

Basic Exception Handling:

try:
    # Trying to divide by zero, which will raise an exception
    result = 10 / 0
except ZeroDivisionError:
    print("Error: Division by zero is not allowed.")

Output:

Error: Division by zero is not allowed.
  • In this example, the ZeroDivisionError is raised when trying to divide by zero. The except block catches this exception and prints an error message.

Catching Multiple Exceptions:

You can catch multiple specific exceptions or catch a general Exception to handle all exceptions.

try:
    # Trying to open a file that does not exist
    file = open("nonexistent_file.txt", "r")
except FileNotFoundError:
    print("Error: The file was not found.")
except IOError:
    print("Error: An I/O error occurred.")
except Exception as e:
    print("An unexpected error occurred:", e)

Output:

Error: The file was not found.
  • Here, we catch specific exceptions such as FileNotFoundError and IOError, and a general Exception is used to handle any unexpected errors.

Using else Block:

The else block runs if no exception is raised in the try block.

try:
    number = int(input("Enter a number: "))
    print("The number entered is:", number)
except ValueError:
    print("Error: That was not a valid number.")
else:
    print("No errors occurred.")

Output (if user enters a valid number):

Enter a number: 5
The number entered is: 5
No errors occurred.
  • The else block runs because no exception was raised in the try block.

Using finally Block:

The finally block runs no matter what happens in the try and except blocks, ensuring that specific cleanup code is always executed.

try:
    file = open("sample.txt", "w")
    file.write("Hello, World!")
except IOError:
    print("Error: Unable to write to file.")
finally:
    print("Closing the file.")
    file.close()

Output:

Closing the file.
  • The finally block ensures that the file is closed, whether an exception occurred or not.

Catching All Exceptions:

If you don’t know the specific exception type, you can catch all exceptions using a general except clause. However, this is generally discouraged because it can make debugging harder and may catch unexpected errors.

try:
    # Some code that may raise an exception
    result = 10 / 0
except:
    print("An error occurred.")

Output:

An error occurred.
  • The code inside the except block is executed, but it doesn’t specify which exception caused the error.

Raising Exceptions:

You can also manually raise exceptions using the raise keyword. This is useful when you want to signal an error condition in your code explicitly.

def divide(a, b):
    if b == 0:
        raise ValueError("Division by zero is not allowed.")
    return a / b

try:
    result = divide(10, 0)
except ValueError as e:
    print(e)

Output:

Division by zero is not allowed.
  • The raise statement raises a ValueError exception when division by zero is attempted.

Summary:

  • try: Contains the code that might raise an exception.
  • except: Catches and handles the exception.
  • else: Runs if no exceptions occur in the try block.
  • finally: Runs regardless of whether an exception occurs or not, typically used for cleanup.

Exception handling is a crucial part of writing robust Python code, allowing you to catch errors, handle them gracefully, and ensure that important code (like cleanup) is always executed.

Read More

If you can’t get enough from this article, Aihirely has plenty more related information, such as python-2.7 interview questions, python-2.7 interview experiences, and details about various python-2.7 job positions. Click here to check it out.

Trace Job opportunities

Hirely, your exclusive interview companion, empowers your competence and facilitates your interviews.

Get Started Now