关于字典:如何在python中将类似属性的对象与dict值相关联

How to associate a property-like object with a dict value in python

我正在使用dict对象在类中保存实例值。我喜欢听写,因为我经常想同时设置或获取一组值,并且很容易通过一个列表或听写来实现这一点。例如,要获得几个实例值,我只需传递一个[键列表]并返回这些键的dict:values。据我所知,如果可能的话,使用每个键的单独属性来完成这项工作需要跳过许多代码循环。

不管怎样,我在找这样的东西:

1
2
3
4
5
6
7
class SomeClass(object):
    def __init__(self):
        self._desc = {
            'hair': 'brown'
            'eyes': 'blue'
            'height': 1.55
        }

到这里为止,这一切都很简单,但有些值需要特殊的"设置"条件。我的第一次尝试是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class SomeClass(object):
    def __init__(self):
        self._haircolor = 'blonde'
        self._eyecolor = 'green'
        self._desc = {
            'haircolor': self.haircolor
            'eyecolor': self.eyecolor
            'height': 1.55
        }

    @property
    self.haircolor(self):
        return self._haircolor

    @haircolor.setter
    self.haircolor(self, color):
        if color.lower() in ['blonde', 'brunette', 'brown', 'black', 'auburn', 'red']:
            self._haircolor = color
        else: raise ValueError()

    # Same type of property construct for eyecolor

这不起作用。self.Desc['hairColor']将初始化为"bround",但它与属性构造或self.hairColor属性没有动态关联。我试着把这个换成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class SomeClass(object):
    def __init__(self):
        self._haircolor = 'blonde'
        self._eyecolor = 'green'
        self._desc = {
            'haircolor': property(self.get_haircolor, self.set_haircolor)
            'eyecolor': property(self.get_eyecolor, self.set_eyecolor)
            'height': 1.55
        }

    self.get_haircolor(self):
        return self._haircolor

    self.set_haircolor(self, color):
        if color.lower() in ['blonde', 'brunette', 'brown', 'black', 'auburn', 'red']:
            self._haircolor = color
        else: raise ValueError()

但这以另一种方式失败了。

1
print self._desc['haircolor']

将返回属性对象的str,并且

1
self._desc['haircolor'] = 'brunette'

已销毁属性对象并将其替换为字符串。

我研究了一些类似的问题,像这个问题和这个问题。

不过,这两种情况都是在字典级别操作的。问题是,在定义自定义dict类时,我需要了解很多关于dict的信息,因为我需要预测键名并在setitem(第一个示例)或dict-level属性对象(第二个示例)中切换它们。理想情况下,我希望将setter与值对象本身关联起来,这样dict就不需要知道键可以引用的值有什么特别的地方。


python对象已经在dict中存储了属性,那么在这里使用另一个dict有什么意义呢?只需按照通常的方式存储属性(在有意义的地方使用属性),并在需要时动态构建descdict。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class SomeClass(object):

    HAIRCOLORS = set(['blonde', 'brunette', 'brown', 'black', 'auburn', 'red'])
    EYECOLORS = set(['green', 'blue', 'brown', 'gray'])

    def __init__(self, haircolor="blond", eyecolor="green"):
        self.haircolor = haircolor
        self.eyecolor = eyecolor
        self.height = 1.55 # you didn't mention any validation here

    @property
    def haircolor(self):
        return self._haircolor

    @haircolor.setter
    def haircolor(self, color):
        color = color.lower()
        if color not in self.HAICOLORS:
            raise ValueError("'%s' is not a valid hair color" % color)
        self._haircolor = color

    # same thing for eyecolor

    @property
    def desc(self):
        return dict(
           haircolor=self.haircolor,
           eyecolor=self.eyecolor,
           height=self.height)

如果这是一个重新固化模式,您最终可以编写自己的描述符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ChoiceDescriptor(object):
    def __init__(self, key, choices):
        self.key = key
        self.storage ="_%s" % key
        self.choices = set(choices)

    def __get__(self, instance, cls=None):
        if instance is None:
            return self
        return getattr(instance, self.storage)


    def __set__(self, instance, value):
        value = value.lower()
        if value not in self.choices:
            raise ValueError(
               "'%s' is not a valid value for '%s'" % (value, self.key))
        setattr(instance, self.storage, value)


class SomeClass(object):
    haircolor = ChoiceDescriptor("haircolor",
        ['blonde', 'brunette', 'brown', 'black', 'auburn', 'red'])
    eyecolor = ChoiceDescriptor("eyecolor",
        ['green', 'blue', 'brown', 'gray'])


如果使用self._desc['haircolor']获取/设置其值,则无论该值是否为描述符,它都将调用dict的__getitem__()/__setitem__()方法。

如果您想访问一个描述符,访问它的方法是使用类似于此instance.descrip的方法,因为此时它将调用__getattribute__()方法,如果descrip是一个描述符,它将使用相应的settergetter方法,否则它将把它当作一个正常属性。这是关于描述符的文档

你只想控制在听写中如何调用键?您可以参考这个问题python:如何"完美地"重写dict来重写dict类中的__getitem__()__setitem__()方法。这并不复杂,您只需创建一个这样的类,然后像使用dict类型变量一样使用它。之后,使用新的dict-like类生成self._desc变量,获取或设置self._desc['haircolor']之类的内容将按您的意愿进行。


我有一个类似的案例,并用描述符来管理它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Description(object):
   """ Descriptor which gets/sets value from instances _desc attribute via key."""

    def __init__(self,key):
        self.key = key

    def __get__( self, instance, owner ):
        return instance._desc[self.key]

    def __set__( self, instance, value ):

        if self.key in instance._constraints:
            if value in instance._constraints[self.key]:
                instance._desc[self.key] = value
            else:
                print('Not allowed.')

        else:
            instance._desc[self.key] = value

class Person(object):

    _constraints = {'hair':['brown','blonde']}

    def __init__(self):
        self._desc = {
            'hair': 'brown',
            'eyes': 'blue',
            'height': 1.55,
        }

    hair = Description('hair')
    eyes = Description('eyes')
    height = Description('height')

现在我们可以:

1
2
3
p = Person()

p.hair = 'purple'

将打印"不允许"。因为值"紫色"不符合约束。

1
p.eyes = 1

将"Eyes"设置为1。


如前所述:python对象已经在dict中存储了属性,所以您可以用"desc"来代理它。

1
2
3
4
5
6
7
class SomeClass(object):
    def __init__(self, d):
        self.__dict__ = d

    @property
    def desc(self):
        return self.__dict__

此外,还可以从此类继承,以更新对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class objdict(dict):
    def __getattr__(self, name):
        if name in self:
            return self[name]
        else:
            raise AttributeError("No such attribute:" + name)

    def __setattr__(self, name, value):
        self[name] = value

    def __delattr__(self, name):
        if name in self:
            del self[name]
        else:
            raise AttributeError("No such attribute:" + name)

或者使用命名元组