正确使用字典值的getter / setter

时间:2011-10-13 21:57:17

标签: python getter-setter

我对Python很陌生,所以如果这里的任何内容都很糟糕,请指出。

我有一个这个字典的对象:

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

每个特征的值应该是1-10范围内的int,并且不应该允许添加新特征。我想要getter / setter所以我可以确保保留这些约束。以下是我现在制作吸气剂和制定者的方法:

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

我在this website上了解了property()方法。但我没有看到一种简单的方法来利用它来获取/设置字典中的值。有一个更好的方法吗?理想情况下,我希望这个对象的使用是obj.traits['happy'] = 14,它将调用我的setter方法并抛出一个ValueError,因为14超过10。

3 个答案:

答案 0 :(得分:9)

如果您愿意使用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,那么你可以定义一个dict的子类,并使obj.traits成为这个子类的一个实例。 然后子类将覆盖__getitem____setitem__(见下文)。

PS。要继承dict,请继承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]

答案 1 :(得分:2)

首先出现了一些明显的提示:

  1. 检查是否存在某些密钥(而非.keys()使用if key not in self.traits.keys())时,请勿使用if key not in self.traits方法。
  2. 不要显式抛出KeyError异常 - 如果您尝试访问不存在的密钥,则抛出它。
  3. 在上述更改后,您的代码可能如下所示:

    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
    

    聚苯乙烯。我没有彻底检查你的代码的正确性 - 可能还有其他一些问题。

答案 2 :(得分:1)

  

并且不应该允许添加新特征。

执行此操作的自然方法是使用对象而不是字典,并设置类“__slots__

  

每个特征的值应该是1-10范围内的int ...我想要getter / setter所以我可以确保保留这些约束。

执行此操作的自然方法是使用对象而不是字典,以便您可以编写作为类的一部分的getter / setter逻辑,并将它们作为属性包装起来。由于所有这些属性都以相同的方式工作,我们可以进行一些重构来编写生成给定属性名称的属性的代码。

以下可能是过度设计的:

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.