Python属性中部分的完整性检查
我刚开始学Python,想请教一些问题。我创建了一些类和属性,目的是为了避免传递一些没有意义的参数。
比如说,我有这样一个类:
class match(object):
__teams=(None,None)
def setTeams(self,tms):
if type(tms) != type(list()) and type(tms) != type(tuple()):
raise Exception("Teams must be a list of length 2")
if len(tms) != 2:
raise Exception("Teams must be a list of length 2")
if (type(tms[0])==type(str()) or (type(tms[0])==type(unicode()))) \
and (type(tms[1])==type(str()) or type(tms[1])==type(unicode())):
self.__teams=tms
else:
raise Exception("Both teams must be strings")
return
teams=property(getTeams,setTeams)
如果我写:
match1=match()
match1.teams=(2,4)
我会得到一个异常,这很正常。但是
match1.teams[0]=5
却不会引发异常,反而传递了数字5。请注意,这不是整个类的代码,我只是写下了和我问题相关的部分,所以可以假设代码的行为如我所描述的那样。
我想这可能是因为在Python中所有东西都是通过引用传递的,但我必须小心,不要给我的对象赋值一些没有意义的数据,这样就失去了使用属性的初衷。
那么,除了不使用列表,还有没有其他方法可以解决这个问题,还是我得学会接受这个情况呢?
3 个回答
Python和类型检查是没法搭配在一起的。你得接受这个事实。使用代码的人需要自己确保传入正确的类型。你可以在代码里写明它期待什么,但别去强行检查。
除了列表和元组,还有其他的集合类型。为什么要禁止使用,比如说namedtuple呢?Python是个动态语言,不要通过写类型检查来和它对着干。
可以查查EAFP这个概念。别试图去预见错误;发生错误时再处理就行。
如果不想做类型检查,可以考虑把东西转换成列表:
self.__teams = list(tms)
如果类型不兼容,这一行会抛出异常,从此你就可以放心地处理列表了。 (当然,这并不能阻止别人往列表里放非字符串的东西。)
哦,如果你真的(有正当理由!)需要做类型检查,记得用isinstance函数,而不是用type()
来比较。这样可以捕捉到你所需类型的子类。而且,尽量使用最通用的基础类型。检查字符串(无论是Unicode还是其他)的方法是:
if isinstance(my_object, basestring):
....
而检查一个像列表一样的集合——不仅仅是狭隘的“列表或元组”——的方法是:
import collections
if isinstance(my_object, collections.Sequence):
...
不过这只是个旁注,并不是解决你问题的正确方法。没有正当理由的话,别做类型检查。
这个错误并不是因为你没有通过某种类型检查。
除非你把代码写错了(显然你发的代码经过编辑,不能正常运行),这个问题是因为 match1.teams[0]
调用了你的 getTeams
函数,而不是 setTeams
函数。想要自己验证一下,可以试试这个练习:
class match(object):
__teams=(None,None)
def setTeams(self,tms):
print "in set"
self.__teams = tms
def getTeams(self):
print "in get"
return self.__teams
teams=property(getTeams,setTeams)
当我尝试这个时,我得到了以下结果:
>>> match1 = match()
>>> match1.teams[0]=5
in get
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> match1.teams = ["team1","team2"]
in set
>>> match1.teams[0]=5
in get
使用property
的一个好处是可以进行数据验证——有时候确保获取到特定的数据是非常重要的。
在你的情况下,你需要做以下两件事之一:
- 将你的
teams
数据存储在一个不能被修改的结构中,比如tuple
(元组)或namedtuple
(命名元组);这样当你获取数据时,它就不能被改变。
或者
- 让你的
get
方法返回数据的一个副本,这样任何修改都不会影响到原始数据。
第一种解决方案(不可变类型)看起来是这样的:
class match(object):
__teams=(None,None)
def setTeams(self,tms):
"any sequence type will do, as long as length is two"
if len(tms) != 2:
raise TypeError(
"Teams must be a sequence of length 2"
)
if not isinstance(tms[0], (str, unicode)):
raise TypeError(
"Team names must be str or unicode, not %r" % type(tms[0])
)
if not isinstance(tms[1], (str, unicode)):
raise TypeError(
"Team names must be str or unicode, not %r" % type(tms[0])
)
self.__teams = tuple(tms)
def getTeams(self):
return self.__teams
teams=property(getTeams,setTeams)
当你尝试在获取值后进行赋值时,会发生这样的情况:
Traceback (most recent call last):
File "test.py", line 22, in <module>
match1.teams[0]=5
TypeError: 'tuple' object does not support item assignment
第二种解决方案(返回副本而不是原始数据)看起来是这样的:
class match(object):
__teams=(None,None)
def setTeams(self,tms):
"any sequence type will do, as long as length is two"
if len(tms) != 2:
raise TypeError(
"Teams must be a sequence of length 2"
)
if not isinstance(tms[0], (str, unicode)):
raise TypeError(
"Team names must be str or unicode, not %r" % type(tms[0])
)
if not isinstance(tms[1], (str, unicode)):
raise TypeError(
"Team names must be str or unicode, not %r" % type(tms[0])
)
self.__teams = list(tms)
def getTeams(self):
return list(self.__teams)
teams=property(getTeams,setTeams)
# and the code in action...
match1=match()
match1.teams=('us',u'them')
match1.teams[0]=5
print match1.teams
它的结果如下:
['us', u'them']
正如你所看到的,修改并没有影响到match
对象。