You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
at runtime dataclass special-cases FunctionType, but at type-time mypy special-cases Callable, leading to mismatches #14869
If you declare an attribute on a dataclass to be Callable, Mypy assumes that its __get__ will not be called, even on a default attribute. But this is only true if the default is a FunctionType, not some other type of callable.
Similarly, the inverse is true: an abstract type (such as a callable Protocol) will tell mypy that __get__will be called, even though if the concrete default value is a FunctionType, it won't be.
Specifically, the following program contains 2 type errors, but MyPy does not think so:
I would expect runtime and type-time behavior to be consistent here. Specifically I just don't expect __get__ to be called on things with a Callable type.
Actual Behavior
Mypy succeeds, rather than reporting the type errors.
Your Environment
Mypy version used: mypy 1.1.1 (compiled: yes)
Mypy command-line flags: none
Mypy configuration options from mypy.ini (and other config files): none
Python version used: 3.11.2 (python.org, macOS, aarch64)
/home/classic/dev/sqlalchemy/test3.py
/home/classic/dev/sqlalchemy/test3.py:21:18 - warning: Instance methods should take a "self" parameter (reportSelfClsParameterName)
It's not that dataclasses discern between "Callable" and "FunctionType", it's just that anything that quacks like a descriptor triggers the behavior described here.
Note the __get__ is only called a couple times, with instance=None, meaning that it's not useful to e.g. bind a method to an instance. Maybe what you're hoping to do isn't even possible?
It looks to me like accidental behavior that got documented, given how it's a mere side-effect of how the default is represented (as a class variable's value), and how changing from Python syntax = d to = field(default=d) eliminates this behavior:
class Descriptor:
def __get__(self, instance, owner):
return 42
d = Descriptor()
@dataclass
class C:
- desc: Descriptor = d+ desc: Descriptor = field(default=d)
c = C()
-assert c.desc == 42+assert isinstance(c.desc, Descriptor)
Right now the plugin does not checks for field's type vs. default's type — it kinda happens on it own (due to typeshed annotations), thought it might be doable.
But is it even worthwhile to replicate given how odd and accidental this feature seems?
Activity
zzzeek commentedon Mar 10, 2023
I think for pep681 it's really important to keep pyright in the loop because the reference implementation is that of pyright: https://peps.python.org/pep-0681/#reference-implementation
for the above test case pyright only reports:
/home/classic/dev/sqlalchemy/test3.py
/home/classic/dev/sqlalchemy/test3.py:21:18 - warning: Instance methods should take a "self" parameter (reportSelfClsParameterName)
for general pep-681 behavior you might want to raise it at https://github.com/python/peps/issues
ikonst commentedon Mar 12, 2023
It's not that dataclasses discern between "Callable" and "FunctionType", it's just that anything that quacks like a descriptor triggers the behavior described here.
Note the
__get__is only called a couple times, withinstance=None, meaning that it's not useful to e.g. bind a method to an instance. Maybe what you're hoping to do isn't even possible?It looks to me like accidental behavior that got documented, given how it's a mere side-effect of how the default is represented (as a class variable's value), and how changing from Python syntax
= dto= field(default=d)eliminates this behavior:class Descriptor: def __get__(self, instance, owner): return 42 d = Descriptor() @dataclass class C: - desc: Descriptor = d + desc: Descriptor = field(default=d) c = C() -assert c.desc == 42 +assert isinstance(c.desc, Descriptor)Right now the plugin does not checks for field's type vs. default's type — it kinda happens on it own (due to typeshed annotations), thought it might be doable.
But is it even worthwhile to replicate given how odd and accidental this feature seems?