-
Notifications
You must be signed in to change notification settings - Fork 249
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to annotate empty containers? #157
Comments
I believe the standard type system approach is to do something like
(with "Nothing" defined as ⊥) Would adding "Nothing" to typing.py be an option? |
What's the use case of having a function that returns an empty list? What is the caller going to do with that list next? |
This problem arose from trying to annotate itertools.chain. An "empty-aware" annotation might look like this:
With that information, a type inferencer can figure out that in the code below,
Another example would be (Both these examples use *args, so another solution might be to just assume that since there's no "T" in the function arguments, the "T" in the return shouldn't exist. That's a bit too magical for my taste, though.) |
Still looks very theoretical to me. Why would anyone call chain(*a) knowing that a is an empty list? It also smells like Haskell-style pattern matching to me, which is just not a direction I want Python to take. |
A |
The type system defined by |
I think this issue is relevant. A common use case would be to express: "this is either an empty list or a list of xyz", e.g. "a list of tuples containing two strings". This occurs when dealing with tags: The html.parser.HTMLParser gives the attributes of a tag either as an empty list (whenever there are no attributes) or as a list of tuples containing two strings (namely name and value of the attribute). In a class derived from this Parser, there may be a method like: def transform_attrs(
tag:str,
attrs: [] or [(str, str)],
startend:bool,
) -> 'Tuple[str, [] or [(str, str)], bool]':
new_attrs = []
for attr in attrs:
key, value = attr
if not key.startswith('data-'):
# process value here
new_attrs.append((key, value))
return tag, new_attrs, startend So the question is how to express "[] or [(str, str)]" here. Something similar to the case 'Optional[list]' for 'Union[list, None]' would be nice. (Indeed, I had more use cases e.g. for "[] or [str]" than for 'Optional[List[str]]'.) In analogy to 'Optional', I would suggest 'Vacant'. E.g. def transform_attrs(
tag:str,
attrs: 'Vacant[List[Tuple[str, str]]]',
startend:bool,
) -> 'Tuple[str, Vacant[List[Tuple[str, str]]], bool]':
... # see above |
I don't get what you're trying to say here. The type |
Thank you for your answer (and, besides, for the work on typing and python in general, which is useful for me every day). So the rules probably are: List[str] - may be empty Sorry, I couldn't infer this for certain from reading https://www.python.org/dev/peps/pep-0484/ and https://docs.python.org/3.5/library/typing.html. I admit, the example from typing import List, cast
def find_first_str(a: List[object]) -> str:
index = next(i for i, x in enumerate(a) if isinstance(x, str))
# We only get here if there's at least one string in a
return cast(str, a[index]) If I call this function with an empty list, a StopIteration happens. Depending on the internal implementation of the function, it might also be an IndexError instead. I admit, in this example, the StopIteration also occurs if I call the function with a list that is non-empty but doesn't contain a So I thought it practical to specify (explicitly or implicitly) whether a container can be empty or not in order to prevent IndexErrors or unpacking errors. If |
The rule is just that all containers with a uniform type can be empty. While there may be some code that specifically requires a non-empty A much more important case is distinguishing nullable types from |
Sample API response JSON:
The JSON response has an always empty list. auto-generated Pydantic model with the datamodel-code-generator: class Response(BaseModel):
foo: int
currency: str
eggs: str
all_bars: List # <-- here is the type issue So, what is the best practice to declare an always empty list in a model? What about UPD: Sorry, I did not checked: UPD_2: EmptyList = Literal[""]
EmptyList.__args__ = [] # type: ignore[attr-defined]
class SomeClass:
all_bars: EmptyList UPD_3: MY CURRENT SOLUTION (for Pydantic): class EmptyList:
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def validate(cls, v):
if not isinstance(v, list):
raise TypeError('list required')
if len(v) > 0:
raise ValueError('list must be empty')
return []
class Model(BaseModel):
foo: int
spams: EmptyList |
@AIGeneratedUsername |
@gvanrossum filter functions can return an empty list if no element matched the condition. |
@gvanrossum also there's a case when we should return a value or empty string. Like Optional, instead of |
I believe returning |
Just to give an example where I think typing would be improved by supporting empty containers. I ended up with code like this:
But, in reality, the
That's probably not enough reason to actually implement the feature, but thought I'd share a reasonable use case. |
It seems like, eg, |
FYI, tuple includes length information in its type so:
for more info, see tuple type annotation docs |
Suppose I have a function that returns an empty list:
How to best annotate the return type?
The text was updated successfully, but these errors were encountered: