tho*_*ate 40 python variables class
如何将类成员变量限制为Python中的特定类型?
更长的版本:
我有一个类有几个成员变量,这些变量在类外部设置.由于它们的使用方式,它们必须是特定类型,int或list.如果这是C++,我只是将它们设为私有,并在'set'函数中进行类型检查.鉴于这是不可能的,有没有办法限制变量的类型,以便在运行时如果为它们分配了错误类型的值,则会发生错误/异常?或者我是否需要在使用它们的每个函数中检查它们的类型?
谢谢.
jsb*_*eno 44
您可以像使用其他答案一样使用属性 - 因此,如果您想约束单个属性,比如说"bar",并将其约束为整数,您可以编写如下代码:
class Foo(object):
def _get_bar(self):
return self.__bar
def _set_bar(self, value):
if not isinstance(value, int):
raise TypeError("bar must be set to an integer")
self.__bar = value
bar = property(_get_bar, _set_bar)
Run Code Online (Sandbox Code Playgroud)
这有效:
>>> f = Foo()
>>> f.bar = 3
>>> f.bar
3
>>> f.bar = "three"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in _set_bar
TypeError: bar must be set to an integer
>>>
Run Code Online (Sandbox Code Playgroud)
(还有一种新的编写属性的方法,使用内置的"属性"作为getter方法的装饰器 - 但我更喜欢旧的方式,就像我上面提到的那样).
当然,如果你的类上有很多属性,并希望以这种方式保护所有属性,它就会开始变得冗长.没有什么可担心的 - Python的内省能力允许人们创建一个类装饰器,可以用最少的线自动化它.
def getter_setter_gen(name, type_):
def getter(self):
return getattr(self, "__" + name)
def setter(self, value):
if not isinstance(value, type_):
raise TypeError("%s attribute must be set to an instance of %s" % (name, type_))
setattr(self, "__" + name, value)
return property(getter, setter)
def auto_attr_check(cls):
new_dct = {}
for key, value in cls.__dict__.items():
if isinstance(value, type):
value = getter_setter_gen(key, value)
new_dct[key] = value
# Creates a new class, using the modified dictionary as the class dict:
return type(cls)(cls.__name__, cls.__bases__, new_dct)
Run Code Online (Sandbox Code Playgroud)
您只需将其auto_attr_check用作类装饰器,并将类主体中所需的属性声明为属性需要约束的类型:
...
... @auto_attr_check
... class Foo(object):
... bar = int
... baz = str
... bam = float
...
>>> f = Foo()
>>> f.bar = 5; f.baz = "hello"; f.bam = 5.0
>>> f.bar = "hello"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in setter
TypeError: bar attribute must be set to an instance of <type 'int'>
>>> f.baz = 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in setter
TypeError: baz attribute must be set to an instance of <type 'str'>
>>> f.bam = 3 + 2j
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in setter
TypeError: bam attribute must be set to an instance of <type 'float'>
>>>
Run Code Online (Sandbox Code Playgroud)
Mad*_*ist 14
总的来说,由于@yak在评论中提到的原因,这不是一个好主意.您基本上阻止用户提供具有正确属性/行为但不在您硬编码的继承树中的有效参数.
除了免责声明之外,还有一些选项适用于您要做的事情.主要问题是Python中没有私有属性.因此,如果您只有一个普通的旧对象引用,self._a即使您提供了一个对其进行类型检查的setter,也无法保证用户不会直接设置它.以下选项演示了如何真正实施类型检查.
此方法仅适用于您执行此操作的(非常)少量属性.__setattr__当您使用点表示法分配常规属性时,该方法将被调用.例如,
class A:
def __init__(self, a0):
self.a = a0
Run Code Online (Sandbox Code Playgroud)
如果我们现在这样做A().a = 32,它会A().__setattr__('a', 32) 在引擎盖下打电话.事实上,self.a = a0在__init__使用self.__setattr__中也是如此.您可以使用它来强制执行类型检查:
class A:
def __init__(self, a0):
self.a = a0
def __setattr__(self, name, value):
if name == 'a' and not isinstance(value, int):
raise TypeError('A.a must be an int')
super().__setattr__(name, value)
Run Code Online (Sandbox Code Playgroud)
此方法的缺点是您必须为if name == ...要检查的每种类型分别(或if name in ...检查给定类型的多个名称).优点是,这是使用户几乎不可能绕过类型检查的最直接的方法.
属性是用描述符对象替换普通属性的对象(通常使用装饰器).描述符可以具有定制基础属性访问方式的方法__get__和__set__方法.这有点像将相应的if分支__setattr__放入并将其放入仅为该属性运行的方法中.这是一个例子:
class A:
def __init__(self, a0):
self.a = a0
@property
def a(self):
return self._a
@a.setter
def a(self, value):
if not isinstance(value, int):
raise TypeError('A.a must be an int')
self._a = value
Run Code Online (Sandbox Code Playgroud)
在@ jsbueno的回答中可以找到一种稍微不同的做同样事情的方法.
虽然这种方式使用属性很漂亮并且主要解决了这个问题,但它确实存在一些问题.第一个是你有一个"私人" _a属性,用户可以直接修改,绕过你的类型检查.这与使用普通的getter和setter几乎相同,除了现在a可以作为"正确"属性访问,该属性重定向到幕后的setter,使得用户不太可能会搞乱_a.第二个问题是你有一个多余的getter来使属性工作为读写.这些问题是这个问题的主题.
这个解决方案可能是整体上最强大的.在上述问题的公认答案中建议.基本上,不是使用具有一堆你无法摆脱的装饰和便利的属性,而是创建自己的描述符(和装饰器)并将其用于需要进行类型检查的任何属性:
class SetterProperty:
def __init__(self, func, doc=None):
self.func = func
self.__doc__ = doc if doc is not None else func.__doc__
def __set__(self, obj, value):
return self.func(obj, value)
class A:
def __init__(self, a0):
self.a = a0
@SetterProperty
def a(self, value):
if not isinstance(value, int):
raise TypeError('A.a must be an int')
self.__dict__['a'] = value
Run Code Online (Sandbox Code Playgroud)
setter将实际值直接存入__dict__实例中,以避免无限期地重复进入自身.这使得可以在不提供显式getter的情况下获取属性的值.由于描述符a没有该__get__方法,因此搜索将继续,直到找到该属性为止__dict__.这可以确保所有集都通过描述符/ setter,同时允许直接访问属性值.
如果您有大量需要像这样的检查的属性,您可以将该行移动self.__dict__['a'] = value到描述符的__set__方法中:
class ValidatedSetterProperty:
def __init__(self, func, name=None, doc=None):
self.func = func
self.__name__ = name if name is not None else func.__name__
self.__doc__ = doc if doc is not None else func.__doc__
def __set__(self, obj, value):
ret = self.func(obj, value)
obj.__dict__[self.__name__] = value
class A:
def __init__(self, a0):
self.a = a0
@ValidatedSetterProperty
def a(self, value):
if not isinstance(value, int):
raise TypeError('A.a must be an int')
Run Code Online (Sandbox Code Playgroud)
更新
Python3.6为您提供了几乎开箱即用的功能:https://docs.python.org/3.6/whatsnew/3.6.html#pep-487-descriptor-protocol-enhancements
对于需要进行类型检查的极少数属性,请__setattr__直接覆盖.对于大量属性,请使用如上所示的setter-only描述符.直接为这种应用程序使用属性会引入比它解决的问题更多的问题.
从Python 3.5开始,您可以使用类型提示来指示类属性应为特定类型。然后,您可以在持续集成过程中包括MyPy之类的内容,以检查是否遵守所有类型合同。
例如,对于以下Python脚本:
class Foo:
x: int
y: int
foo = Foo()
foo.x = "hello"
Run Code Online (Sandbox Code Playgroud)
MyPy将给出以下错误:
6: error: Incompatible types in assignment (expression has type "str", variable has type "int")
Run Code Online (Sandbox Code Playgroud)
如果希望在运行时强制类型,则可以使用force软件包。从自述文件:
>>> import enforce
>>>
>>> @enforce.runtime_validation
... def foo(text: str) -> None:
... print(text)
>>>
>>> foo('Hello World')
Hello World
>>>
>>> foo(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/william/.local/lib/python3.5/site-packages/enforce/decorators.py", line 106, in universal
_args, _kwargs = enforcer.validate_inputs(parameters)
File "/home/william/.local/lib/python3.5/site-packages/enforce/enforcers.py", line 69, in validate_inputs
raise RuntimeTypeError(exception_text)
enforce.exceptions.RuntimeTypeError:
The following runtime type errors were encountered:
Argument 'text' was not of type <class 'str'>. Actual type was <class 'int'>.
Run Code Online (Sandbox Code Playgroud)