-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Annotations for set.__sub__
and related methods are inconsistent
#9004
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Comments
You want an error for |
@Akuli why do you think there should be an error for |
A user shipped code that did |
@Akuli that makes sense. But what about Line 1101 in cf9bdc2
Shouldn't they take I'm also wondering where this |
Good point. In fact, the code from #1840 no longer errors as it should. Apparently this was broken in #7161. @AlexWaygood, what are your thoughts on this?
You often want to do
but that isn't possible, and I think we should use the |
set.__sub__
set.__sub__
and related methods are inconsistent
@Akuli you mentioned that when we do Consider the following example: class TypeA: pass
class TypeB: pass
class TypeAB(TypeA, TypeB): pass
ab = TypeAB()
a: set[TypeA] = {TypeA(), ab}
b: set[TypeB] = {TypeB(), ab}
print(a - b) # {<TypeA object>}
# (isn't no-op) Even if Currently, type checkers complain about the last line, and I believe they shouldn't. What do you think? |
An intersection instance of Here's what the interpreter says if you try:
Edit: Nevermind, you mentioned this already. I get your point, it would be practically almost any type. Maybe we should just stick to |
My proposal is to make |
I would prefer "practicality beats purity". Even though we usually want to prevent false positives, we also don't want people to ship broken code to production, which IMO is more important than rarely getting extra warnings during development. |
What broken code are you talking about? The code in #1840 may be considered broken because it's no-op. But it's no-op only because we can't create a subclass of both |
By broken code, I mean code that doesn't work as intended, in this case |
When you write |
Another example: my_ints = {1, 2, 3}
my_objs = {1, None, 'a', 2}
print(my_ints - my_objs) # {3} I'm getting an error for the last line. This is pretty common scenario though. How am I supposed to do it differently? I can write |
@AlexWaygood What are your thoughts on this issue? Your PR #7161 made the methods inconsistent, and while you argued that |
I'll try to take a look at this today, sorry for the radio silence! |
Here's an annoying consequence of this: COLS = ("TIME", "VALUE")
"""Needed columns."""
csv_data: list[list[str]] = [[]]
header = csv_data[0]
if missing_cols := set(COLS) - set(header): # reportOperatorIssue
raise ValueError(f"Missing columns: {missing_cols}")
I would agree that in most cases |
Not sure if that can be implemented at the moment, I think it may require generic bounds to type-vars (python/typing#548, python/mypy#8278) class MySet[T](MutableSet[T]):
@overload # case A≥B
def __sub__(self, other: AbstractSet[T]) -> Self: ...
@overload # case A≤B
def __sub__[B, A: B](self: MySet[A], other: AbstractSet[B]) -> MySet[A]: ... |
According to the documentation
s1 - s2
returns the set of all elements that are ins1
but not ins2
. So what's the point of constrainings2
elements type? I believe the correct annotation for the argument ofset.__sub__
would beAbstractSet[object]
rather thanAbstractSet[T | None]
.typeshed/stdlib/builtins.pyi
Line 1100 in cf9bdc2
The text was updated successfully, but these errors were encountered: