【问题标题】:How to write interface (contract) for object property (attribute) existence in python?如何在python中为对象属性(属性)的存在编写接口(合同)?
【发布时间】:2019-10-22 21:53:52
【问题描述】:

我想对某个函数的参数进行约定,以强制参数对象必须具有特定属性。我知道 python 不是一种严格类型的语言,但有时拥有合同和接口非常有用。 Python 现在有类型提示,这很棒,所以我们可以这样做:

def myfunc(myparam: MyType) -> SomeType:
    myparam.myprop # this should exist

但是我怎么能说 MyType 必须有一个特定的对象属性(myprop)而不在运行时插入断言和抛出异常呢? 我可以用 abc 元类定义抽象类,它可以用作接口。

from abc import ABC, abstractmethod
class MyInterface(ABC):
     @property
     @abstractmethod
     def myprop(self) -> int: pass

现在我可以在代码中的某个地方将 MyType 定义为:

class MyType(MyInterface):
    myprop = 8

它正在工作,但是 myprop 是一个类属性,而不是一个对象属性(属性)。我当然可以这样做:

class MyType(MyInterface):
    myprop = 0
    def __init__(self):
        self.myprop = 8

很好,但是我必须定义一个(不必要的)类(“静态”)属性,并使用对象属性有效地隐藏它。不是很干净。此外,现在我有一个 myprop 的默认值,这不是我想要的。但如果我这样做:

class MyType(MyInterface):
    myprop = None  # wrong type here
    def __init__(self):
        self.myprop = 8

这是错误的,因为 myprop 必须是 int 并且不能是 None,这会被 linter 正确捕获。应该有一个没有类属性的对象属性。

目标是像 mypy 这样的静态检查器可以捕获实现错误,其中类不遵守定义的接口或要求参数实例必须具有某些属性的协定。

实现这一点的 Pythonic(或不是 Pythonic)方法是什么?

【问题讨论】:

  • 让我想起了这个stackoverflow.com/a/50381071 - 但我的自定义解决方案可能无法与您的 linter 配合使用。最后一个例子 - 但类型注释对我来说似乎最好。顺便提一句。 “属性”可能是比“对象属性”更好的关键字。
  • 术语“属性”添加以澄清。您的解决方案可以满足我的需要,我会看看 linter 是否也喜欢它。鸭子打字很时髦,但在某个地方我应该能够描述什么是鸭子。
  • 我对这种混合打字和 abc 术语感到有些困惑。您在寻找 静态类型检查 断言还是 abc 运行时 断言?并且您需要一些抽象/协议MyType 来保证每个实现 class 都有一个myprop attribute
  • @MisterMiyagi ABC 绝对是关于打字的。这里没有混合。我想定义一个类型安全的接口(或者最好是一个合同)。我想静态地强制执行一些关于对象参数的规则,比如“必须有一个特定的属性”。我肯定对运行时断言不感兴趣,这可以很简单地完成。当然,这在纯 python 中是不可能的,但在 linter 中可能是可能的。
  • 好吧,让我们专注于你到底需要什么。 “属性”是指具体的property 还是一般的任何属性?你真的想要一个“类属性”还是只是你非工作尝试的一部分?您是否需要类型检查器来验证每个实现 class MyType(MyInterface)MyType 是否满足 MyInterface,或者您只是希望能够定义一个可以在需要 MyInterface 的任何地方安全使用的 class MyType

标签: python python-3.x interface abstract-class contract


【解决方案1】:

您无需创建新接口或实现ABC。您可以使用@dataclass。我在 pycharm 中使用默认代码检查器(警告在 cmets 中)。

from dataclasses import dataclass


@dataclass
class MyType:
    my_prop: int


@dataclass
class SomeType:
    my_prop_out: int


def my_func_ok(my_param: MyType) -> SomeType:
    some_type = SomeType(my_prop_out=my_param.my_prop)
    return some_type


def my_func_bad(my_param: MyType) -> SomeType:
    return my_param              # this is not returning SomeType


my_type = MyType()               # this is expecting to set my_prop
my_type = MyType(my_prop="sss")  # this is expecting to set my_prop with int not str
my_func_ok(my_param=100)         # this is expecting MyType object

my_func_ok(my_param=MyType(my_prop=10))  # this is correct, no errors

我正在添加 pycharm 代码检查器警告的图片:


【讨论】:

  • 非常感谢您的反馈,出了什么问题?
  • 您能否阐明各个部分之间的关​​系?旧的abstractproperty 并不意味着二传手。 setter 不会影响property 在其 中的查找方式。分配 myprop = 10 不会创建“静态属性”——无论基类是否具有该名称的属性——它只是一个类属性。
  • @MisterMiyagi 我已经更准确地阅读了这个问题,是的,你是对的,再次;)。我已经编辑了我的答案。
  • 这可能是我们在 2021 年使用 python 3.7+(向后移植到 3.6)所拥有的最好的。不幸的是,这并不是一个真正的答案(我担心没有真正的解决方案)。 Dataclass 不会提供合同,实际上与 OP 中的示例 #3 并没有什么不同(实际上小于那个)。
  • @goteguru 我觉得这很有可能。这个问题只是(还)不清楚“这个”到底是什么。
猜你喜欢
  • 1970-01-01
  • 2017-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-10
  • 1970-01-01
  • 1970-01-01
  • 2018-10-13
相关资源
最近更新 更多