在python中为抽象变量定义具体类时检查唯一值



假设我的类有这样的体系结构:

# abstracts.py
import abc
class AbstractReader(metaclass=abc.ABCMeta):

@classmethod
def get_reader_name(cl):
return cls._READER_NAME
@classmethod
@property
@abc.abstractmethod
def _READER_NAME(cls):
raise NotImplementedError
# concretes.py
from .abstracts import AbstractReader
class ReaderConcreteNumber1(AbstractReader):
_READER_NAME = "NAME1"
class ReaderConcreteNumber2(AbstractReader):
_READER_NAME = "NAME2"

我还有一个管理器类,通过_READER_NAME变量找到具体的类。因此,我需要为每个具体类定义唯一的_READER_NAME

当具体类要定义时,我如何检查NAME1NAME2是唯一的?

可以用构造函数创建元类,构造函数使用集合来跟踪每个实例化类的名称,如果给定的名称已经存在于集合中,则引发异常:

class UniqueName(type):
names = set()
def __new__(metacls, cls, bases, classdict):
name = classdict['_READER_NAME']
if name in metacls.names:
raise ValueError(f"Class with name '{name}' already exists.")
metacls.names.add(name)
return super().__new__(metacls, cls, bases, classdict)

并使其成为AbstractReader类的元类。因为Python不允许一个类有多个元类,你需要让AbstractReaderabc.ABCMeta继承,而不是把它作为一个元类:

class AbstractReader(abc.ABCMeta, metaclass=UniqueName):
... # your original code here

或者如果你想在AbstractReader中使用ABCMeta作为元类,只需覆盖ABCMeta类并将AbstractReader中的子ABC设置为metaclass:

class BaseABCMeta(abc.ABCMeta):
"""
Check unique name for _READER_NAME variable
"""
_readers_name = set()
def __new__(mcls, name, bases, namespace, **kwargs):
reader_name = namespace['_READER_NAME']
if reader_name in mcls._readers_name:
raise ValueError(f"Class with name '{reader_name}' already exists. ")
mcls._readers_name.add(reader_name)
return super().__new__(mcls, name, bases, namespace, **kwargs)
class AbstractReader(metaclass=BaseABCMeta):
# Your codes ...

:

class ReaderConcreteNumber1(AbstractReader):
_READER_NAME = "NAME1"
class ReaderConcreteNumber2(AbstractReader):
_READER_NAME = "NAME1"

会产生:

ValueError: Class with name 'NAME1' already exists.

演示:https://replit.com/@blhsing/MerryEveryInternet

这是一个非常特殊的情况,但它可以通过单例模式解决。

为了方便我们自己,我们首先创建一个单例注释

# anotations.py
def singleton(clazz):
"""Singleton annotator ensures the annotated class is a singleton"""
class ClassW(clazz):
"""Creates a new sealed class from the object to create."""
_instance = None
def __new__(cls, *args, **kwargs):
if ClassW._instance is None:
ClassW._instance = super(ClassW, cls).__new__(clazz, *args, **kwargs)
ClassW._instance._sealed = False
return ClassW._instance
def __init__(self, *args, **kwargs):
if self._sealed:
return
super(ClassW, self).__init__(*args, **kwargs)
self._sealed = True
ClassW.__name__ = clazz.__name__
return ClassW

现在构造一个单例Registry类,在其中注册我们的类并进行检查。

# registry.py
from .annotations import singleton 
@singleton
class ReaderRegistry:
"""
Singleton class to register processing readers
### Usage
To register a block call the register function with an ID and the class object.
ReaderRegistry().register('FooName', FooReader)
The class for the block can then be obtained via
Registry()['FooName']
"""
registry = {}
def register(self, key: str, clazz: Type[Block]) -> None:
"""Register a new reader. Names must be unique within the registry"""
if key in self:
raise f"Reader with key {key} already registered."
self.registry[key] = clazz
def __contains__(self, key: str) -> bool:
return key in self.registry.keys()
def __getitem__(self, key: str) -> Type[Block]:
return self.registry[key]

现在,你可以

#concretes.py 
from .abstracts import AbstractReader
from .registry import ReaderRegistry
class ReaderConcreteNumber1(AbstractReader):
_READER_NAME = "NAME1"
# Note that this is OUTSIDE and AFTER the class definition, 
# e.g. end of the file. 
RederRegistry().register(ReaderConcreteNumber1._READER_NAME , ReaderConcreteNumber1)

如果注册表中已经存在具有该名称的读取器,则一旦导入该文件将引发异常。现在,您只需在注册表中根据类的名称查找要构造的类,例如

if reader _namenot in ReaderRegistry():
raise f"Block [{reader _name}] is not known."
reader = ReaderRegistry()[reader _name]

最新更新