Accessing dict keys like an attribute?

前端 未结 27 2104
南笙
南笙 2020-11-22 04:22

I find it more convenient to access dict keys as obj.foo instead of obj[\'foo\'], so I wrote this snippet:

class AttributeDict(dict         


        
相关标签:
27条回答
  • 2020-11-22 05:00

    Just to add some variety to the answer, sci-kit learn has this implemented as a Bunch:

    class Bunch(dict):                                                              
        """ Scikit Learn's container object                                         
    
        Dictionary-like object that exposes its keys as attributes.                 
        >>> b = Bunch(a=1, b=2)                                                     
        >>> b['b']                                                                  
        2                                                                           
        >>> b.b                                                                     
        2                                                                           
        >>> b.c = 6                                                                 
        >>> b['c']                                                                  
        6                                                                           
        """                                                                         
    
        def __init__(self, **kwargs):                                               
            super(Bunch, self).__init__(kwargs)                                     
    
        def __setattr__(self, key, value):                                          
            self[key] = value                                                       
    
        def __dir__(self):                                                          
            return self.keys()                                                      
    
        def __getattr__(self, key):                                                 
            try:                                                                    
                return self[key]                                                    
            except KeyError:                                                        
                raise AttributeError(key)                                           
    
        def __setstate__(self, state):                                              
            pass                       
    

    All you need is to get the setattr and getattr methods - the getattr checks for dict keys and the moves on to checking for actual attributes. The setstaet is a fix for fix for pickling/unpickling "bunches" - if inerested check https://github.com/scikit-learn/scikit-learn/issues/6196

    0 讨论(0)
  • 2020-11-22 05:00

    You can do it using this class I just made. With this class you can use the Map object like another dictionary(including json serialization) or with the dot notation. I hope help you:

    class Map(dict):
        """
        Example:
        m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
        """
        def __init__(self, *args, **kwargs):
            super(Map, self).__init__(*args, **kwargs)
            for arg in args:
                if isinstance(arg, dict):
                    for k, v in arg.iteritems():
                        self[k] = v
    
            if kwargs:
                for k, v in kwargs.iteritems():
                    self[k] = v
    
        def __getattr__(self, attr):
            return self.get(attr)
    
        def __setattr__(self, key, value):
            self.__setitem__(key, value)
    
        def __setitem__(self, key, value):
            super(Map, self).__setitem__(key, value)
            self.__dict__.update({key: value})
    
        def __delattr__(self, item):
            self.__delitem__(item)
    
        def __delitem__(self, key):
            super(Map, self).__delitem__(key)
            del self.__dict__[key]
    

    Usage examples:

    m = Map({'first_name': 'Eduardo'}, last_name='Pool', age=24, sports=['Soccer'])
    # Add new key
    m.new_key = 'Hello world!'
    print m.new_key
    print m['new_key']
    # Update values
    m.new_key = 'Yay!'
    # Or
    m['new_key'] = 'Yay!'
    # Delete key
    del m.new_key
    # Or
    del m['new_key']
    
    0 讨论(0)
  • 2020-11-22 05:00

    What would be the caveats and pitfalls of accessing dict keys in this manner?

    As @Henry suggests, one reason dotted-access may not be used in dicts is that it limits dict key names to python-valid variables, thereby restricting all possible names.

    The following are examples on why dotted-access would not be helpful in general, given a dict, d:

    Validity

    The following attributes would be invalid in Python:

    d.1_foo                           # enumerated names
    d./bar                            # path names
    d.21.7, d.12:30                   # decimals, time
    d.""                              # empty strings
    d.john doe, d.denny's             # spaces, misc punctuation 
    d.3 * x                           # expressions  
    

    Style

    PEP8 conventions would impose a soft constraint on attribute naming:

    A. Reserved keyword (or builtin function) names:

    d.in
    d.False, d.True
    d.max, d.min
    d.sum
    d.id
    

    If a function argument's name clashes with a reserved keyword, it is generally better to append a single trailing underscore ...

    B. The case rule on methods and variable names:

    Variable names follow the same convention as function names.

    d.Firstname
    d.Country
    

    Use the function naming rules: lowercase with words separated by underscores as necessary to improve readability.


    Sometimes these concerns are raised in libraries like pandas, which permits dotted-access of DataFrame columns by name. The default mechanism to resolve naming restrictions is also array-notation - a string within brackets.

    If these constraints do not apply to your use case, there are several options on dotted-access data structures.

    0 讨论(0)
  • 2020-11-22 05:01

    I found myself wondering what the current state of "dict keys as attr" in the python ecosystem. As several commenters have pointed out, this is probably not something you want to roll your own from scratch, as there are several pitfalls and footguns, some of them very subtle. Also, I would not recommend using Namespace as a base class, I've been down that road, it isn't pretty.

    Fortunately, there are several open source packages providing this functionality, ready to pip install! Unfortunately, there are several packages. Here is a synopsis, as of Dec 2019.

    Contenders (most recent commit to master|#commits|#contribs|coverage%):

    • addict (2019-04-28 | 217 | 22 | 100%)
    • munch (2019-12-16 | 160 | 17 | ?%)
    • easydict (2018-10-18 | 51 | 6 | ?%)
    • attrdict (2019-02-01 | 108 | 5 | 100%)
    • prodict (2019-10-01 | 65 | 1 | ?%)

    No longer maintained or under-maintained:

    • treedict (2014-03-28 | 95 | 2 | ?%)
    • bunch (2012-03-12 | 20 | 2 | ?%)
    • NeoBunch

    I currently recommend munch or addict. They have the most commits, contributors, and releases, suggesting a healthy open-source codebase for each. They have the cleanest-looking readme.md, 100% coverage, and good looking set of tests.

    I do not have a dog in this race (for now!), besides having rolled my own dict/attr code and wasted a ton of time because I was not aware of all these options :). I may contribute to addict/munch in the future as I would rather see one solid package than a bunch of fragmented ones. If you like them, contribute! In particular, looks like munch could use a codecov badge and addict could use a python version badge.

    addict pros:

    • recursive initialization (foo.a.b.c = 'bar'), dict-like arguments become addict.Dict

    addict cons:

    • shadows typing.Dict if you from addict import Dict
    • No key checking. Due to allowing recursive init, if you misspell a key, you just create a new attribute, rather than KeyError (thanks AljoSt)

    munch pros:

    • unique naming
    • built-in ser/de functions for JSON and YAML

    munch cons:

    • no recursive init / only can init one attr at a time

    Wherein I Editorialize

    Many moons ago, when I used text editors to write python, on projects with only myself or one other dev, I liked the style of dict-attrs, the ability to insert keys by just declaring foo.bar.spam = eggs. Now I work on teams, and use an IDE for everything, and I have drifted away from these sorts of data structures and dynamic typing in general, in favor of static analysis, functional techniques and type hints. I've started experimenting with this technique, subclassing Pstruct with objects of my own design:

    class  BasePstruct(dict):
        def __getattr__(self, name):
            if name in self.__slots__:
                return self[name]
            return self.__getattribute__(name)
    
        def __setattr__(self, key, value):
            if key in self.__slots__:
                self[key] = value
                return
            if key in type(self).__dict__:
                self[key] = value
                return
            raise AttributeError(
                "type object '{}' has no attribute '{}'".format(type(self).__name__, key))
    
    
    class FooPstruct(BasePstruct):
        __slots__ = ['foo', 'bar']
    
    

    This gives you an object which still behaves like a dict, but also lets you access keys like attributes, in a much more rigid fashion. The advantage here is I (or the hapless consumers of your code) know exactly what fields can and can't exist, and the IDE can autocomplete fields. Also subclassing vanilla dict means json serialization is easy. I think the next evolution in this idea would be a custom protobuf generator which emits these interfaces, and a nice knock-on is you get cross-language data structures and IPC via gRPC for nearly free.

    If you do decide to go with attr-dicts, it's essential to document what fields are expected, for your own (and your teammates') sanity.

    Feel free to edit/update this post to keep it recent!

    0 讨论(0)
  • 2020-11-22 05:01

    You can use dict_to_obj https://pypi.org/project/dict-to-obj/ It does exactly what you asked for

    From dict_to_obj import DictToObj
    a = {
    'foo': True
    }
    b = DictToObj(a)
    b.foo
    True
    
    
    0 讨论(0)
  • 2020-11-22 05:01

    The easiest way is to define a class let's call it Namespace. which uses the object dict.update() on the dict. Then, the dict will be treated as an object.

    class Namespace(object):
        '''
        helps referencing object in a dictionary as dict.key instead of dict['key']
        '''
        def __init__(self, adict):
            self.__dict__.update(adict)
    
    
    
    Person = Namespace({'name': 'ahmed',
                         'age': 30}) #--> added for edge_cls
    
    
    print(Person.name)
    
    0 讨论(0)
提交回复
热议问题