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

Python OOP,方法/类中参数的类型验证,python,python-3.x,oop,types,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)

我试图理解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'
但是,即使我制作了一个专用的函数、方法或装饰器,在类周围的任何地方都要实现,这听起来像是一项艰巨的工作。所以,我的问题是:有没有更“pythonic”的方法


顺便说一句,我使用的是Python 3.6.5。

在运行时不强制执行类型注释。时期它们目前仅由IDE或静态分析器(如)使用,或由您自己编写的任何内省这些注释的代码使用。但是由于Python很大程度上是基于类型的,所以运行时不会也不会强制执行类型

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

但它们也主要用于调试,因为断言可以关闭。要拥有强类型断言,您需要显式:

if not isinstance(Q, Tee):
    raise TypeError(f'Expected instance of Tee, got {type(Q)}')
但同样,这防止了duck类型,而duck类型并不总是可取的

顺便说一句,您的类型注释应该只是
def copy(self,Q:'Tee')
,而不包括
“main”
;另见

所以,我的问题是:有没有一种更“蟒蛇式”的方法

是:清楚地记录
Q
对象所期望的API(在本例中:它应该具有
x
int属性),并在一天内调用它

关键是,无论您是否“验证”参数的类型,错误都会在运行时发生,因此从实际的POV类型检查与否不会有很大的区别,但它会毫无理由地阻止传递“兼容”对象

另外,由于
Tee.x
是公共的,因此它可以在代码中的任何位置设置为任何值,这实际上更令人担忧,因为它可能在完全不相关的位置中断,使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/在传递无效值的确切位置断开,通过检查回溯,使错误变得明显且易于修复


请注意,这里的要点是说,类型检查完全是毫无用处的,但(至少在Python中)您应该只在对上下文真正有意义的时候和地方使用它。我知道当你接受“静态类型是好的,因为它可以防止错误”的想法时,这可能看起来很奇怪(在这里,这样做了…),但实际上类型错误非常罕见(与逻辑错误相比),而且通常很快就被发现。关于静态类型的真相是,它不是帮助开发人员编写更好的代码,而是帮助编译器优化代码——这是一个有价值的目标,但完全不同

脱离主题,但是
copy
最好作为类方法编写,因为它是类的替代构造函数<代码>@classmethod def copy(cls,q):返回三通(q.x)。这需要将
\uuuu init\uuuu
稍微泛化为
def\uuuu init\uuuu(self,x=0):self.x=x
。FWIW,我几乎在所有地方都使用类型提示,但我只是依靠我的IDE(PyCharm)来突出问题。这大大简化了开发,因为在我键入某些类错误时,它会发现这些类错误,但对运行时仍然没有影响。类型提示和告诉您这些提示的适当工具可以很好地防止某些类型的逻辑错误,否则这些错误将在运行时出现。像你这样的人忘记了你处理的是一系列的事情,而不是一件事之类的。这些也是正确的类型提示快速捕获的逻辑错误。我是一个用Python调情的C和C++家伙。Python OOP有一些重要的范例差异,我仍然需要理解。你的回答让我想了很多,我很感激。除了文档之外,你知道我可以从哪些方面提高我在这一新颖的Python OOP范例中的技能吗?@deceze你提到的这些错误会很快被你的测试(我指的是单元测试)发现99次,对不起,但是(恕我直言)您的示例充其量是可疑的-良好的命名约定将立即明确您是在处理集合还是单个对象。我曾经是静态类型的狂热拥护者,当我开始使用Python时,我浪费了很多时间试图在任何地方强制进行适合的类型检查。然后我开始阅读一些严肃的、真实的Python程序代码,发现我所做的不仅没有用,而且适得其反。是的,复数和单数应该是一种赠品。但老实说,许多现实生活中的Python程序对其类型太模糊,让您对它们将接受或期望的值充满猜测。我发现键入提示并让IDE帮助我比(单元)测试它更快。类型提示确实是一个非常有用的文档工具,我不会对此提出异议——我也同意一些LIB可以更好地进行文档化,但我从来没有费心贡献,所以我宁愿关闭f。。向上xD。我的主要观点是,所有关于静态类型是代码正确性和健壮性的阿尔法和欧米茄的洗脑都是胡说八道。
if not isinstance(Q, Tee):
    raise TypeError(f'Expected instance of Tee, got {type(Q)}')
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)