Python字典使用点表示法和链接

3
理想情况下,我想要实现的是一个类,它可以扩展(或者非常类似于)Python中的dict,并具有以下附加功能:
  • 可进行设置和获取值的点表示法
  • dict一样具有键-值能力(例如setitem,getitem)
  • 可以链接点表示法操作
如果我有这样的东西:example = DotDict(),我就可以对其执行以下操作:example.configuration.first= 'first',它将在example下实例化适当的DotDict实例。关键的限制是如果操作不是赋值,则应像dict一样引发一个KeyError
以下是我最初组装的代码:
class DotDict(dict):
    def __getattr__(self, key):
        """ Make attempts to lookup by nonexistent attributes also attempt key lookups. """
        import traceback
        import re
        s= ''.join(traceback.format_stack(sys._getframe(1),1))
        if re.match(r'  File.*\n.*[a-zA-Z]+\w*\.[a-zA-Z]+[a-zA-Z0-9_. ]*\s*=\s*[a-zA-Z0-9_.\'"]+',s):
            self[key] = DotDict()
            return self[key]

        return self[key]

    def __setattr__(self, key, value):
        if isinstance(value,dict):
            self[key] = DotDict(value)
        self[key] = value

除了一些常见的边缘情况外,它可以正常工作。我必须说,我非常讨厌这种方法,肯定有更好的方法。查看堆栈并在最后一行上运行正则表达式并不是实现此目标的好方法。
关键问题是,Python将代码行从左到右解释,因此当它到达类似于 a.b.c = 3 的语句时,它的第一个操作是 getattr(a, b),而不是 setattr,因此我无法轻松确定堆栈操作中的最后一个操作是否为赋值。
我想知道的是是否有一种好的方法来确定操作堆栈中的最后一个操作,或者至少它是否为setattr。
编辑:
这是我根据user1320237的建议提出的解决方案。
class DotDict(dict):
    def __getattr__(self, key):
        """ Make attempts to lookup by nonexistent attributes also attempt key lookups. """
        if self.has_key(key):
            return self[key]
        import sys
        import dis
        frame = sys._getframe(1)
        if '\x00%c' % dis.opmap['STORE_ATTR'] in frame.f_code.co_code:
            self[key] = DotDict()
            return self[key]

        raise AttributeError('Problem here')

    def __setattr__(self, key, value):
        if isinstance(value,dict):
            self[key] = DotDict(value)
        self[key] = value

实际实现中还有一点小细节,但它的功能非常出色。它的工作原理是检查栈中的最后一帧并检查字节码是否包含 STORE_ATTR 操作,这意味着正在执行的操作属于 a.b.this.doesnt.exist.yet = 'something' 这种类型。我很好奇这是否可以在 CPython 之外的其他解释器上完成。


请抛出 AttributeError 而不是 KeyError,否则您的代码将无法与 hasattr 等一起使用。 - Antti Haapala -- Слава Україні
5个回答

3
你可能需要覆盖 getattribute 来处理这些边缘情况,然后使用它。
object.__getattribute__

请查看模块dis。但是,您写的比反汇编更好。
>>> import dis
>>> def g():
    a.b.c = 4


>>> dis.dis(g)
  2           0 LOAD_CONST               1 (4)
              3 LOAD_GLOBAL              0 (a)
              6 LOAD_ATTR                1 (b)
              9 STORE_ATTR               2 (c)
             12 LOAD_CONST               0 (None)
             15 RETURN_VALUE        

谢谢,看起来很不错,我会稍微试试,同时给你支票! - lukecampbell
我无法形容我已经从中获得了多少帮助,谢谢!请查看上面的基本实现。 - lukecampbell

2
如果有人在2022年重新访问这个问题,这是我发现在Python 3中可以工作的版本 - 至少我能够在运行Python 3.10的Mac机器上测试这个版本。
请注意,原始问题中的代码在3.x中需要进行轻微修改,因为似乎dict.has_key现在已经被弃用,还有其他一些原因。
import sys
import dis


_STORE_ATTR_OP = dis.opmap['STORE_ATTR']


class DotDict(dict):

    def __getattr__(self, key, __get=dict.__getitem__):
        """ Make attempts to lookup by nonexistent attributes also attempt key lookups. """
        if key in self:
            return __get(self, key)

        frame = sys._getframe(1)

        if _STORE_ATTR_OP in frame.f_code.co_code:
            self[key] = res = DotDict()
            return res

        raise AttributeError('Problem here')

    def __setattr__(self, key, value):
        self[key] = DotDict(value) if isinstance(value, dict) else value

NB:如果有人感兴趣,我还在 PyPI 上发布了一个库 {{link1:dotwiz}},它提供了自己的版本 DotDict

这种方法的主要动力是性能,因此__setattr__,特别是__getitem__或访问时间应该非常快——实际上就像普通的dict一样快。

我还计划通过__setitem__来支持“链接”方法,就像这个问题所启发的那样,例如:

obj['a.b.c.d'] = 1

应该可以正常工作,并且基本上执行相当于:
obj = ✫(a=✫(b=✫(c=✫(d=1))))

也许有意义添加一个“回退”方法set,以便在我们实际上不需要这种行为的情况下使用。因此,在上面的例子中,
obj.set('a.b.c', 2)

应该导致obj的以下值:
✫(a.b.c=2)

1
根据user1320237的回答,这是我得到的。它似乎可以做到你想要的。
class A(object):
    def __getattribute__(self, attr):
        try:
            return super(A, self).__getattribute__(attr)
        except AttributeError:
            self.__setattr__(attr, A())
            return super(A, self).__getattribute__(attr)

1

-2

类 DotDict(字典):

"""Dot natation for dict"""

def __init__(self, theDict):
    super(MyDict, self).__init__()
    for key, item in theDict.items():
        if isinstance(item, dict):
            item = MyDict(item)
        self.__dict__[key] = item

def __getattr__(self, key):
    return self.__dict__[key]

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

请勿这样做:self.__dict__[key] = item - juanpa.arrivillaga

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接