Skip to content
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

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

Open
finite-state-machine opened this issue Jan 26, 2024 · 3 comments
Open
Labels
bug mypy got something wrong topic-descriptors Properties, class vs. instance attributes

Comments

@finite-state-machine
Copy link

finite-state-machine commented Jan 26, 2024

[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
@finite-state-machine finite-state-machine added the bug mypy got something wrong label Jan 26, 2024
@finite-state-machine

This comment was marked as duplicate.

@JelleZijlstra JelleZijlstra added the topic-descriptors Properties, class vs. instance attributes label Jan 26, 2024
@erictraut
Copy link

The property decorator requires significant special-casing within a static type checker, so mypy's behavior here doesn't surprise me. You're using property in a very unusual manner here. FWIW, pyright's behavior is the same as mypy in this case.

@finite-state-machine
Copy link
Author

finite-state-machine commented Jan 26, 2024

You're using property in a very unusual manner here.

Agreed. I was hoping to work around #6700 as follows:

class SomeClass:
    ...
    some_prop = property(getter, setter)
    some_alias = property(getter, setter)

I guess I'll have to "spell it out" until #6700 is definitively addressed.

EliahKagan added a commit to EliahKagan/GitPython that referenced this issue Mar 8, 2024
HEAD inherits its commit attribute from SymbolicReference, which
defines it as a property using an explicit function call to
`property`, rather than using it as a decorator. Due at least to
python/mypy#16827, mypy has trouble with
this. Currently that assignment in SymbolicReference is marked as
type[ignore], which suppresses a type error (or something mypy
regards as one) in the call itself. Even without that type comment,
the type of the SymboilicReference instance attribute produced by
the property is inferred as Any. This commit does not change that.

This commit replaces the HEAD class's partially successful attempt
to annotate `commit` as `self.commit` in `__init__` with a fully
recognized annotation for `commit` in the class scope (which is
interpreted as an instance attribute).

Merely removing the annotation in `__init__` is sufficient to make
the mypy error for it go away, but this causes the inferred type of
`x.commit` when `x` is a `HEAD` instance to be inferred as Any
rather than the desired specific type Commit. That is why this also
adds an annotation to the class to achieve that without causing its
own mypy error as the old one did.

Once the SymbolicReference.commit property has a more specific
static type, however that is to be achieved, there will be no need
for the annotation added in the HEAD class body here. (This differs
from the situation in 3aeef46 before this, where Diffable does not
inherit any `repo` attribute--and is intended not to--and therefore
had to introduce an annotation for it, for mypy to accept it.)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug mypy got something wrong topic-descriptors Properties, class vs. instance attributes
Projects
None yet
Development

No branches or pull requests

3 participants