What is Scope in Python?
In Python, the term “scope” refers to the region of a program where a particular identifier, such as a variable or a function name, is accessible or visible. Python has four levels of scope:
- Local scope: Variables defined within a function are only accessible within that function. They are said to have a local scope.
- Enclosing (or non-local) scope: This refers to the scope of the enclosing function. If a function is defined inside another function, it can access variables from the enclosing function’s scope.
- Global scope: Variables defined at the top level of a module are accessible throughout the module. They are said to have global scope.
- Built-in scope: This is the widest scope and includes all the names that are built into Python, such as
print()
,len()
, etc.
Python follows the LEGB rule to determine the order of scopes to look up names:
- L: Local scope
- E: Enclosing scope
- G: Global scope
- B: Built-in scope
Python searches for a name in these scopes in the order specified by the LEGB rule until it finds the name or reaches the built-in scope. If the name is not found in any of these scopes, Python raises a NameError
.
What are lists and tuples?
Lists and tuples are both data structures in Python used to store collections of items, but they have some differences in their behavior and usage:
Lists:
- Lists are ordered collections of items.
- They are mutable, meaning their elements can be changed after creation.
- Lists are defined using square brackets
[ ]
. - Elements in a list can be accessed using indexing.
- Lists can contain elements of different data types.
- Lists are typically used when you have a collection of items that may need to be modified, such as a list of tasks, a list of numbers, etc.
Example of a list:
my_list = [1, 2, 3, 'apple', 'banana', 'orange']
Tuples:
- Tuples are ordered collections of items, similar to lists.
- They are immutable, meaning once a tuple is created, its elements cannot be changed.
- Tuples are defined using parentheses
( )
. - Elements in a tuple can be accessed using indexing.
- Tuples can contain elements of different data types.
- Tuples are typically used when you have a collection of items that should not be changed, such as coordinates, configuration settings, etc.
Example of a tuple:
my_tuple = (1, 2, 3, 'apple', 'banana', 'orange')
In summary, lists are mutable and defined with square brackets, while tuples are immutable and defined with parentheses. Both can contain elements of different data types and are used for storing collections of items, but the choice between them depends on whether mutability is desired and the specific requirements of the program.
What is pass in Python?
In Python, pass
is a keyword that is used as a placeholder where syntactically some code is required, but you want to do nothing or indicate that the code will be added later.
It’s essentially a no-operation statement. When Python encounters pass
, it does nothing and moves on to the next line of code. It’s often used as a placeholder when a statement is syntactically necessary but the programmer doesn’t want to perform any action.
Here’s an example of how pass
can be used:
if condition:
# code to be added later
pass
else:
# some other code
print("Condition is False")
In this example, if the condition is true, the if
block would be executed, but since there’s no actual code to execute yet, pass
is used as a placeholder. Similarly, you might use pass
in defining a function, class, or loop when you plan to implement its body later.
def my_function():
# code to be added later
pass
This can be useful during development when you’re sketching out the structure of your program and want to indicate where certain functionality will be added later.
What are modules and packages in Python?
In Python, a module is a file containing Python code. The code within a module can consist of functions, classes, or variables. Modules allow you to organize your Python code logically into separate files, making it easier to manage and maintain your codebase. You can import modules into other Python scripts or modules to reuse their functionality.
Here’s an example of a simple module:
# module.py
def greet(name):
print("Hello, " + name + "!")
You can then import this module into another Python script and use its functionality:
# main.py
import module
module.greet("Alice")
Output:
Hello, Alice!
A package in Python is a collection of related modules organized within a directory. The directory containing the modules must include a special file called __init__.py
. Packages allow you to further organize your code by grouping related functionality together.
Here’s an example of a package structure:
my_package/
__init__.py
module1.py
module2.py
The __init__.py
file can be empty, but it’s required to indicate that the directory is a Python package. You can then import modules from the package using dot notation:
# main.py
import my_package.module1
my_package.module1.some_function()
Alternatively, you can use relative imports within the package:
# module2.py
from . import module1
module1.some_function()
Packages allow for better organization and modularization of your codebase, making it easier to manage larger projects. They also facilitate code reuse and sharing across different projects.
What are global, protected and private attributes in Python?
In Python, attributes are variables associated with a class instance or a class itself. They can be classified into different access levels:
- Public attributes: These attributes can be accessed from outside the class. They are not prefixed with any underscore. By default, all attributes in Python are considered public unless explicitly marked otherwise.
Example:
class MyClass:
def __init__(self):
self.public_attribute = 10
obj = MyClass()
print(obj.public_attribute) # Output: 10
- Protected attributes: Protected attributes are conventionally indicated by prefixing an underscore (
_
) to the attribute name. These attributes are intended to be accessed within the class itself and by its subclasses. While it’s not enforced by the language, it serves as a signal to other programmers that the attribute should be treated as protected.
Example:
class MyClass:
def __init__(self):
self._protected_attribute = 20
obj = MyClass()
print(obj._protected_attribute) # Output: 20
- Private attributes: Private attributes are indicated by prefixing a double underscore (
__
) to the attribute name. This causes name mangling to occur, making it harder to access the attribute from outside the class. Private attributes are intended to be accessed only within the class that defines them.
Example:
class MyClass:
def __init__(self):
self.__private_attribute = 30
obj = MyClass()
# This will raise an AttributeError because the private attribute is not directly accessible
print(obj.__private_attribute)
However, Python does not have true private access control like some other languages (e.g., Java). You can still access private attributes using name mangling:
print(obj._MyClass__private_attribute) # Output: 30
It’s important to note that while Python provides these conventions for indicating access levels, they are primarily for code readability and should be followed by convention rather than enforced by the language itself.
What is the use of self in Python?
In Python, self
is a conventionally used parameter name that refers to the current instance of a class. It’s the first parameter in the definition of any instance method within a class. When you call a method on an instance of a class, Python automatically passes the instance itself as the first argument to the method. By convention, this parameter is named self
, but you can name it whatever you like (though it’s highly recommended to stick to the convention for readability).
The primary purpose of self
is to access and modify the attributes and methods of the current instance within the class definition. By using self
, you can differentiate between instance variables (attributes) and local variables within methods.
Here’s a simple example to illustrate the use of self
:
class MyClass:
def __init__(self, value):
self.value = value # Define an instance variable 'value'
def display_value(self):
print(self.value) # Access instance variable 'value' using 'self'
# Create an instance of MyClass
obj = MyClass(42)
# Call the instance method 'display_value' on the object
obj.display_value() # Output: 42
In this example, self.value
refers to the value
attribute of the current instance of MyClass
.
Without self
, Python wouldn’t know which instance’s attribute or method you’re referring to within the class definition. By using self
, you make it clear that you’re accessing or modifying the attributes and methods of the current instance.
What is init?
__init__
is a special method in Python classes that is automatically called when a new instance of the class is created. It is also known as the constructor method. The __init__
method is used to initialize the attributes (variables) of the class instance.
Here’s a basic example:
class MyClass:
def __init__(self, x, y):
self.x = x
self.y = y
# Create an instance of MyClass and pass values for 'x' and 'y'
obj = MyClass(3, 5)
# Access the attributes initialized in the __init__ method
print(obj.x) # Output: 3
print(obj.y) # Output: 5
In this example, the __init__
method takes three parameters: self
, x
, and y
. self
represents the instance of the class itself, and x
and y
are the values used to initialize the attributes self.x
and self.y
, respectively.
When you create a new instance of the class MyClass
and pass arguments to it (MyClass(3, 5)
), Python automatically calls the __init__
method with the instance being created (obj
) as the first argument (self
). The values 3
and 5
are then assigned to the attributes x
and y
, respectively.
The __init__
method is commonly used to set up the initial state of an object by initializing its attributes. However, it’s not required to define an __init__
method in a class. If you don’t define one, Python provides a default __init__
method that does nothing.
What is break, continue and pass in Python?
In Python, break
, continue
, and pass
are control flow statements used within loops and conditional statements to alter the flow of execution.
- break:
- The
break
statement is used to terminate the current loop prematurely. - When Python encounters a
break
statement within a loop (such asfor
orwhile
), it immediately exits the loop, skipping any remaining iterations. break
is commonly used to exit a loop early when a certain condition is met.
Example:
for i in range(5):
if i == 3:
break
print(i)
Output:
0
1
2
- continue:
- The
continue
statement is used to skip the rest of the code inside a loop for the current iteration and continue with the next iteration. - When Python encounters a
continue
statement within a loop, it skips the rest of the loop’s body for the current iteration and proceeds to the next iteration. continue
is commonly used to skip certain iterations based on a condition, without terminating the loop entirely.
Example:
for i in range(5):
if i == 2:
continue
print(i)
Output:
0
1
3
4
- pass:
- The
pass
statement is a no-operation statement that is used when a statement is syntactically required but you don’t want to execute any code. - It’s often used as a placeholder when defining a block of code that will be implemented later.
pass
essentially does nothing and allows the code to continue executing without raising any errors.
Example:
if condition:
# code to be added later
pass
else:
print("Condition is False")
In summary, break
is used to exit a loop prematurely, continue
is used to skip the rest of the loop for the current iteration, and pass
is used as a placeholder when no action is needed.
What are unit tests in Python?
Unit tests in Python are a way to verify that individual units of code (such as functions, methods, or classes) work correctly in isolation. The primary goal of unit testing is to validate that each unit of code behaves as expected under various inputs and conditions.
Python provides a built-in module called unittest
for writing and running unit tests. With unittest
, you can create test cases that define the expected behavior of your code and check whether the actual output matches the expected output.
Here’s a basic example of a unit test using unittest
:
import unittest
def add(a, b):
return a + b
class TestAddFunction(unittest.TestCase):
def test_add_positive_numbers(self):
self.assertEqual(add(2, 3), 5)
def test_add_negative_numbers(self):
self.assertEqual(add(-2, -3), -5)
def test_add_zero(self):
self.assertEqual(add(0, 0), 0)
if __name__ == '__main__':
unittest.main()
In this example:
- We define a function
add(a, b)
that simply adds two numbers. - We create a test case class
TestAddFunction
that inherits fromunittest.TestCase
. - Within the test case class, we define individual test methods, each starting with the prefix
test_
. - Inside each test method, we use assertion methods provided by
unittest.TestCase
(such asassertEqual
) to check whether the actual output of the function matches the expected output.
To run the tests, you typically execute the script containing the tests. When run, unittest
automatically discovers test cases within the script and executes them, reporting any failures or errors.
Unit testing helps ensure that changes to your code don’t introduce unintended bugs or regressions. By writing tests for each unit of code, you can have confidence that your code behaves correctly as you make modifications and enhancements.
What is docstring in Python?
In Python, a docstring is a string literal that occurs as the first statement in a module, function, class, or method definition. Its primary purpose is to document the purpose, usage, and behavior of the code it accompanies. Docstrings are used to provide documentation that can be accessed using the built-in help()
function or documentation generation tools like Sphinx.
Docstrings can be written using triple quotes ('''
or """
) and can span multiple lines. There are two common styles for writing docstrings: the one-line summary style and the multi-line style.
- One-line summary style:
- This style consists of a single-line description of the function or method’s purpose.
- It’s typically used for simple functions or methods with straightforward functionality.
Example:
def add(a, b):
"""Return the sum of two numbers."""
return a + b
- Multi-line style:
- This style includes a more detailed description of the function or method, along with information about its parameters, return value, and any exceptions it may raise.
- It’s used for more complex functions or methods that require detailed documentation.
Example:
def divide(dividend, divisor):
"""
Divide two numbers.
Parameters:
dividend (int): The number to be divided.
divisor (int): The number by which the dividend is divided.
Returns:
float: The quotient of the division.
Raises:
ZeroDivisionError: If the divisor is zero.
"""
if divisor == 0:
raise ZeroDivisionError("Cannot divide by zero")
return dividend / divisor
Docstrings are not just comments; they are accessible programmatically and are used by tools and libraries to generate documentation. Writing descriptive and informative docstrings can greatly improve the usability and maintainability of your code.