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