【问题标题】:How to properly subclass dict and override __getitem__ & __setitem__如何正确继承 dict 并覆盖 __getitem__ 和 __setitem__
【发布时间】:2023-03-17 18:17:01
【问题描述】:

我正在调试一些代码,我想知道何时访问特定的字典。嗯,它实际上是一个继承dict 的类,并实现了一些额外的特性。无论如何,我想做的是我自己的子类dict 并添加覆盖__getitem____setitem__ 以产生一些调试输出。现在,我有

class DictWatch(dict):
    def __init__(self, *args):
        dict.__init__(self, args)

    def __getitem__(self, key):
        val = dict.__getitem__(self, key)
        log.info("GET %s['%s'] = %s" % str(dict.get(self, 'name_label')), str(key), str(val)))
        return val

    def __setitem__(self, key, val):
        log.info("SET %s['%s'] = %s" % str(dict.get(self, 'name_label')), str(key), str(val)))
        dict.__setitem__(self, key, val)

'name_label' 是一个键,最终将被设置,我想用它来识别输出。然后我将我正在检测的类更改为子类DictWatch 而不是dict,并将调用更改为超级构造函数。不过,似乎什么都没有发生。我以为我很聪明,但我想知道我是否应该换个方向。

感谢您的帮助!

【问题讨论】:

  • 您是否尝试使用打印而不是日志?另外,您能解释一下如何创建/配置您的日志吗?
  • dict.__init__ 不接受*args
  • 看起来有点像装饰师的好人选。

标签: python dictionary


【解决方案1】:

你正在做的应该绝对有效。我测试了您的课程,除了您的日志语句中缺少左括号外,它工作得很好。我能想到的只有两件事。首先,您的日志语句的输出设置是否正确?您可能需要在脚本顶部放置logging.basicConfig(level=logging.DEBUG)

其次,__getitem____setitem__ 仅在 [] 访问期间被调用。所以请确保您只通过d[key] 访问DictWatch,而不是d.get()d.set()

【讨论】:

  • 其实不是多余的括号,而是(str(dict.get(self, 'name_label')), str(key), str(val)))周围缺少的开头括号
  • 是的。致 OP:为了将来参考,您可以简单地执行 log.info('%s %s %s', a, b, c),而不是 Python 字符串格式化运算符。
  • 日志记录级别最终成为问题。我正在调试别人的代码,我最初是在另一个文件中进行测试,该文件指向不同级别的调试集。谢谢!
【解决方案2】:

这不应该真正改变结果(这应该有效,以获得良好的日志记录阈值): 你的 init 应该是:

def __init__(self,*args,**kwargs) : dict.__init__(self,*args,**kwargs) 

相反,因为如果您使用 DictWatch([(1,2),(2,3)]) 或 DictWatch(a=1,b=2) 调用您的方法,这将失败。

(或者,最好不要为此定义构造函数)

【讨论】:

  • 我只担心dict[key] 的访问形式,所以这不是问题。
【解决方案3】:

子类化dict 时的另一个问题是内置__init__ 不调用update,而内置update 不调用__setitem__。因此,如果您希望所有 setitem 操作都通过您的 __setitem__ 函数,您应该确保它被自己调用:

class DictWatch(dict):
    def __init__(self, *args, **kwargs):
        self.update(*args, **kwargs)

    def __getitem__(self, key):
        val = dict.__getitem__(self, key)
        print('GET', key)
        return val

    def __setitem__(self, key, val):
        print('SET', key, val)
        dict.__setitem__(self, key, val)

    def __repr__(self):
        dictrepr = dict.__repr__(self)
        return '%s(%s)' % (type(self).__name__, dictrepr)
        
    def update(self, *args, **kwargs):
        print('update', args, kwargs)
        for k, v in dict(*args, **kwargs).iteritems():
            self[k] = v

【讨论】:

  • 如果您使用的是 Python 3,您需要更改此示例,以便 printprint() 函数,而 update() 方法使用 items() 而不是 iteritems()
  • 我已经尝试过你的 sol,但它似乎只适用于 仅一级索引(即 dict[key] 而不是 dict[key1][key2] ...) *
  • d[key1] 返回一些东西,也许是字典。第二个关键索引。除非返回的东西也支持监视行为,否则此技术无法工作。
  • @AndrewNaguib:为什么它应该与嵌套数组一起使用?嵌套数组也不适用于普通的 python dict(如果你没有自己实现)
  • @AndrewNaguib: __getitem__ 需要测试 val 并且只能有条件地进行测试 - 即 if isinstance(val, dict): ...
【解决方案4】:

你要做的就是

class BatchCollection(dict):
    def __init__(self, inpt={}):
        super(BatchCollection, self).__init__(inpt)

我个人使用的示例用法

### EXAMPLE
class BatchCollection(dict):
    def __init__(self, inpt={}):
        super(BatchCollection, self).__init__(inpt)

    def __setitem__(self, key, item):
        if (isinstance(key, tuple) and len(key) == 2
                and isinstance(item, collections.Iterable)):
            # self.__dict__[key] = item
            super(BatchCollection, self).__setitem__(key, item)
        else:
            raise Exception(
                "Valid key should be a tuple (database_name, table_name) "
                "and value should be iterable")

注意:仅在python3中测试过

【讨论】:

  • 因为这是 Python 3,我建议只使用 super() 而不是 super(BatchCollection, self)
【解决方案5】:

考虑子类化UserDictUserList。这些类旨在成为子类,而普通的 dictlist 不是,并且包含优化。

【讨论】:

  • 作为参考,Python 3.6 中的 documentation 表示“对此类的需求已被直接从 dict 子类化的能力部分取代;但是,此类更易于使用,因为底层字典可作为属性访问”。
  • @andrew 一个例子可能会有所帮助。
  • @VasanthaGaneshK treyhunner.com/2019/04/…
【解决方案6】:

正如 andrew pate 的回答所建议的那样,子类化 collections.UserDict 而不是 dict 更不容易出错。

下面是一个示例,展示了天真继承 dict 时的问题:

class MyDict(dict):

  def __setitem__(self, key, value):
    super().__setitem__(key, value * 10)


d = MyDict(a=1, b=2)  # Bad! MyDict.__setitem__ not called
d.update(c=3)  # Bad! MyDict.__setitem__ not called
d['d'] = 4  # Good!
print(d)  # {'a': 1, 'b': 2, 'c': 3, 'd': 40}

UserDict 继承自 collections.abc.MutableMapping,因此按预期工作:

class MyDict(collections.UserDict):

  def __setitem__(self, key, value):
    super().__setitem__(key, value * 10)


d = MyDict(a=1, b=2)  # Good: MyDict.__setitem__ correctly called
d.update(c=3)  # Good: MyDict.__setitem__ correctly called
d['d'] = 4  # Good
print(d)  # {'a': 10, 'b': 20, 'c': 30, 'd': 40}

同样,您只需实现__getitem__ 即可自动兼容key in my_dictmy_dict.get、...

注意:UserDict 不是dict 的子类,所以isinstance(UserDict(), dict) 会失败(但isinstance(UserDict(), collections.abc.MutableMapping) 会起作用)

【讨论】:

    猜你喜欢
    • 2021-10-22
    • 1970-01-01
    • 1970-01-01
    • 2020-12-28
    • 2019-06-29
    • 2023-03-14
    • 2012-04-10
    • 2013-03-03
    相关资源
    最近更新 更多