像属性一样访问字典键?

415

我发现通过 obj.foo 访问字典键比通过 obj['foo'] 更加方便,因此我写了这段代码:

class AttributeDict(dict):
    def __getattr__(self, attr):
        return self[attr]
    def __setattr__(self, attr, value):
        self[attr] = value

然而,我认为Python没有默认提供这个功能一定有原因。以这种方式访问字典键的注意事项和陷阱是什么?


21
如果您在各处访问来自固定大小有限集合的硬编码密钥,最好创建保存它们的对象。 collections.namedtuple 对此非常有用。 - user395760
6
这个问题的类似解决方案可在https://dev59.com/mnA75IYBdhLWcg3w7tt6找到,不过它更进一步。 - keflavich
1
在 https://github.com/bcj/AttrDict 找到了一个模块。我不知道它与这里和相关问题中的解决方案相比如何。 - matt wilkie
我建议将该返回语句放在try块中,并添加一个返回False的异常。这样,像if (not) dict.key:这样的检查就可以起作用了。 - Marki
参见:https://dev59.com/dXE95IYBdhLWcg3wV8e_ - dreftymac
显示剩余2条评论
32个回答

0

编辑:NeoBunch已经弃用,Munch(上文提到的)可以作为替代品。我仍然将解决方案放在这里,对某些人可能有用。

正如Doug所指出的,有一个Bunch包可用于实现obj.key功能。实际上,有一个更新的版本叫做

NeoBunch Munch

它还有一个很棒的功能,可以通过其neobunchify函数将您的字典转换为一个NeoBunch对象。我经常使用Mako模板,并将数据作为NeoBunch对象传递,这使它们更易读,因此,如果您在Python程序中使用普通字典但想在Mako模板中使用点符号表示法,则可以使用它:

from mako.template import Template
from neobunch import neobunchify

mako_template = Template(filename='mako.tmpl', strict_undefined=True)
data = {'tmpl_data': [{'key1': 'value1', 'key2': 'value2'}]}
with open('out.txt', 'w') as out_file:
    out_file.write(mako_template.render(**neobunchify(data)))

而Mako模板可能如下所示:

% for d in tmpl_data:
Column1     Column2
${d.key1}   ${d.key2}
% endfor

NeoBunch的链接是404。 - DeusXMachina

0
首先,我想感谢 @Kimvais 他的天才回答。 我在其中添加了一些内容。
创建一个名为 punktdict.py 的文件。
import sys

dictconfig = sys.modules[__name__]
# Create a reference to the current module's `sys.modules` dictionary,
# which allows accessing and modifying the configuration options.

dictconfig.allow_nested_attribute_creation = True
# Set the configuration option `allow_nested_attribute_creation` to True,
# allowing the creation of nested attributes in PunktDict objects.

dictconfig.allow_nested_key_creation = True
# Set the configuration option `allow_nested_key_creation` to True,
# allowing the creation of nested keys in PunktDict objects.

dictconfig.convert_all_dicts_recursively = True
# Set the configuration option `convert_all_dicts_recursively` to True,
# enabling recursive conversion of all nested dictionaries in PunktDict objects.


def check_if_compatible_dict(di, class_):
    """
    Check if the provided object is a compatible dictionary based on its type and attributes.

    Args:
    - di: The object to check for compatibility.
    - class_: The class representing the compatible dictionary type.

    Returns:
    - True if the object is a compatible dictionary, False otherwise.


    """
    return (isinstance(di, dict) and not isinstance(di, class_)) or (
        (hasattr(di, "items") and hasattr(di, "keys") and hasattr(di, "values"))
    )


def convert_to_dict(di):
    """
    Recursively converts a PunktDict object or a compatible dictionary to a regular dictionary.

    Args:
    - di: The PunktDict object or compatible dictionary to convert.

    Returns:
    - The converted regular dictionary.
    """
    if isinstance(di, dict) or (
        hasattr(di, "items") and hasattr(di, "keys") and hasattr(di, "values")
    ):
        di = {k: convert_to_dict(v) for k, v in di.items()}
    return di


class PunktDict(dict):
    """
    A subclass of dict with additional functionality for handling nested dictionaries.

    Overrides some methods and provides additional methods to handle nested dictionaries.

    Attributes:
    - allow_nested_attribute_creation: A boolean flag indicating whether PunktDict allows creation of nested attributes.
    - allow_nested_key_creation: A boolean flag indicating whether PunktDict allows creation of nested keys.
    - convert_all_dicts_recursively: A boolean flag indicating whether PunktDict recursively converts all nested dictionaries to PunktDict objects.

    Methods:
    - __init__(self, *args, **kwargs): Initializes a new PunktDict object.
    - __setitem__(self, key, value): Sets an item in the PunktDict.
    - __missing__(self, key): Handles missing key access in the PunktDict.
    - update(self, *args, **kwargs): Updates the PunktDict with new data.
    - _check_forbidden_key(self, *args, **kwargs): Checks if a key is forbidden.
    - __getattr__(self, item): Handles attribute access in the PunktDict.


    """

    def __init__(self, *args, **kwargs):
        """
        Initializes a new PunktDict object.

        Args:
        - *args: Variable length argument list.
        - **kwargs: Arbitrary keyword arguments.

        Notes:
        - Converts all nested dictionaries in the PunktDict object to PunktDict objects
          if the `convert_all_dicts_recursively` option is enabled.
        - Maps the PunktDict object itself to its `__dict__` attribute.
        """

        def convert_dict(di):
            """
            Recursively converts a compatible dictionary to a PunktDict object.

            Args:
            - di: The compatible dictionary to convert.

            Returns:
            - The converted PunktDict object.
            """
            if check_if_compatible_dict(di, self.__class__):
                ndi = self.__class__({})
                for k, v in di.items():
                    ndi[k] = convert_dict(v)
                return ndi
            return di

        super().__init__(*args, **kwargs)
        if dictconfig.convert_all_dicts_recursively:
            for key in self:
                if key not in dir(dict):
                    self[key] = convert_dict(self[key])
        self.__dict__ = self

    def __setitem__(self, key, value):
        """
        Sets an item in the PunktDict.

        Args:
        - key: The key to set.
        - value: The value to assign to the key.

        Notes:
        - Converts the value to a PunktDict object if it is a compatible dictionary
          and the `convert_all_dicts_recursively` option is enabled.
        - Raises a ValueError if the key is not allowed.
        """
        if dictconfig.convert_all_dicts_recursively:
            if check_if_compatible_dict(value, self.__class__):
                value = self.__class__(value)
        if key not in dir(dict):
            super().__setitem__(key, value)
        else:
            raise ValueError(f'Key "{key}" not allowed!')

    def __missing__(self, key):
        """
        Handles missing key access in the PunktDict.

        Args:
        - key: The missing key.

        Returns:
        - The created nested PunktDict if `allow_nested_key_creation` is enabled.

        Raises:
        - KeyError: If `allow_nested_key_creation` is disabled and the key is not found.
        """
        if dictconfig.allow_nested_key_creation:
            self[key] = self.__class__({})
            return self[key]
        raise KeyError(f'"{key}" not found')

    def update(self, *args, **kwargs) -> None:
        """
        Updates the PunktDict with new data.

        Args:
        - *args: Variable length argument list.
        - **kwargs: Arbitrary keyword arguments.

        Notes:
        - Converts all nested dictionaries in the new data to PunktDict objects
          if the `convert_all_dicts_recursively` option is enabled.
        """
        if dictconfig.convert_all_dicts_recursively:
            args = [self.__class__(x) for x in args]
        super().update(*args, **kwargs)

    def _check_forbidden_key(self, *args, **kwargs):
        """
        Checks if a key is forbidden.
        Method can be overwritten

        Args:
        - *args: Variable length argument list.
        - **kwargs: Arbitrary keyword arguments.

        Returns:
        - False always.
        """
        return False

    def __getattr__(self, item):
        """
        Handles attribute access in the PunktDict.

        Args:
        - item: The attribute name.

        Returns:
        - The created nested PunktDict if `allow_nested_attribute_creation` is enabled.

        Raises:
        - AttributeError: If `allow_nested_attribute_creation` is disabled or the attribute is forbidden.
        """
        if not self._check_forbidden_key(item):
            if dictconfig.allow_nested_attribute_creation:
                self[item] = self.__class__({})
                return self[item]
        raise AttributeError

像这样使用:

import sys
from punktdict import PunktDict, dictconfig, convert_to_dict

# Configure PunktDict behavior
dictconfig.allow_nested_attribute_creation = True
dictconfig.allow_nested_key_creation = True
dictconfig.convert_all_dicts_recursively = True
PunktDict._check_forbidden_key = (
    lambda self, x: (x.startswith("_ipython") or x == "_repr_mimebundle_")
)

# Create a PunktDict object
d = PunktDict({'popeye': {'mole': {'bole': 'dolle'}}})

# Access and modify nested dictionary values
d['hallo']['baba'] = 11
d.lll.ddd.xxx = 333

# Update PunktDict with new data
d.update({'rrrx': {'xxxxx': 'bbbb'}})
d.rrrx.xxxxxxx = []
d.rrrx.xxxxxxx.append(3)

# Access nested dictionary values using attribute syntax
d['gggg']['xxxxx']['tttt'] = 12
print(d.gggg.xxxxx.tttt)

# Convert PunktDict to a regular dictionary
di = convert_to_dict(d)

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