Skip to content

Property, classmethod and staticmethod aliases not supported #6700

Closed

Description

@serhiy-storchaka

MyPy does not support property aliases. Example:

$ cat a.py 
class A:
    @property
    def f(self) -> int:
        return 1
    g = f

x: int = A().f
y: int = A().g
$ mypy a.py 
a.py:8: error: Incompatible types in assignment (expression has type "Callable[[], int]", variable has type "int")

Activity

  1. ilevkivskyi commented on Apr 19, 2019

    @ilevkivskyi
    Member

    Thanks for reporting!

    Yeah, unfortunately properties are known to be often problematic. They were implemented via some special-casing before a more general descriptor support was added.

  2. serhiy-storchaka commented on Apr 19, 2019

    @serhiy-storchaka
    MemberAuthor

    The problem is not only with properties, but with static and class methods too.

    $ cat b.py
    class A:
        @classmethod
        def f(cls) -> int:
            return 1
        g = f
    
    x: int = A().f()
    y: int = A().g()
    $ mypy b.py
    b.py:8: error: Invalid self argument "A" to attribute function "g" with type "Callable[[Type[A]], int]"
    
    $ cat c.py
    class A:
        @staticmethod
        def f() -> int:
            return 1
        g = f
    
    x: int = A().f()
    y: int = A().g()
    $ mypy c.py
    c.py:8: error: Attribute function "g" with type "Callable[[], int]" does not accept self argument
    
  3. changed the title [-]Property aliases not supported[/-] [+]Property, classmethod and staticmethod aliases not supported[/+] on Apr 19, 2019
  4. ilevkivskyi commented on Apr 25, 2019

    @ilevkivskyi
    Member

    Hm, this is more serious than I though then, raising priority to high.

  5. ex-nerd commented on Dec 31, 2019

    @ex-nerd

    And apparently with callable properties on a dataclass (though the same errors are returned for a class with callable properties, which do not receive a self passed in when they are called):

    from typing import Callable
    from dataclasses import dataclass
    
    @dataclass
    class Demo:
        call_this: Callable[[str], str]
    
    def call(s: str) -> str:
        return s
    
    demo = Demo(call_this=call)
    
    demo.call_this("test")

    When run through mypy, this gets:

    demo.py:13: error: Invalid self argument "Demo" to attribute function "call_this" with type "Callable[[str], str]"
    demo.py:13: error: Too many arguments
    
  6. bzoracler commented on Feb 21, 2022

    @bzoracler
    Contributor

    Currently, this issue forces rather awkward typing for @classmethods, as we can't even omit the typing inside the class definition. AFAIK, the current only workarounds are:

    • Hint the alias as typing.Callable[..., <return_type>] inside the class definition
    • Bind the alias outside of the class definition (no need to provide a type-hint)

    I'm not sure what is supposed to be a convincing way to type the alias; some attempts below:

    from __future__ import annotations
    
    from typing import Callable
    from types import MethodType
    
    
    class A:
        @classmethod
        def _internal(cls) -> int:
            return 0
    
        public_1 = _internal                            # (bad; see `value_1`)
        public_2: classmethod[int] = _internal          # Incompatible types in assignment (expression has type "Callable[[Type[A]], int]", variable has type "classmethod[int]")
        public_3: Callable[[], int] = _internal         # Incompatible types in assignment (expression has type "Callable[[Type[A]], int]", variable has type "Callable[[], int]")
        public_4: Callable[[type[A]], int] = _internal  # (bad; see `value_4`)
        public_5: MethodType = _internal                # Incompatible types in assignment (expression has type "Callable[[Type[A]], int]", variable has type "MethodType")
        public_6: Callable[..., int] = _internal        # OK
    
    public_7 = A._internal                              # OK
    
    
    value_1: int = A.public_1()  # Too few arguments
    value_2: int = A.public_2()  # (bad; see `public_2`)
    value_3: int = A.public_3()  # (bad; see `public_3`)
    value_4: int = A.public_4()  # Too few arguments
    value_5: int = A.public_5()  # (bad; see `public_5`)
    value_6: int = A.public_6()  # OK
    value_7: int = public_7()    # OK

    public_4 / value_4 is especially concerning, as it is contradictory; what mypy thinks is the correct type for the public_4 method causes a mypy error when actually called.

  7. erictraut commented on Feb 21, 2022

    @erictraut

    Do you really require it to be an alias? Here's another workaround:

    class A:
        @classmethod
        def public_8(cls) -> int:
            return cls._internal()
  8. mvadari commented on Jul 6, 2023

    @mvadari

    Are there any updates on this issue?

  9. 00-kat commented on Mar 2, 2025

    @00-kat

    I'm not sure if this is useful information, but I just want to note that the types (according to reveal_type) seem to only be incorrect:

    • For properties: in instances but not the class itself.
    class Foo:
        @property
        def colour(self) -> int:
            return 42
    
        color = colour
    
    
    reveal_type(Foo.colour)  # def (self: repro.Foo) -> builtins.int
    reveal_type(Foo.color)  # def (self: repro.Foo) -> builtins.int
    
    reveal_type(Foo().colour)  # builtins.int
    reveal_type(Foo().color)  # def () -> builtins.int
    • For classmethods: in the class but not instances.
    class Foo:
        @staticmethod
        def bar(cls) -> int:
            return 42
    
        life = bar
    
    
    reveal_type(Foo.bar)  # def () -> builtins.int
    reveal_type(Foo().bar)  # def () -> builtins.int
    
    reveal_type(Foo.life)  # def (cls: type[repro.Foo]) -> builtins.int
    reveal_type(Foo().life)  # def () -> builtins.int
    • For staticmethods: nowhere, allegedly.
    class Foo:
        @staticmethod
        def bar() -> int:
            return 42
    
        life = bar
    
    
    reveal_type(Foo.bar)  # def () -> builtins.int
    reveal_type(Foo().bar)  # def () -> builtins.int
    
    reveal_type(Foo.life)  # def () -> builtins.int
    reveal_type(Foo().life)  # def () -> builtins.int

    The last line still has a diagnostic of Attribute function "life" with type "Callable[[], int]" does not accept self argument.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugmypy got something wrongfalse-positivemypy gave an error on correct codepriority-0-hightopic-descriptorsProperties, class vs. instance attributes

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions