I have a base service, which provides common methods for all services. Moreover, this BaseService
serves as a service registry:
class BaseService:
instances = {}
@classmethod
def get_instance(cls) -> 'BaseService':
if cls.instances.get(cls) is None:
cls.instances[cls] = cls()
return cls.instances[cls]
class Service1(BaseService):
pass
class Service2(BaseService):
pass
Service1.get_instance()
Service2.get_instance()
Service1.get_instance()
The get_instance()
method is returning the children class instance and I feel that the current annotation -> 'BaseService'
is incorect. How should I annotate this method properly?
As I said in my comments, doing this for the classmethod of a baseclass is problematic because by definition, that method is going to shared with any subclasses. This is especially true for a singleton.
The workaround is to give each subclass its own similarly named method with the proper return value annotation. While this can be done with a class decorator, as illustrated in the earlier version of my answer, using a metaclass seems like a cleaner approach, so I've updated my answer accordingly:
class BaseServiceMeta(type):
""" Metaclass that properly annotates the return value of the get_instance() method of
any subclasses of the BaseService class.
"""
def __new__(metaclass, classname, bases, classdict):
cls = super(metaclass, metaclass).__new__(metaclass, classname, bases, classdict)
if classname != 'BaseService': # subclass?
# define function with the correct return value annotation
def get_instance() -> classname:
return super(cls, cls).get_instance() # call superclass classmethod
setattr(cls, 'get_instance', get_instance) # override inherited method
return cls
class BaseService(metaclass=BaseServiceMeta): # metaclass added
instances = {}
@classmethod
def get_instance(cls) -> 'BaseService':
if cls.instances.get(cls) is None:
cls.instances[cls] = cls()
return cls.instances[cls]
class Service1(BaseService):
pass
class Service2(BaseService):
pass
# show that the methods have the correct return annotation
print(repr(BaseService.get_instance.__annotations__['return'])) # -> 'BaseService'
print(repr( Service1.get_instance.__annotations__['return'])) # -> 'Service1'
print(repr( Service2.get_instance.__annotations__['return'])) # -> 'Service2'
# call subclass methods to show they return the correct singleton instance of each type
print(Service1.get_instance()) # -> <__main__.Service1 object at 0x004A07D0>
print(Service2.get_instance()) # -> <__main__.Service2 object at 0x004A07F0>
print(Service1.get_instance()) # -> <__main__.Service1 object at 0x004A07D0>