Правильное использование метода получения / установки для значений словаря - PullRequest
11 голосов
/ 14 октября 2011

Я довольно плохо знаком с Python, поэтому, если здесь есть что-то плохое, пожалуйста, укажите на это.

У меня есть объект с этим словарем:

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

Значение для каждой черты должно быть int в диапазоне 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 ]

9 голосов
/ 14 октября 2011

Если вы хотите использовать синтаксис, такой как 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.MutableMapping и dict. В противном случае 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]
2 голосов
/ 14 октября 2011

Сначала приходят на ум некоторые очевидные советы:

  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

Ps. Я не проверил правильность вашего кода полностью - могут быть некоторые другие проблемы.

1 голос
/ 14 октября 2011

и новые черты нельзя допускать.

Естественный способ сделать это - использовать объект вместо словаря и установить класс '__slots__.

Значение для каждой черты должно быть int в диапазоне 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.
...