简体   繁体   中英

What are some similar alternatives to using classmethod and property decorators together?

What are some similar alternatives to using classmethod and property decorators together?

In python 3.11 and later, combining them is no longer supported per:https://docs.python.org/3.11/library/functions.html#classmethod

I have code like so:

class Bike:
  @classmethod
  @property
  def tire_type(cls) -> tire_type.Road:
    return tire_type.Road

from . import tire_type

tire_type import must be last because it has a cyclic dependency on the current module. What are some options for providing the tire property in the Bike class that does not combine the two decorators?

I also want the tire_type type hint to show up correctly in vscode.

You can always implement your own descriptor. Here is a bare-bones one that assumes the method you are decorating is a "getter":

>>> class classproperty:
...     def __init__(self, method):
...         self.method = method
...     def __get__(self, obj, cls=None):
...         if cls is None:
...             cls = type(obj)
...         return self.method(cls)
...
>>> class Foo:
...     bar = 42
...     @classproperty
...     def baz(cls):
...         return cls.bar * 2
...
>>> class FooSpecial(Foo):
...     bar = 11
...
>>> Foo.baz
84
>>> FooSpecial.baz
22

Both classmethod and property are implemented as descriptors. You can read more about how the descriptor protocol works in the Descriptor HowTo which actually shows pure python implementations of property and classmethod which you can consult if you need to elaborate on the above.

Some options are

  1. Write a custom descriptor that uses a generic to return the wrapped type
T = typing.TypeVar('T')

class classprop(typing.Generic[T]):
    def __init__(self, method: typing.Callable[..., T]):
        self.method = method
        functools.update_wrapper(self, method) # type: ignore

    def __get__(self, obj, cls=None) -> T:
        if cls is None:
            cls = type(obj)
        return self.method(cls)


class Bike:
    @classprop
    def tire_type(cls) -> typing.Type[tire_type.Road]:
        return tire_type.Road

print(Bike.tire_type)
  1. Make the method classmethod only, this keeps class access but () are required
  2. Make it staticmethod but () are required
  3. Make the class a dataclass and use default_factory to return the desired class
@dataclasses.dataclass
class Bike
    tire_type: typing.Type[tire_type.Road] = dataclasses.field(
        default_factory=lambda: tire_type.Road
    )


print(Bike().tire_type)

Option 1 or 4 is preferred because it does not need () and it allows a type discriptions of the property and delayed evaluation of the property later when accessing Bike or instantiating it

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM