python / python/mypy

Using `x = property(get_x)` in a class definition misbehaves

Open
#16,827 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug topic-descriptors
Dominant language
Python
Stars
20.6k
Forks
3.3k
PR merge metrics
PR metrics pending

Description

[It's difficult to believe this hasn't been reported before, but, with apologies, I haven't been able to find an open issue describing this.]

Bug Report

Mypy doesn't support using @property as a function in a class definition. This makes it challenging to work around #6700.

We might expect these declarations to be similar (a11y version follows):

# with '@property' used as a function:            │  # with '@property' used as a decorator:
                                                  │
class SomeClass:                                  │  class SomeClass:
                                                  │
    def _get_foo(self) -> int:                    │      def _get_foo(self) -> int:
        return 42                                 │          return 42
                                                  │
    foo = property(get_foo)                       │      @property
                                                  │      def foo(self) -> int:
                                                  │          return _get_foo()
Accessible version of the above
# with '@property' used as a function:

class SomeClass:

    def _get_foo(self) -> int:
        return 42

    foo = property(get_foo)


# with '@property' used as a decorator:

class SomeClass:

    def _get_foo(self) -> int:
        return 42

    @property
    def foo(self) -> int:
        return _get_foo()

But in mypy, the left form results in foo having type Any, whether accessed on the class itself or an instance.

To Reproduce

mypy-play.net: gist

from typing_extensions import (
    assert_type,
    )

class SomeClass:
    @property
    def controlcase(self) -> int:
        return 42

    def get_testcase(self) -> int:
        return 42

    testcase = property(get_testcase)

inst = SomeClass()

# 'testcase' should behave the same way as 'controlcase':
reveal_type(SomeClass.controlcase)  # ... "def (self: SomeClass) -> int"
reveal_type(inst.controlcase)       # ... "int"

# but it does not:
reveal_type(SomeClass.testcase)     # ... "Any"
reveal_type(inst.testcase)          # ... "Any"

Expected Behavior

controlcase and testcase should be indistinguishable

Actual Behavior

Mypy doesn't understand @property when used as a function.

Your Environment

  • Mypy version used: 1.8.0 (and probably at least as far as 0.730)
  • Mypy command-line flags: none
  • Mypy configuration options from mypy.ini (and other config files): none
  • Python version used: 3.8, 3.12

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the mypy-play.net reproduction and compare how the decorator form and property(get_testcase) form are analyzed. Use the reveal_type checks as the completion criteria: SomeClass.testcase and inst.testcase should match the corresponding controlcase types rather than becoming Any.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.