【问题标题】:How to override an abstract property method without forgetting to add the property decorator in the child class?如何覆盖抽象属性方法而不忘记在子类中添加属性装饰器?
【发布时间】:2023-03-18 16:53:02
【问题描述】:

如果我重写了抽象属性方法,但忘记指定它仍然是子类中的属性方法,我希望 Python 解释器对我大喊大叫。

class Parent(metaclass=ABCMeta):
  @property
  @abstractmethod
  def name(self) -> str:
    pass

class Child(Parent):
  @property # If I forget this, I want Python to yell at me.
  def name(self) -> str:
    return 'The Name'

if __name__ == '__main__':
  print(Child().name)

Python 真的没有内置方法可以做到这一点吗?我真的必须创建自己的装饰器来处理这种类型的行为吗?

【问题讨论】:

    标签: python python-3.x properties abstract-class


    【解决方案1】:

    您可以使用metaclass

    class Parent(type):
      def __new__(cls, name, bases, body):
        if 'name' not in body.keys() or body['name'].__class__.__name__ != 'property':
          raise TypeError(f"Can't instantiate class {name} without property name")
        return super().__new__(cls, name, bases, body)
    
    
    class Child(metaclass=Parent):
      @property # If I forget this, raise TypeError
      def name(self) -> str: # also, name must be implemented
        return 'The Name'
    
    if __name__ == '__main__':
      print(Child().name)
    

    这会引发TypeError: Can't instantiate class Child without property name - 当@property 被注释掉时!

    【讨论】:

    • 感谢您提供替代解决方案;这是非常有见地的。赞成。
    【解决方案2】:

    您可以在 Parent 的 __init__ 方法中进行运行时检查,如果 name 是一个方法,则引发异常。

    class Parent(metaclass=ABCMeta):
      def __init__(self):
        assert not callable(self.name)
    
      @abstractmethod
      def name(self) -> str:
        pass
    
    
    class GoodChild(Parent):
      @property
      def name(self) -> str:
        return 'The Name'
    
    
    class BadChild(Parent):
      def name(self) -> str:
        return 'Whoops, not a property'
    
    
    if __name__ == '__main__':
      good_child = GoodChild()
      print(good_child.name)   # Prints 'The Name'
      bad_child = BadChild()   # Raises an AssertionError when initialized
    

    【讨论】:

    • IMO,这是最简单的答案,所以我将标记为最佳答案。谢谢。
    【解决方案3】:

    TLDR - 在我看来,不值得麻烦:

    在 linting/mypy 和单元测试之间,应该涵盖您的大部分需求以及围绕类分析/元类的小技巧可能不值得它们带来额外的认知负担。您只会“失败”一次装饰,但会每次都必须阅读异国情调的脚手架代码以了解您想做的事情。

    详细信息 - 实际使用被标记为什么?

    if badchild1.name.startswith("John"): 将在运行时失败,我希望 mypy 或 pylint 例如也能在分析中进行标记,因为它将是一个方法对象。串联也是如此。唯一真正需要监督的是 f 字符串、直接布尔值或 ==!= 相等比较,它们不在乎它不是字符串。

    pylint 这么说:

    test_171_prop.py:11 Method 'name' was expected to be 'property', found it instead as 'method' (invalid-overridden-method)
    

    mypy 没有问题。

    但是,如果我添加这个:

    child = Child()
    
    print("name:" + child.name)
    

    然后 mypy 说:

    test_171_prop.py:16: error: Unsupported operand types for + ("str" and "Callable[[], str]")
    Found 1 error in 1 file (checked 1 source file)
    

    并用 2 个新行运行代码:

    TypeError: can only concatenate str (not "method") to str
    

    【讨论】:

    • 我真的很喜欢这个答案,但它并不总是适用于可能无法在工作中使用这些工具的人。
    • @FriskySaga 没问题。为我的回答中的“不要这样做”方面道歉,这往往会激怒我自己对 Stackoverflow 的看法。虽然我不能谈论你的上下文,但如果我正在照顾一群插入框架代码的用户,我预计会有比 just @property 缺少装饰器更多的傻瓜。我的处理将取决于 a) 犯错的频率和持续性,b) 它们造成的破坏程度 - 显然,如果每次忘记 @prop 时小狗都会死亡,这与 2 分钟的调整不同。我可能会通过文档/支持来解决更多问题
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-02
    • 1970-01-01
    • 2017-03-18
    • 2011-08-14
    相关资源
    最近更新 更多