【问题标题】:Logic in a constructor Python构造函数 Python 中的逻辑
【发布时间】:2020-03-27 14:24:47
【问题描述】:

我是 Python 中的 OOP 新手,假设我有一个可以进行简单计算的类:

class Calc: 
    def __init__(self, n1, n2): 
        self.n1 = n1 
        self.n2 = n2 

    def sum(self):
        return self.n1 + self.n2

在这个简化的示例中,验证类属性的最佳方法是什么?例如,假设我期望 n1n2 有一个 float,这样我将构造函数定义为:

self.n1 = float(n1) 
self.n2 = float(n2) 

如果 n1n2None 我会收到属性错误,因为 NoneType 不能是 float - 出于某种原因,我们在Calc 类的构造函数来捕获这个。

在创建类的实例以在上游捕获它之前,我是否需要某种验证逻辑?

有没有办法让我使用一些技术来动态验证,比如装饰器或属性注释?

感谢任何建议

【问题讨论】:

  • 啊谢谢@balderman 我知道这与properties有关
  • 你想要什么样的行为?你只是想告诉客户你的代码你期望什么类型,如果他们搞砸了,那是他们的错吗?你想强制你得到正确的类型吗?如果给出了错误的值,您是否想默默地选择一个默认值(正如您在 None 上的注释所暗示的那样)?您是否只想对传递给构造函数的参数或任何属性分配进行验证?您想要静态验证还是动态验证?
  • 所有有效问题,我从答案中获得了足够的信息,可以继续前进:)

标签: python python-3.x oop


【解决方案1】:

这取决于您从哪里获取数据以及您希望代码的简单程度。如果您希望此类绝对验证您无法信任的输入数据,例如因为它直接来自用户输入,所以你要进行显式验证:

class Calc: 
    def __init__(self, n1, n2): 
        if not all(isinstance(n, float) for n in (n1, n2)):
            raise TypeError('All arguments are required to be floats')

        self.n1 = n1 
        self.n2 = n2 

下一个级别是调试断言

class Calc: 
    def __init__(self, n1, n2): 
        assert all(isinstance(n, float) for n in (n1, n2)), 'Float arguments required'

        self.n1 = n1 
        self.n2 = n2 

assert statements can be disabled for performance gain,因此不应作为实际验证依赖。但是,如果您的数据在此之前通过验证层,并且您通常希望您的参数是浮点数,那么这是简洁明了的。它还兼作相当不错的自我文档。

这之后的下一步是type annotations

class Calc: 
    def __init__(self, n1: float, n2: float): 
        self.n1 = n1 
        self.n2 = n2 

这更具可读性和自文档化,但在运行时从不做任何事情。这依赖于静态类型检查器来分析你的代码并指出明显的错误,例如:

Calc(input(), input())

static type checker 可以发现此类问题并向您指出(因为已知input 会返回不符合类型提示的字符串),并且它们已集成在大多数现代 IDE 中。

哪种策略最适合您和您的情况,由您决定。日常代码中使用了所有三种方法的不同组合。

【讨论】:

  • 感谢您提供此信息,我将研究 explicit validationtype annotations 的组合 - 性能是这里的重中之重,但理想情况下,我希望有效数据继续,不正确的数据可能添加到 CSV 报告或其他内容中
  • 在运行前发现问题的单元测试和静态分析与确保有效性的实际运行时测试之间的权衡。这实际上取决于您的数据在到达这部分代码之前经过了哪些层,您是否需要显式检查。
  • 我可能会在它进入这个阶段之前使用assertions 创建一个小验证,因为我可以在那个阶段牺牲性能,但此时我需要它尽可能快
  • 如果属性不满足条件而不是引发错误,我将如何覆盖?
  • 我要求类传递正确的输入 - 我想在一定程度上回答我自己的问题,在接近 Calc 的实例之前,我应该更多地关注验证块/跨度>
【解决方案2】:

验证类型是一场你无法获胜的战斗。它带来了严重的开销,并且仍然无法保护您免受错误的影响——如果您收到错误的类型,您所能做的就是失败。

默认为使用type hints 静态验证类型:

class Calc: 
    def __init__(self, n1: float, n2: float): 
        self.n1 = n1 
        self.n2 = n2 

    def sum(self):
        return self.n1 + self.n2

这允许 IDE 和类型检查器,例如mypy,静态验证类型正确性。它没有运行时开销,并且可以作为持续集成和类似的一部分进行检查。

对于不能接受损坏状态的关键部分,使用assertions 来验证类型。

class Calc: 
    def __init__(self, n1: float, n2: float):
        assert isinstance(n1, float)
        assert isinstance(n2, float)
        self.n1 = n1 
        self.n2 = n2 

    def sum(self):
        return self.n1 + self.n2

断言确实有运行时开销,但一旦(类型)正确性得到验证,它们就可以完全关闭。

【讨论】:

    【解决方案3】:

    在启动之前验证值

    class Calc: 
        def validate(self,n1,n2):
            if not isinstance(n1, float) or not isinstance(n2, float):
                return False
            return True
    
        def __init__(self, n1, n2): 
            if self.validate(n1,n2):
                self.n1 = n1 
                self.n2 = n2 
    
        def sum(self):
            return self.n1 + self.n2
    

    【讨论】:

    • 感谢您的回答 - 我可能会使用您的 if not isinstance 逻辑和 @balderman 提供的 properties 信息
    • 返回TrueFalse 不是一个好主意,它将检查数据有效性的负担放在调用它的每段代码上,这将不得不检查返回值。引发异常会好得多。
    • 在构造函数中默默地不做任何事情可能不是一个好主意,这肯定会引发异常。
    猜你喜欢
    • 1970-01-01
    • 2016-09-05
    • 2021-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-11
    • 1970-01-01
    相关资源
    最近更新 更多