Previous | Tutorial index | Next
Import and use specific parts from a module or specific modules from a package.
In Tutorial 3, you learned how to bring entire modules into your program using import module_name. This is like bringing an entire toolbox into your workspace. It's safe and organized, but sometimes it's cumbersome. If you only need a single screwdriver, why bring the entire 50-piece tool chest?
Selective imports address this by allowing you to reach into a module (or package) and pull out exactly what you need – whether it's a single function, a specific class, a constant, or even an entire submodule. This results in cleaner, more concise code that clearly communicates exactly which external components you depend on.
However, with great power comes great responsibility. Selective imports, especially the infamous wildcard (*), can lead to messy, bug-prone code if used carelessly. This tutorial will teach you how to wield selective imports effectively and safely.
from ... import ... SyntaxThe most common selective import statement allows you to bring specific names directly into your current namespace.
Syntax:
from module_name import name1, name2, name3
What happens when you run this?
import module_name).module_name, it extracts the specified attributes (name1, name2, etc.) from the module.Example:
# Traditional full import
import math
print(math.sqrt(16)) # 4.0 - Must use dot notation
# Selective import
from math import sqrt, pi
print(sqrt(16)) # 4.0 - Direct use, no prefix!
print(pi) # 3.14159 - Direct use!
| Feature | import module |
from module import name |
|---|---|---|
| Namespace | Creates a separate namespace (module). |
Injects names directly into your current namespace. |
| Access | Requires dot notation: module.func(). |
Direct access: func(). |
| Code length | Longer (more typing). | Shorter, less repetitive. |
| Clarity | Always clear where the function comes from. | May be unclear to readers if the source isn't obvious. |
| Safety | Safe from name collisions (uses namespace). | Prone to name collisions (names are in your space). |
Just as with full module imports, you can use the as keyword to give a specific imported item an alias. This is particularly useful when:
Syntax:
from module_name import original_name as alias_name
Example:
# Aliasing a long function name
from datetime import datetime as dt
current_time = dt.now()
print(current_time)
# Resolving a conflict between two modules
from math import factorial as math_fact
from my_utils import factorial as util_fact
print(math_fact(5)) # 120
print(util_fact(5)) # Output from your custom function
You can mix regular imports and aliases in one line:
from math import pi, sqrt as square_root, sin
print(pi) # 3.14159
print(square_root(16)) # 4.0
print(sin(0)) # 0.0
The from ... import ... syntax is not limited to single modules. You can use it to import specific modules from within a package, or even specific functions from deep within a submodule.
Syntax:
from package_name import module_name
Example (using the xml standard library package):
# Instead of:
import xml.etree.ElementTree
tree = xml.etree.ElementTree.parse("data.xml") # Verbose!
# Use selective import:
from xml.etree import ElementTree as ET
tree = ET.parse("data.xml") # Clean and concise!
Syntax:
from package.subpackage import module_name
Example:
# Instead of:
import os.path
print(os.path.join("folder", "file.txt"))
# Use selective import:
from os import path
print(path.join("folder", "file.txt"))
Syntax:
from package.subpackage.module import function_name
Example (with a hypothetical company package):
from company.hr.employee import calculate_salary
salary = calculate_salary(employee_id=101)
This is the most concise form, but it's also the most "fragile" if your directory structure changes.
from module import * (The Evil Twin)The wildcard import imports all public names from a module (or those listed in __all__ for a package) directly into your current namespace.
Syntax:
from math import *
print(sqrt(16)) # Works
print(pi) # Works
print(sin(0)) # Works
Namespace Pollution: It dumps dozens (or hundreds) of names into your namespace. You might accidentally overwrite your own variables.
from math import *
sqrt = 10 # Oops! You just overwrote the sqrt function.
print(sqrt(16)) # TypeError: 'int' object is not callable!
Ambiguity and Readability: When you see sqrt(16) in your code, you (and your collaborators) might not remember if sqrt came from math, numpy, or if it's a local function. This makes the code much harder to understand and debug.
Hidden Dependencies: If you use from math import *, and later you remove the import line (or Python changes the module), your code breaks in mysterious ways. Explicit imports clearly document your dependencies.
Masking Bugs: If a function doesn't exist in the module, you won't know until you try to call it at runtime. With explicit imports, you get an ImportError immediately when the script starts, making debugging easier.
Conflicts with Standard Library: If you import two different modules with *, and they both have a read() function, the later import overwrites the earlier one. This is a nightmare to debug.
The only somewhat acceptable use of * is during interactive sessions (like in the REPL, Jupyter notebooks, or IPython) where you are just exploring or prototyping. It saves typing. Even then, it's sloppy. For production code, never use from module import * in your scripts.
Remember: If you need so many things from a module that you're tempted to use *, it's a strong signal that you should import the whole module with import module and use dot notation instead.
import os vs. from os import pathThis is a common point of confusion for beginners. Let's break down the practical differences.
import osimport os
current_dir = os.getcwd() # Accessing a function from os
file_path = os.path.join("a", "b") # Accessing the 'path' submodule via os
os. No namespace pollution.os. repeatedly. Slightly longer lines.from os import pathfrom os import path
file_path = path.join("a", "b") # Accessing 'path' directly
# But to use os.getcwd(), you would still need to import os separately or use a different method.
path directly, which is handy if you use it a lot.path. You cannot use os.getcwd() because you didn't import os. You would need to add import os as well.from os import * (Not Recommended)from os import *
print(getcwd()) # Works, but where did getcwd come from?
print(path.join("a","b")) # Works, but path is a submodule
getcwd came from.If you use many functions from a module (more than 3 or 4), use import os. If you only need one specific submodule (like path), and you use it very frequently, from os import path is acceptable, but consider importing the parent module too for clarity.
# Recommended hybrid approach for clarity
import os.path # Although this doesn't import os, it imports os.path as a side effect, but you still can't use os.getcwd()
# Better:
import os
from os import path as p # Now you have both os and path
current_dir = os.getcwd()
full_path = p.join(current_dir, "data.txt")
Test your foundational knowledge before proceeding to the practical exercises.
Q1: Which import statement allows you to use sqrt(16) directly without a prefix?
A) import math
B) import math.sqrt
C) from math import sqrt
D) math import sqrt
Q2: What is the main problem with using from module import * in a production script?
A) It is slower than other import methods.
B) It pollutes the namespace and makes code unclear.
C) It cannot import classes, only functions.
D) It requires the as keyword to work.
Q3: You have a package named utils with a submodule math_ops. Which statement imports the math_ops module correctly?
A) import utils.math_ops
B) from utils import math_ops
C) import math_ops from utils
D) from math_ops import utils
Q4: Given the import from datetime import datetime as dt, what is the correct way to call the now() function?
A) datetime.now()
B) dt.now()
C) datetime.datetime.now()
D) datetime.now() as dt
Q5: True or False: When you use from math import sqrt, Python still loads the entire math module into memory.
These exercises will help you internalize the differences between various import styles.
sqrt and pi ImportGoal: Practice selective imports and observe the direct access behavior.
Instructions:
from math import sqrt, pi.sqrt(25) and confirm it returns 5.0.pi and confirm it returns 3.14159....math.sqrt(25). What error do you get? Why?cos(0) from the math module. What error do you get? Why?path from osGoal: Understand how to import a submodule and compare readability.
Instructions:
path from os: from os import path.path.join("home", "user", "docs") to create a file path string.os.getcwd(). You will get a NameError because os itself is not imported.import os. You now have both os and path available.path.join("a", "b")os.path.join("a", "b")Goal: Learn how to use aliases to differentiate between functions from different modules that have the same name.
Instructions:
sqrt function from math and give it the alias math_sqrt.sqrt that takes a number x and returns x**3 (cubed).math_sqrt(x) (from math) and sqrt(x) (your custom cube function).sqrt(3) – it should return 27.math_sqrt(3) – it should return 1.732....* – A Live DemonstrationGoal: Visually see why wildcard imports are dangerous.
Instructions:
sum = 100.from builtins import * (this imports a lot of built-in functions, including sum).sum variable? Type sum([1, 2, 3]). It returns 6, which is the built-in sum function. Your sum = 100 variable has been completely overwritten!sum = 100 after the import (which is annoying).* silently overwrites your existing names without warning.| Problem | Likely Cause | Solution |
|---|---|---|
ImportError: cannot import name 'function_name' |
The function doesn't exist in the module, or you misspelled it. | Check the spelling and case. Use dir(module_name) to see all available names. |
You get NameError for a name you thought you imported. |
You imported it with an alias (as something), but you forgot to use the alias. |
Double-check your import statement. If you did from math import sqrt as square_root, you must use square_root. |
| You imported two functions with the same name, and the second overwrote the first. | You used from module1 import func and then from module2 import func. |
Use aliases: from module1 import func as func1 and from module2 import func as func2. |
AttributeError: 'module' object has no attribute 'func' |
You used import module and then tried to call func() without the module. prefix. |
Remember that import module requires dot notation. Use module.func(). Or switch to from module import func. |
| You accidentally imported a local variable instead of a module function. | You have a variable in your script with the same name as the function you're trying to import. | Rename your variable. Python will not warn you if you shadow an imported name; it will just overwrite it. |
Trying to import from a package but getting ImportError. |
The package exists, but the submodule might not be directly importable. | Ensure the package has an __init__.py file (for regular packages). Check the correct dotted path. |
These questions are designed to test your practical ability and deep conceptual understanding.
Task: Below is a Python script written by a junior developer. It works, but it's a nightmare of wildcard imports and namespace pollution.
# messy_script.py
from math import *
from random import *
from os import *
# Some local configuration
random = 42 # A variable named 'random'
path = "/home/user" # A variable named 'path'
# Calculate something
num = sqrt(16)
print(f"The square root is {num}")
# Random number generation
print(randint(1, 10))
# File path joining
print(path.join(path, "documents")) # This will fail!
Assignment:
*), use appropriate aliases where necessary, and rename the conflicting variables to make the script work without errors.Problems identified:
random = 42 shadows the random module's functions.path = "/home/user" shadows the os.path module, causing path.join to fail.randint function's origin is unclear (it could be from random or another module).Refactored script:
import math
import random
import os
# Use clear variable names that don't shadow modules
custom_random_value = 42
home_path = "/home/user"
# Calculate something
num = math.sqrt(16)
print(f"The square root is {num}")
# Random number generation
print(random.randint(1, 10))
# File path joining - using os.path explicitly
print(os.path.join(home_path, "documents"))
Why it's better: The refactored version uses explicit imports, so the source of every function is immediately clear. The variable names no longer shadow module names, preventing confusing bugs. This code is more maintainable, readable, and less error-prone for future developers.
Task:
Create a Python script that uses selective imports from the math module to perform geometric calculations. The script should:
pi, sin, cos, and tan.area_of_circle(radius) that returns pi * radius ** 2.sine_of_degrees(degrees) that converts degrees to radians (using math.radians – but you must import this specific function too) and returns the sine.hypotenuse(a, b) that uses sqrt (hint: import it) to calculate the square root of a**2 + b**2.Requirements:
.py file.if __name__ == "__main__": block that demonstrates each function with sample inputs.math module; only import what you need.Submission: Provide the full Python script.
# geometry.py
from math import pi, sin, cos, tan, radians, sqrt
def area_of_circle(radius):
"""Calculate the area of a circle given its radius."""
return pi * radius ** 2
def sine_of_degrees(degrees):
"""Calculate the sine of an angle given in degrees."""
return sin(radians(degrees))
def hypotenuse(a, b):
"""Calculate the hypotenuse of a right triangle."""
return sqrt(a**2 + b**2)
if __name__ == "__main__":
# Test area of circle
r = 5
print(f"Area of circle with radius {r}: {area_of_circle(r):.2f}")
# Test sine of degrees
angle = 30
print(f"Sine of {angle} degrees: {sine_of_degrees(angle):.4f}")
# Test hypotenuse
a, b = 3, 4
print(f"Hypotenuse of triangle with sides {a} and {b}: {hypotenuse(a, b):.2f}")
Sample output:
Area of circle with radius 5: 78.54
Sine of 30 degrees: 0.5000
Hypotenuse of triangle with sides 3 and 4: 5.00
Task:
A developer writes the following code, but it fails with ImportError or NameError. Identify exactly why each line fails and propose a fix.
# buggy_imports.py
from datetime import date, timedelta, time
import sys
# Line 1: This fails
today = date.today()
# Line 2: This works fine
current_time = time.now()
# Line 3: This fails
one_week = timedelta(days=7)
# Line 4: This fails
print(sys.platform)
Assignment:
NameError, ImportError, AttributeError) and the exact reason.Line 1 failure: today = date.today()
AttributeError or TypeError depending on context.date is a class in the datetime module. When you import date directly, you get the class. date.today() is a class method, so this actually works if date is imported correctly. The issue might be that date is not imported in the correct way.Line 2 failure: current_time = time.now()
AttributeError: type object 'time' has no attribute 'now'time class from datetime does not have a now() method. now() is a class method of datetime, not time.Line 3 failure: one_week = timedelta(days=7)
NameError: name 'timedelta' is not definedtimedelta is imported, but it was imported incorrectly or the import statement failed. The import is from datetime import date, timedelta, time, which should work.Line 4 failure: print(sys.platform)
AttributeError: module 'sys' has no attribute 'platform'sys.platform (with 'a'), not sys.platform. This is a typo.Corrected script:
from datetime import date, timedelta, datetime
import sys
# Line 1: Works correctly
today = date.today()
# Line 2: Fixed - use datetime.now()
current_time = datetime.now()
# Line 3: Works correctly now
one_week = timedelta(days=7)
# Line 4: Fixed typo
print(sys.platform) # Note: 'platform' not 'platform'
Task: You are working on a large data analysis project. You frequently use the following modules and their components:
pandas (for DataFrames) – you use DataFrame, read_csv, and concat.numpy (for arrays) – you use array, mean, and std.matplotlib.pyplot (for plotting) – you use plot, title, and show.Questions:
import pandas as pd or from pandas import DataFrame, read_csv, concat? Justify your choice based on code length, clarity, and best practices.numpy, you use the functions mean and std hundreds of times in your script. Suggest the optimal import strategy.matplotlib.pyplot, you only use it in one specific function, but that function calls plot, title, and show multiple times. Suggest an import strategy that keeps the code clean without polluting the global namespace.Submission: Provide a written explanation (200-300 words) and code snippets demonstrating your import strategy.
1. Pandas Strategy:
I would use import pandas as pd. Although this means typing pd. before every function call, it provides several benefits. First, it's the widely accepted convention among Python data scientists, making code immediately recognizable. Second, it prevents namespace pollution and makes it clear that DataFrame comes from pandas. Third, the alias pd is short enough that the overhead is minimal. Using selective imports like from pandas import DataFrame, read_csv would be acceptable, but if I need more than a handful of pandas functions, import pandas as pd is cleaner.
2. NumPy Strategy:
For numpy, since I use mean and std hundreds of times, I would use selective imports with aliases to reduce typing:
from numpy import array, mean as np_mean, std as np_std
This keeps the array function directly accessible while using short aliases for the statistical functions. The aliases np_mean and np_std prevent confusion with any other mean or std functions in the script.
3. Matplotlib Strategy:
For matplotlib.pyplot, since it's used only in one function, I would import it inside that function:
def create_plot(data):
import matplotlib.pyplot as plt
plt.plot(data)
plt.title("My Plot")
plt.show()
This keeps the import local to the function, avoiding global namespace pollution. It also makes the dependency clear—anyone reading the function can see that it requires matplotlib.pyplot.
Full example:
import pandas as pd
from numpy import array, mean as np_mean, std as np_std
def create_plot(data):
import matplotlib.pyplot as plt
plt.plot(data)
plt.title("Data Visualization")
plt.show()
# Usage
df = pd.read_csv("data.csv")
values = array([1, 2, 3, 4, 5])
mean_val = np_mean(values)
print(f"Mean: {mean_val}")
create_plot(values)
__all__ and Wildcard InteractionsTask: Consider this package structure:
my_pkg/
├── __init__.py # Contains: __all__ = ['module_a']
├── module_a.py # Contains: def a_func(): print("A")
├── module_b.py # Contains: def b_func(): print("B")
└── module_c.py # Contains: def c_func(): print("C")
Now, a user writes this script:
from my_pkg import *
module_a.a_func() # Works
module_b.b_func() # Fails with NameError
module_c.c_func() # Fails with NameError
Assignment:
module_b and module_c are not available even though they exist in the directory.__init__.py)?from my_pkg import * to get access to module_a, module_b, and module_c, how would you modify __init__.py?__all__ is a responsible design choice.1. Why module_b and module_c are not available:
The __init__.py file defines __all__ = ['module_a']. When a user runs from my_pkg import *, Python only imports the names listed in __all__. Since module_b and module_c are not in this list, they are not imported into the user's namespace, even though they exist in the directory.
2. Two ways to import all three modules without modifying __init__.py:
Method 1: Use explicit imports:
import my_pkg.module_a
import my_pkg.module_b
import my_pkg.module_c
Or with aliases: import my_pkg.module_a as ma, etc.
Method 2: Import each module separately:
from my_pkg import module_a
from my_pkg import module_b
from my_pkg import module_c
3. How to modify __init__.py to allow all three:
# my_pkg/__init__.py
__all__ = ['module_a', 'module_b', 'module_c']
4. Why controlling wildcard imports with __all__ is responsible:
Defining __all__ gives package authors control over their public API. It prevents users from accidentally importing internal or experimental modules that are not intended for public use. This protects users from relying on implementation details that might change in future versions, ensuring better backward compatibility and cleaner code.
from ... import ... statement selectively imports specific names (functions, classes, variables, or submodules) into your current namespace.as keyword to alias selectively imported items, which is essential for resolving naming conflicts or shortening long names.from package.subpackage import module.from module import *) is a dangerous anti-pattern in production code because it pollutes the namespace, reduces readability, masks dependencies, and can silently overwrite variables.import os and from os import path depends on your specific use case. The full module import (import os) is generally safer and more explicit.import module over from module import *, and only use selective imports for names you use very frequently, ensuring you don't shadow existing names.This concludes the Tutorial 4. Once you have completed the exercises and homework, you will have a nuanced understanding of when and how to use selective imports effectively and safely. You are now ready to move on to Tutorial 5, where we will dive into the Python Standard Library and explore some of its most widely used modules in detail!