声明抽象类属性的大多数Python方法

编程入门 行业动态 更新时间:2024-10-18 03:36:54
本文介绍了声明抽象类属性的大多数Python方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述

假设您正在编写一个抽象类,并且其一个或多个非抽象类方法要求具体类具有特定的类属性;例如,如果可以通过与不同的正则表达式进行匹配来构造每个具体类的实例,则可能需要为ABC提供以下内容:

Assume you're writing an abstract class and one or more of its non-abstract class methods require the concrete class to have a specific class attribute; e.g., if instances of each concrete class can be constructed by matching against a different regular expression, you might want to give your ABC the following:

@classmethod def parse(cls, s): m = re.fullmatch(cls.PATTERN, s) if not m: raise ValueError(s) return cls(**m.groupdict())

(也许使用自定义元类可以更好地实现,但为示例起见,请尝试忽略它.)

(Maybe this could be better implemented with a custom metaclass, but try to ignore that for the sake of the example.)

现在,因为覆盖了抽象方法&在实例创建时而不是子类创建时检查属性,尝试使用abc.abstractmethod来确保具体类具有PATTERN属性不会起作用-但可以肯定的是,这里应该有 something 来告诉任何人查看您的代码我没有忘记在ABC上定义PATTERN;具体的类应该定义自己的类."问题是:哪个东西最适合Python?

Now, because overriding of abstract methods & properties is checked at instance creation time, not subclass creation time, trying to use abc.abstractmethod to ensure concrete classes have PATTERN attributes won't work — but surely there should be something there to tell anyone looking at your code "I didn't forget to define PATTERN on the ABC; the concrete classes are supposed to define their own." The question is: Which something is the most Pythonic?

  • 装饰堆 @property @abc.abstractmethod def PATTERN(self): pass

    (顺便说一下,假设使用Python 3.4或更高版本.)这可能会误导读者,因为这暗示PATTERN应该是实例属性而不是类属性.

    (Assume Python 3.4 or higher, by the way.) This can be very misleading to readers, as it implies that PATTERN should be an instance property instead of a class attribute.

    装饰塔

    @property @classmethod @abc.abstractmethod def PATTERN(cls): pass

    这对读者来说非常混乱,因为@property和@classmethod通常不能结合使用.它们仅在此处一起工作(对于给定的工作"值),因为一旦重写该方法,该方法就会被忽略.

    This can be very confusing to readers, as @property and @classmethod normally can't be combined; they only work together here (for a given value of "work") because the method is ignored once it's overridden.

    虚拟值

    PATTERN = ''

    如果具体类未能定义其自己的PATTERN,则parse将仅接受空输入.此选项并不广泛适用,因为并非所有用例都将具有适当的虚拟值.

    If a concrete class fails to define its own PATTERN, parse will only accept empty input. This option isn't widely applicable, as not all use cases will have an appropriate dummy value.

    导致错误的虚拟值

    PATTERN = None

    如果具体类未能定义自己的PATTERN,则parse将引发错误,程序员将得到应有的报酬.

    If a concrete class fails to define its own PATTERN, parse will raise an error, and the programmer gets what they deserve.

    不执行任何操作.基本上是#4的更严格的变体. ABC的文档字符串中可能有一个注释,但ABC本身不应该使用PATTERN属性的形式.

    Do nothing. Basically a more hardcore variant of #4. There can be a note in the ABC's docstring somewhere, but the ABC itself shouldn't have anything in the way of a PATTERN attribute.

    其他???

    推荐答案

    Python> = 3.6版本

    (向下滚动以找到适用于Python< = 3.5的版本).

    (Scroll down for a version that works for Python <= 3.5).

    如果有幸仅使用Python 3.6,而不必担心向后兼容性,则可以使用新的 __init_subclass__ 方法,该方法在Python 3.6中引入到使自定义类的创建更加容易,而无需使用元类.定义新类时,它被称为创建类对象之前的最后一步.

    If you are fortunate enough to only be using Python 3.6 and not have to worry about backwards compatibility, you can use the new __init_subclass__ method which was introduced in Python 3.6 to make customizing class creation easier without resorting to metaclasses. When defining a new class, it is called as the last step before the class object is created.

    我认为,使用此功能最有效的方法是制作一个类装饰器,该类装饰器接受要创建抽象的属性,从而使用户可以清楚地知道他们需要定义的内容.

    In my opinion, the most pythonic way to use this would be to make a class decorator that accepts the attributes to make abstract, thus making it explicit to the user what they need to define.

    from custom_decorators import abstract_class_attributes @abstract_class_attributes('PATTERN') class PatternDefiningBase: pass class LegalPatternChild(PatternDefiningBase): PATTERN = r'foo\s+bar' class IllegalPatternChild(PatternDefiningBase): pass

    回溯可能如下,并且发生在子类创建时,而不是实例化时.

    The traceback might be as follows, and occurs at subclass creation time, not instantiation time.

    NotImplementedError Traceback (most recent call last) ... 18 PATTERN = r'foo\s+bar' 19 ---> 20 class IllegalPatternChild(PatternDefiningBase): 21 pass ... <ipython-input-11-44089d753ec1> in __init_subclass__(cls, **kwargs) 9 if cls.PATTERN is NotImplemented: 10 # Choose your favorite exception. ---> 11 raise NotImplementedError('You forgot to define PATTERN!!!') 12 13 @classmethod NotImplementedError: You forgot to define PATTERN!!!

    在显示装饰器的实现方式之前,先展示一下如何在没有装饰器的情况下实现此方法很有帮助.这里的好处是,如果需要,您可以使基类成​​为抽象基类,而无需执行任何工作(只需继承自abc.ABC或使元类成为abc.ABCMeta).

    Before showing how the decorator is implemented, it is instructive to show how you could implement this without the decorator. The nice thing here is that if needed you could make your base class an abstract base class without having to do any work (just inherit from abc.ABC or make the metaclass abc.ABCMeta).

    class PatternDefiningBase: # Dear programmer: implement this in a subclass OR YOU'LL BE SORRY! PATTERN = NotImplemented def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) # If the new class did not redefine PATTERN, fail *hard*. if cls.PATTERN is NotImplemented: # Choose your favorite exception. raise NotImplementedError('You forgot to define PATTERN!!!') @classmethod def sample(cls): print(cls.PATTERN) class LegalPatternChild(PatternDefiningBase): PATTERN = r'foo\s+bar'

    这是装饰器的实现方式.

    Here is how the decorator could be implemented.

    # custom_decorators.py def abstract_class_attributes(*names): """Class decorator to add one or more abstract attribute.""" def _func(cls, *names): """ Function that extends the __init_subclass__ method of a class.""" # Add each attribute to the class with the value of NotImplemented for name in names: setattr(cls, name, NotImplemented) # Save the original __init_subclass__ implementation, then wrap # it with our new implementation. orig_init_subclass = cls.__init_subclass__ def new_init_subclass(cls, **kwargs): """ New definition of __init_subclass__ that checks that attributes are implemented. """ # The default implementation of __init_subclass__ takes no # positional arguments, but a custom implementation does. # If the user has not reimplemented __init_subclass__ then # the first signature will fail and we try the second. try: orig_init_subclass(cls, **kwargs) except TypeError: orig_init_subclass(**kwargs) # Check that each attribute is defined. for name in names: if getattr(cls, name, NotImplemented) is NotImplemented: raise NotImplementedError(f'You forgot to define {name}!!!') # Bind this new function to the __init_subclass__. # For reasons beyond the scope here, it we must manually # declare it as a classmethod because it is not done automatically # as it would be if declared in the standard way. cls.__init_subclass__ = classmethod(new_init_subclass) return cls return lambda cls: _func(cls, *names)

    Python< = 3.5版本

    如果没有足够的幸运只使用Python 3.6,而不必担心向后兼容,则必须使用元类.尽管这是完全有效的Python,但人们可能会争论 pythonic 的解决方案是如何解决的,因为元类很难缠住您的大脑,但我认为它符合 Python的禅宗,所以我认为还不错.

    If you are not fortunate enough to only be using Python 3.6 and not have to worry about backwards compatibility, you will have to use a metaclass. Even though this is perfectly valid Python, one could debate how pythonic the solution is because metaclasses are hard to wrap your brain around, but I think it hits most of the points of The Zen of Python so I think it's not so bad.

    class RequirePatternMeta(type): """Metaclass that enforces child classes define PATTERN.""" def __init__(cls, name, bases, attrs): # Skip the check if there are no parent classes, # which allows base classes to not define PATTERN. if not bases: return if attrs.get('PATTERN', NotImplemented) is NotImplemented: # Choose your favorite exception. raise NotImplementedError('You forgot to define PATTERN!!!') class PatternDefiningBase(metaclass=RequirePatternMeta): # Dear programmer: implement this in a subclass OR YOU'LL BE SORRY! PATTERN = NotImplemented @classmethod def sample(cls): print(cls.PATTERN) class LegalPatternChild(PatternDefiningBase): PATTERN = r'foo\s+bar' class IllegalPatternChild(PatternDefiningBase): pass

    行为与上面显示的Python> = 3.6 __init_subclass__方法完全一样(但回溯看起来会有所不同,因为它会在失败之前通过一组不同的方法进行路由).

    This behaves exactly like the Python >= 3.6 __init_subclass__ method shown above (except the traceback will look bit different because it is routed through a different set of methods before failing).

    与__init_subclass__方法不同,如果要使子类成为抽象基类,则只需做一些额外的工作(您必须用ABCMeta组成元类).

    Unlike the __init_subclass__ method, if you want to make a subclass an abstract base class you will have to do just a bit of extra work (you'll have to compose the metaclass with ABCMeta).

    from abs import ABCMeta, abstractmethod ABCRequirePatternMeta = type('ABCRequirePatternMeta', (ABCMeta, RequirePatternMeta), {}) class PatternDefiningBase(metaclass=ABCRequirePatternMeta): # Dear programmer: implement this in a subclass OR YOU'LL BE SORRY! PATTERN = NotImplemented @classmethod def sample(cls): print(cls.PATTERN) @abstractmethod def abstract(self): return 6 class LegalPatternChild(PatternDefiningBase): PATTERN = r'foo\s+bar' def abstract(self): return 5 class IllegalPatternChild1(PatternDefiningBase): PATTERN = r'foo\s+bar' print(LegalPatternChild().abstract()) print(IllegalPatternChild1().abstract()) class IllegalPatternChild2(PatternDefiningBase): pass

    输出与您期望的一样.

    5 TypeError: Can't instantiate abstract class IllegalPatternChild1 with abstract methods abstract # Then the NotImplementedError if it kept on going.
  • 更多推荐

    声明抽象类属性的大多数Python方法

    本文发布于:2023-11-10 02:51:20,感谢您对本站的认可!
    本文链接:https://www.elefans.com/category/jswz/34/1574139.html
    版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
    本文标签:属性   声明   方法   抽象类   Python

    发布评论

    评论列表 (有 0 条评论)
    草根站长

    >www.elefans.com

    编程频道|电子爱好者 - 技术资讯及电子产品介绍!