Reputation: 26895
I have a function which compares a file path against two lists of folder paths. It returns true if the start of the path matches any folder from list A (include
) but doesn't match an optional list B (exclude
).
from typing import List, Optional
def match(file: str, include: List[str], exclude: Optional[List[str]]=None):
return any(file.startswith(p) for p in include) and not any(
file.startswith(p) for p in exclude
)
The function works as expected if all parameter values are provided, but fails with a TypeError if no exclude
folders are given.
# True
match(file="source/main.py", include=["source/", "output/"], exclude=["output/debug/"])
# False
match(file="output/debug/output.log", include=["source/", "output/"], exclude=["output/debug/"])
# Expected result - True
# Actual result - TypeError: 'NoneType' object is not iterable
match(file="output/debug/output.log", include=["source/", "output/"])
There was a PEP proposal to introduce null-aware operators that may have helped, but it has not been implemented as of now.
How can I safely iterate over an Optional list in Python without running into null errors?
Upvotes: 0
Views: 1180
Reputation: 26895
Use an or
condition to check if exclude
is None
before iterating through it.
from typing import List, Optional
def match(file: str, include: List[str], exclude: Optional[List[str]]=None):
return any(file.startswith(p) for p in include) and (
exclude is None or not any(file.startswith(p) for p in exclude)
)
# True
match(file="output/debug/output.log", include=["source/", "output/"])
Upvotes: 0
Reputation: 532313
You don't seem to care if exclude
is really a list, just that it's iterable. In that case, you can change the default to an immutable empty tuple if you change the type to Iterable[str]
.
from typing import Iterable
def match(file: str, include: Iterable[str], exclude:Iterable[str]=()):
return (any(file.startswith(p) for p in include)
and all(not file.startswith(p) for p in exclude)
This also requires switching not any(... for p in exclude)
to all(not ... for p in exclude)
so that the test is vacuously true for the default value of exclude
.
Upvotes: 1
Reputation: 10033
Just use the "or []" technique to ensure "exclude" is only iterated when provided.
from typing import List, Optional
def match(file: str, include: List[str], exclude: Optional[List[str]]=None):
return any(file.startswith(p) for p in include) and not any(
file.startswith(p) for p in exclude or []
)
>>> match('foo', ['f',])
True
>>> match('foo', ['f',], ['foo'])
False
Upvotes: 1