Supported behaviors
Typing¶
Unimport can understand that imports are used these cases.
from typing import List, Dict
def test(arg: List[Dict]) -> None:
pass
String¶
Unimport supports the following cases
from typing import List, Dict
def test(arg: 'List[Dict]') -> None:
pass
from typing import List, Dict
def test(arg: "List['Dict']") -> None:
pass
Comments¶
Imports in the example below aren’t flag as unused by unimport.
from typing import Any
from typing import Tuple
from typing import Union
def function(a, b):
# type: (Any, str) -> Union[Tuple[None, None], Tuple[str, str]]
pass
For more information
PEP 526 - Syntax for Variable Annotations
TYPE_CHECKING¶
Unimport recognizes if TYPE_CHECKING: blocks and skips imports inside them. These imports only run during static analysis and are not available at runtime, so they should never shadow or conflict with runtime imports.
from qtpy import QtCore
import typing as t
if t.TYPE_CHECKING:
from PySide6 import QtCore
class MyThread(QtCore.QThread):
pass
In this example, unimport correctly keeps from qtpy import QtCore (the runtime import) and ignores the TYPE_CHECKING-guarded import. Both if TYPE_CHECKING: and if typing.TYPE_CHECKING: (or any alias like if t.TYPE_CHECKING:) are supported.
All¶
Unimport looks at the items in the __all__ list, if it matches the imports, marks it as being used.
import os
__all__ = ["os"] # this import is used and umimport can understand
Other supported operations, append and extend
from os import *
__all__ = []
__all__.append("removedirs")
__all__.extend(["walk"])
after refactoring
from os import removedirs, walk
__all__ = []
__all__.append("removedirs")
__all__.extend(["walk"])
Star Import¶
When used with --include-star-import, unimport can refactor star imports into explicit imports by detecting which names are actually used in the code.
input
from os import *
from json import *
print(getcwd())
print(JSONEncoder)
output
from os import getcwd
from json import JSONEncoder
print(getcwd())
print(JSONEncoder)
Duplicate Name Resolution¶
When multiple star imports export the same name, unimport deduplicates suggestions so that only the last import provides each name (matching Python’s shadowing semantics). This produces correct output in a single pass.
input
from _collections import *
from collections import *
print(defaultdict)
output
from collections import defaultdict
print(defaultdict)
If the star imports partially overlap, each import keeps only its unique names:
input
from collections import *
from _collections import *
print(Counter)
print(defaultdict)
output
from collections import Counter
from _collections import defaultdict
print(Counter)
print(defaultdict)
Star import suggestions also respect explicit imports β if a name already has an explicit import, star imports won’t produce a duplicate for that name.
Subpackage Imports¶
import a.b binds a in the namespace, so any reference to a.* relies on that import. Unimport recognizes this and won’t remove a subpackage import when its root package is used in the code.
import urllib.request
def parse_url(url):
return urllib.parse.urlparse(url)
In this example, import urllib.request is kept because removing it would remove urllib from the namespace, breaking the urllib.parse.urlparse() call.
However, when a more specific import exists, unimport correctly identifies the redundant one:
input
import urllib.request
import urllib.parse
urllib.parse.urlparse('http://example.com')
output
import urllib.parse
urllib.parse.urlparse('http://example.com')
Here import urllib.parse directly provides the needed namespace, so import urllib.request is correctly flagged as unused.
Scope¶
Unimport tries to better understand whether the import is unused by performing scope analysis.
Let me give a few examples.
input
import x
def func():
import x
def inner():
import x
x
output
def func():
def inner():
import x
x
input
import x
class Klass:
def f(self):
import x
def ff():
import x
x
output
class Klass:
def f(self):
def ff():
import x
x
Deferred Execution¶
Function and async function bodies are deferred β they only execute when called. So a module-level import that appears textually after a function definition is still available when the function runs. Unimport understands this and won’t remove such imports.
def bob():
print(sys.path)
import sys # kept: sys is available when bob() is called
This applies to regular functions, async functions, nested functions, and methods:
async def fetch():
return aiohttp.get(url)
import aiohttp # kept
Class bodies execute immediately (not deferred), so the lineno check still applies:
class Foo:
x = sys.platform
import sys # removed: class body runs before this import