Python属性中部分的完整性检查

4 投票
3 回答
1225 浏览
提问于 2025-04-17 04:59

我刚开始学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 个回答

4

Python和类型检查是没法搭配在一起的。你得接受这个事实。使用代码的人需要自己确保传入正确的类型。你可以在代码里写明它期待什么,但别去强行检查。

除了列表和元组,还有其他的集合类型。为什么要禁止使用,比如说namedtuple呢?Python是个动态语言,不要通过写类型检查来和它对着干。

可以查查EAFP这个概念。别试图去预见错误;发生错误时再处理就行。


如果不想做类型检查,可以考虑把东西转换成列表:

self.__teams = list(tms)

如果类型不兼容,这一行会抛出异常,从此你就可以放心地处理列表了。 (当然,这并不能阻止别人往列表里放非字符串的东西。)


哦,如果你真的(有正当理由!)需要做类型检查,记得用isinstance函数,而不是用type()来比较。这样可以捕捉到你所需类型的子类。而且,尽量使用最通用的基础类型。检查字符串(无论是Unicode还是其他)的方法是:

if isinstance(my_object, basestring):
    ....

而检查一个像列表一样的集合——不仅仅是狭隘的“列表或元组”——的方法是:

import collections
if isinstance(my_object, collections.Sequence):
    ...

不过这只是个旁注,并不是解决你问题的正确方法。没有正当理由的话,别做类型检查。

5

这个错误并不是因为你没有通过某种类型检查。

除非你把代码写错了(显然你发的代码经过编辑,不能正常运行),这个问题是因为 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
2

使用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对象。

撰写回答