Python OOP,方法/类中参数的类型验证

时间:2018-10-10 13:24:15

标签: python python-3.x oop types

我试图理解Python中的OOP,但遇到了这种“非Python的思维方式”的问题。我想要一个用于类的方法来验证参数的类型,并在参数类型不正确的情况下引发异常(例如ValueError)。我最渴望得到的是:

class Tee(object):
    def __init__(self):
        self.x = 0
    def copy(self, Q : '__main__.Tee'):
        self.x = Q.x
    def __str__(self):
        return str(self.x)

a = Tee()
b = Tee()
print(type(a))  # <class '__main__.Tee'>
print(isinstance(a, Tee))  # True
b.x = 255
a.copy(b)
print(a)        # 255
a.copy('abc')   # Traceback (most recent call last): [...]
                # AttributeError: 'str' object has no attribute 'x'

因此,即使我试图确保我的Q方法中的参数copy的类型属于同一类,解释器也只是通过它并引发一个AttributeError尝试从字符串中获取x成员时。

我知道我可以做这样的事情:

[...]
    def copy(self, Q):
        if isinstance(Q, Tee):
            self.x = Q.x
        else:
            raise ValueError("Trying to copy from a non-Tee object")
[...]
a = Tee()
a.copy('abc')   # Traceback (most recent call last): [...]
                # ValueError: Trying to copy from a non-Tee object

但是,即使我做了一个专用的函数,方法或装饰器,在类周围的任何地方实现它也要进行很多工作。因此,我的问题是:是否有更“ pythonic”的方法

顺便说一下,我正在使用Python 3.6.5。

2 个答案:

答案 0 :(得分:5)

在运行时不强制使用类型注释。期。当前,它们仅由IDE或mypy之类的静态分析器使用,或者由您自己编写的可内省这些注释的任何代码使用。但是,由于Python很大程度上基于duck typing,因此运行时不会也不会实际执行类型。

如果在开发过程中使用静态类型检查器来捕获此类错误,通常这已经足够了。如果要进行实际的运行时检查,可以使用assertions

assert isinstance(Q, Tee), f'Expected instance of Tee, got {type(Q)}'

但是它们也主要用于调试,因为可以关闭断言。要具有强类型断言,您需要明确:

if not isinstance(Q, Tee):
    raise TypeError(f'Expected instance of Tee, got {type(Q)}')

但是,这再次阻止了鸭子的输入,这并不总是可取的。

顺便说一句,您的类型注释应该仅为def copy(self, Q: 'Tee'),不包括'__main__';另请参见https://docs.python.org/3/whatsnew/3.7.html#whatsnew37-pep563

答案 1 :(得分:1)

  

所以,我的问题是:还有其他的“ pythonic”方法吗?

是:明确记录Q对象期望的API(在这种情况下:它应该具有x int属性)并每天调用。

重点是,无论您是否“验证”参数的类型,该错误都会在运行时发生,因此从实际的POV类型检查与否不会有很大的不同-但这会阻止传递“兼容”没有充分的理由反对。

此外,由于Tee.x是公开的,因此可以在代码中的任何位置将其设置为任何值,而 this 实际上更值得关注,因为它可以在完全不相关的情况下中断位置,使该bug更加难以跟踪和解决,因此,如果您真的坚持要防御(根据情况的不同,它可能有意义),那么您应该真正关注的是。

class Tee(object):
    def __init__(self):
        self.x = 0

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        # this will raise if value cannot be 
        # used to build an int
        self._x = int(value)


    def copy(self, Q : '__main__.Tee'):
        # we don't care what `Q` is as long
        # as it has an `x` attribute that can
        # be used for our purpose
        self.x = Q.x

    def __str__(self):
        return str(self.x)

这将1 /防止Tee.x无法使用,并且2 /在传递无效值的确切位置中断,从而使该错误显而易见,并且易于通过检查回溯来解决。

请注意,这里的意思是说typecheking完全没有用,但是(至少在Python中)您只能在对上下文真正有意义的时间和地点使用它。我知道当您购买了“静态类型好,因为它可以防止错误”(在这里,这样做了……)的想法时,这似乎很奇怪,但是实际上类型错误很少见(与逻辑错误相比),并且通常很快斑。静态类型的真相在于,它并不是在帮助开发人员编写更好的代码,而是在帮助编译器优化代码-这是一个有价值的目标,但却是一个完全不同的目标。