-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
callables are inferred to be descriptors when they shouldn't always be, protocols are inferred to not be descriptors when maybe they should, and mypy converts between them with no error #15189
Comments
I've come to the same conclusion based on the problems discussed in this typeshed PR: python/typeshed#9834 Note that this problem affects all type checkers though, so maybe there needs to be a new PEP to specify the behavior. |
@glyph Your point about class Abstract(Protocol):
def stuff(self):
pass
class Impl(Abstract):
def __get__(self, instance: object, owner: object) -> str:
return "imposter"
def stuff(self):
print("stuff")
class Concrete:
var: Abstract = Impl()
Concrete().var.stuff() # runtime error: "imposter" detected But would this also apply to any and all subtypes? class A:
val = 1
class B(A):
def __get__(x, y, z):
return "imposter"
class Z:
a: A = B()
Z.a.val # runtime error: "imposter" detected Any thoughts? |
It had not occurred to me that the normal checking of subtype compatibility for https://mypy-play.net/?mypy=latest&python=3.12&flags=strict&gist=0ecd67b6fce983b0d27c900c4ec4d603 I'd argue that |
(The thing that is special about |
@glyph Do you want to discuss this further? I am interested in your perspective.
I don't quite understand what you mean by this. Do you mean that any Protocol can't be used as it is abstract? protocol Foo:
f: int
class A:
foo: Foo
def f(a: A):
a.foo # error, foo is abstract I you want we can discuss via email or a call or something. |
No, I mean the parts of a Protocol that aren't specified ought to be unspecified, not "specified to be the default behavior of |
Just to be clear about why this example doesn't make sense from my perspective: class A:
foo: ClassVar[Foo]
def f(a: A):
a.foo then yes, we don't know what class NonGettableFoo:
# otherwise conforms to Foo
def __get__(
self,
instance: object,
owner: type | None = None,
) -> None:
return None
class A:
foo: Foo = NonGettableFoo() # error, won't be Foo at instance level |
I don't quite follow what you are saying in regards to instance vs class: class D:
def __get__(x, y, z):
return None
class A:
d = D()
print(A.d) # None
print(A().d) # None |
if it was protocol P:
pass
p: P
p == 1
print("done") # error, unreachable Maybe |
Ah, fair enough. I suppose "unreachable" is not really the right error to report here. |
Right, right, I always forget about that nuance. It's not really "instance" vs. |
Bug Report
Callable
implicitly has a__get__
method which makes it a bindable functionwhen used at class scope.
Protocol
s with a__call__
method, on the other hand, are not assumed tohave a
__get__
method, and thus, not to be functions.However, implicit conversion is allowed between the two, leading to confusing
behavior in higher-order applications, such as decorators which wish to apply
in similar fashion to either methods or free functions. For example, consider
a decorator which returns a
__call__
ableProtocol
, in an effort to capturethe nuances of named and default arguments (since
mypy_extensions.NamedArg
et. al. are clearly
deprecated).
It needs to manually re-specify the behavior of
__get__
in order to betreated the same way as an equivalent
Callable
would.To Reproduce
Consider the errors and non-errors in this program, with particular emphasis on
the incredibly subtle distinction between
be_a_typevar
andbe_a_callable
,which behave differently:
Proposed Solution
I think there really needs to be a
FunctionType
, and it should behave more orless like
Callable
does today with respect to the descriptor protocol. Itshould also have all the attributes that functions have, so that
metaprogramming with
__code__
and__name__
and soforth doesn't need to belittered with
type:ignore
s.In strict mode, I also think that this should become an error:
Any concrete type can trace its implementation of
__get__
up through itshierarchy to
object
, butProtocol
might have any implementation, or noimplementation. It can't really be treated as if we can guess what it's going
to return.
In general
Callable
and descriptors and all the adjacent special cases arekind of a big mess and this keeps getting reported in different ways. See
also:
Callable
assumed to be a user defined function? #14392And this one is only tangentially related, but it makes it more annoying to
write the
__get__
for the descriptor protocol directly to work around it:self
#15188The text was updated successfully, but these errors were encountered: