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

mqkwyuun  于 2022-12-02  发布在  Python
关注(0)|答案(2)|浏览(134)

假设我的类有这样的架构:

# 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是否唯一?

isr3a4wc

isr3a4wc1#

您可以使用建构函式建立中继类别,该建构函式使用集合来追踪每个执行严修化类别的名称,如果集合中已经存在指定的名称,则会引发例外状况:

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不允许一个类拥有多个元类,因此需要使AbstractReader继承自abc.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

2guxujil

2guxujil2#

这是一个非常特殊的情况,但可以用singleton pattern来解决。
为了方便我们自己,我们首先创建一个单例注解

# 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]

相关问题