Most Frequently asked python-2.7 Interview Questions and Answers
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:
-
@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
-
@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'>
-
@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:
Operator | Checks For | Comparison Type |
---|---|---|
== | Value equality | Compares the contents of objects to see if they are the same |
is | Identity | Checks 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 returnTrue
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
andy
refer to the same object. However, for integers outside this range,is
will returnFalse
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
:
-
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.
-
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.
-
Accessing Other Methods:
self
allows you to call other methods of the class from within a method.
-
Not a Keyword, but a Convention:
- While
self
is not a reserved keyword in Python, it is a strong convention and must be namedself
to ensure clarity and consistency. However, you could technically use any name, but it is highly discouraged to deviate from this naming convention.
- While
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
andself.age
are used to set and access the instance variables of theDog
class. These are unique to each instance of the class.- The method
speak
usesself.name
to print a message with the dog’s name. - The
have_birthday
method modifies theself.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:
-
try
Block:- The
try
block contains code that might raise an exception. If an exception occurs in this block, the code execution jumps to theexcept
block.
- The
-
except
Block:- The
except
block defines how to handle specific exceptions. You can catch specific exceptions or catch all exceptions.
- The
-
else
Block (optional):- The
else
block runs if no exceptions were raised in thetry
block. It is useful for code that should run only when thetry
block is successful (i.e., no exception occurred).
- The
-
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.
- The
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. Theexcept
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
andIOError
, and a generalException
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 thetry
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 aValueError
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 thetry
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.
Tags
- Python 2.7
- Python Interview
- Python 3 vs Python 2
- List Comprehension
- Python Classes
- Memory Management
- Decorators
- Is vs ==
- Self Keyword
- Exception Handling
- Yield
- Variable Scope
- Generators
- Dependencies Management
- File Handling
- Shallow Copy vs Deep Copy
- String Formatting
- List vs Tuple
- Dictionary Iteration
- Iterators
- Unicode Handling
- From future import