pythondecoratorcircular-dependency

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


What are some 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.


Solution

  • There are a number of solutions. Here are 4 possibilities:

    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