字典值的正确获取/设置方法

17 投票
3 回答
23638 浏览
提问于 2025-04-17 04:17

我刚开始学Python,所以如果这里有什么不对的地方,请告诉我。

我有一个对象,里面有一个字典:

traits = {'happy': 0, 'worker': 0, 'honest': 0}

每个特征的值应该是1到10之间的整数,而且不允许添加新的特征。我想要一些获取和设置的方法,这样我就能确保这些规则被遵守。现在我做的获取和设置方法是这样的:

def getTrait(self, key):
    if key not in self.traits.keys():
        raise KeyError

    return traits[key]

def setTrait(self, key, value):
    if key not in self.traits.keys():
        raise KeyError

    value = int(value)

    if value < 1 or value > 10:
        raise ValueError

    traits[key] = value

我在这个网站上看到关于property()方法的介绍。但我没有找到简单的方法来使用它来获取或设置字典里的值。有没有更好的方法呢?理想情况下,我希望使用这个对象时,可以这样写obj.traits['happy'] = 14,这样就会调用我的设置方法,并抛出一个ValueError,因为14超过了10。

3 个回答

1

而且不应该允许添加新的特性。

实现这个的自然方式是使用对象而不是字典,并设置类的 __slots__

每个特性的值应该是一个范围在1到10之间的整数……我想要获取器和设置器,这样我可以确保这些限制被遵守。

实现这个的自然方式是使用对象而不是字典,这样你就可以编写获取器和设置器的逻辑,这些逻辑是类的一部分,并将它们封装为属性。由于这些属性的工作方式都一样,我们可以进行一些重构,编写代码来根据属性名称生成一个属性。

以下的代码可能有点过于复杂:

def one_to_ten(attr):
  def get(obj): return getattr(obj, attr)
  def set(obj, val):
    val = int(val)
    if not 1 <= val <= 10: raise ValueError
    setattr(obj, attr, val)
  return property(get, set)

def create_traits_class(*traits):
  class Traits(object):
    __slots__ = ['_' + trait for trait in traits]
    for trait in traits: locals()[trait] = one_to_ten('_' + trait)
    def __init__(self, **kwargs):
      for k, v in kwargs.items(): setattr(self, k, v)
      for trait in traits: assert hasattr(self, trait), "Missing trait in init"
    def __repr__(self):
      return 'Traits(%s)' % ', '.join(
        '%s = %s' % (trait, getattr(self, trait)) for trait in traits
      )
  return Traits

example_type = create_traits_class('happy', 'worker', 'honest')
example_instance = example_type(happy=3, worker=8, honest=4)
# and you can set the .traits of some other object to example_instance.
3

我想到了一些明显的建议:

  1. 检查某个键是否存在时,不要使用 .keys() 方法(比如,不要写 if key not in self.traits.keys(),而是直接写 if key not in self.traits)。
  2. 不要手动抛出 KeyError 异常 - 当你尝试访问一个不存在的键时,系统会自动抛出这个异常。

按照以上建议,你的代码可能会变成这样:

def getTrait(self, key):
    return traits[key]

def setTrait(self, key, value):
    if key not in self.traits:
        raise KeyError

    value = int(value)

    if value < 1 or value > 10:
        raise ValueError

    traits[key] = value

另外,我没有仔细检查你的代码是否正确,可能还有其他问题。

14

如果你愿意使用像 obj['happy'] = 14 这样的语法,那么你可以使用 __getitem____setitem__ 这两个方法。

def __getitem__(self, key):
    if key not in self.traits.keys():
        raise KeyError
    ... 
    return traits[key]

def __setitem__(self, key, value):
    if key not in self.traits.keys():
        raise KeyError
    ...
    traits[key] = value

如果你真的想要用 obj.traits['happy'] = 14 这样的方式,那么你可以创建一个字典的子类,并让 obj.traits 成为这个子类的一个实例。这个子类会重写 __getitem____setitem__ 方法(见下文)。

另外,想要创建字典的子类时,需要同时继承 collections.MutableMappingdict。否则,dict.update 方法就不会调用你新定义的 __setitem__ 方法。

import collections
class TraitsDict(collections.MutableMapping,dict):
    def __getitem__(self,key):
        return dict.__getitem__(self,key)
    def __setitem__(self, key, value):
        value = int(value)
        if not 1 <= value <= 10:
            raise ValueError('{v} not in range [1,10]'.format(v=value))
        dict.__setitem__(self,key,value)
    def __delitem__(self, key):
        dict.__delitem__(self,key)
    def __iter__(self):
        return dict.__iter__(self)
    def __len__(self):
        return dict.__len__(self)
    def __contains__(self, x):
        return dict.__contains__(self,x)

class Person(object):
    def __init__(self):
        self.traits=TraitsDict({'happy': 0, 'worker': 0, 'honest': 0})

p=Person()
print(p.traits['happy'])
# 0

p.traits['happy']=1
print(p.traits['happy'])
# 1

p.traits['happy']=14
# ValueError: 14 not in range [1,10]

撰写回答