【问题标题】:Python dictionary from an object's fields来自对象字段的 Python 字典
【发布时间】:2010-09-08 20:35:45
【问题描述】:

你知道是否有一个内置函数可以从任意对象构建字典吗?我想做这样的事情:

>>> class Foo:
...     bar = 'hello'
...     baz = 'world'
...
>>> f = Foo()
>>> props(f)
{ 'bar' : 'hello', 'baz' : 'world' }

注意:不应包含方法。只有字段。

【问题讨论】:

    标签: python dictionary attributes object metaprogramming


    【解决方案1】:

    dir 内置函数将为您提供对象的所有属性,包括特殊方法,如 __str____dict__ 和一大堆您可能不想要的其他方法。但是你可以这样做:

    >>> class Foo(object):
    ...     bar = 'hello'
    ...     baz = 'world'
    ...
    >>> f = Foo()
    >>> [name for name in dir(f) if not name.startswith('__')]
    [ 'bar', 'baz' ]
    >>> dict((name, getattr(f, name)) for name in dir(f) if not name.startswith('__')) 
    { 'bar': 'hello', 'baz': 'world' }
    

    因此可以通过像这样定义props 函数来扩展它以仅返回数据属性而不是方法:

    import inspect
    
    def props(obj):
        pr = {}
        for name in dir(obj):
            value = getattr(obj, name)
            if not name.startswith('__') and not inspect.ismethod(value):
                pr[name] = value
        return pr
    

    【讨论】:

    • 此代码包含方法。有没有办法排除方法?我只需要对象的字段。谢谢
    • ismethod 不捕获函数。示例:inspect.ismethod(str.upper)。不过,inspect.isfunction 并没有多大帮助。不知道如何立即处理。
    • 我做了一些调整以粗略地重复并忽略所有错误到这里的深度,谢谢! gist.github.com/thorsummoner/bf0142fd24974a0ced778768a33a3069
    【解决方案2】:

    我已经解决了两个答案的组合:

    dict((key, value) for key, value in f.__dict__.iteritems() 
        if not callable(value) and not key.startswith('__'))
    

    【讨论】:

    • 这也有效,但请注意,它只会为您提供在实例上设置的属性,而不是在类上(如您的示例中的类 Foo)...
    • 所以,jcarrascal,你最好将上面的代码包装在一个像 props() 这样的函数中,然后你可以调用 props(f) 或 props(Foo)。请注意,编写函数几乎总是比编写“内联”代码更好。
    • 很好,顺便说一句,这是针对 python2.7 的,对于 python3 relpace iteritems() 与简单的 items()。
    • 那么staticmethod呢?不是callable
    【解决方案3】:

    请注意,Python 2.7 中的最佳实践是使用 new-style 类(Python 3 不需要),即

    class Foo(object):
       ...
    

    此外,“对象”和“类”之间也有区别。要从任意对象 构建字典,使用__dict__ 就足够了。通常,您将在类级别声明您的方法,在实例级别声明您的属性,所以__dict__ 应该没问题。例如:

    >>> class A(object):
    ...   def __init__(self):
    ...     self.b = 1
    ...     self.c = 2
    ...   def do_nothing(self):
    ...     pass
    ...
    >>> a = A()
    >>> a.__dict__
    {'c': 2, 'b': 1}
    

    更好的方法(由 cmets 中的 robert 建议)是内置的 vars 函数:

    >>> vars(a)
    {'c': 2, 'b': 1}
    

    或者,根据您想要做什么,从dict 继承可能会很好。那么你的类已经是一本字典,如果你愿意,你可以覆盖getattr 和/或setattr 来调用并设置字典。例如:

    class Foo(dict):
        def __init__(self):
            pass
        def __getattr__(self, attr):
            return self[attr]
    
        # etc...
    

    【讨论】:

    • 如果 A 的属性之一具有自定义 getter 会发生什么? (带有@property 装饰器的函数)?它是否仍然出现在__dict____中?它的价值是多少?
    • 如果对象使用插槽(或在 C 模块中定义),__dict__ 将不起作用。
    • 类对象是否有与此方法等效的方法? IE。而不是使用 f=Foo() 然后执行 f.__dict__,而是直接执行 Foo.__dict__?
    • 对不起,我来晚了,但vars(a)不应该这样做吗?对我来说,最好直接调用__dict__
    • 对于第二个例子,最好使用__getattr__ = dict.__getitem__ 来精确复制行为,然后您还需要__setattr__ = dict.__setitem____delattr__ = dict.__delitem__ 以确保完整性。
    【解决方案4】:

    要从任意对象构建字典,使用__dict__就足够了。

    这会丢失对象从其类继承的属性。例如,

    class c(object):
        x = 3
    a = c()
    

    hasattr(a, 'x') 为真,但 'x' 没有出现在 a.__dict__ 中

    【讨论】:

    • 在这种情况下,解决方案是什么?由于vars() 不起作用
    • @should_be_working dir 是这种情况下的解决方案。请参阅其他答案。
    【解决方案5】:

    迟到的答案,但为了完整性和谷歌员工的利益而提供:

    def props(x):
        return dict((key, getattr(x, key)) for key in dir(x) if key not in dir(x.__class__))
    

    这不会显示类中定义的方法,但仍会显示字段,包括分配给 lambda 的字段或以双下划线开头的字段。

    【讨论】:

      【解决方案6】:

      我认为最简单的方法是为类创建一个 getitem 属性。如果需要写入对象,可以创建自定义 setattr 。以下是 getitem 的示例:

      class A(object):
          def __init__(self):
              self.b = 1
              self.c = 2
          def __getitem__(self, item):
              return self.__dict__[item]
      
      # Usage: 
      a = A()
      a.__getitem__('b')  # Outputs 1
      a.__dict__  # Outputs {'c': 2, 'b': 1}
      vars(a)  # Outputs {'c': 2, 'b': 1}
      

      dict 将对象属性生成到字典中,字典对象可用于获取您需要的项目。

      【讨论】:

      • 在这个答案之后仍然不清楚如何从对象中获取字典。不是属性,而是整个字典;)
      【解决方案7】:

      我想我会花一些时间向您展示如何通过 dict(obj) 将对象转换为 dict。

      class A(object):
          d = '4'
          e = '5'
          f = '6'
      
          def __init__(self):
              self.a = '1'
              self.b = '2'
              self.c = '3'
      
          def __iter__(self):
              # first start by grabbing the Class items
              iters = dict((x,y) for x,y in A.__dict__.items() if x[:2] != '__')
      
              # then update the class items with the instance items
              iters.update(self.__dict__)
      
              # now 'yield' through the items
              for x,y in iters.items():
                  yield x,y
      
      a = A()
      print(dict(a)) 
      # prints "{'a': '1', 'c': '3', 'b': '2', 'e': '5', 'd': '4', 'f': '6'}"
      

      这段代码的关键部分是__iter__ 函数。

      正如 cmets 所解释的,我们要做的第一件事是获取 Class 项目并阻止任何以 '__' 开头的内容。

      创建dict 后,您可以使用update dict 函数并传入实例__dict__

      这些将为您提供完整的类+实例成员字典。现在剩下的就是迭代它们并产生回报。

      另外,如果你打算经常使用它,你可以创建一个@iterable 类装饰器。

      def iterable(cls):
          def iterfn(self):
              iters = dict((x,y) for x,y in cls.__dict__.items() if x[:2] != '__')
              iters.update(self.__dict__)
      
              for x,y in iters.items():
                  yield x,y
      
          cls.__iter__ = iterfn
          return cls
      
      @iterable
      class B(object):
          d = 'd'
          e = 'e'
          f = 'f'
      
          def __init__(self):
              self.a = 'a'
              self.b = 'b'
              self.c = 'c'
      
      b = B()
      print(dict(b))
      

      【讨论】:

      • 这也将获取所有方法,但我们只需要类+实例字段。也许dict((x, y) for x, y in KpiRow.__dict__.items() if x[:2] != '__' and not callable(y)) 会解决它?但仍然可能有static 方法:(
      【解决方案8】:

      而不是x.__dict__,实际上使用vars(x) 更符合pythonic。

      【讨论】:

      • 同意。请注意,您还可以通过键入MyClass(**my_dict) 来转换其他方式(dict->class),假设您已经定义了一个带有反映类属性的参数的构造函数。无需访问私有属性或覆盖字典。
      • 你能解释一下为什么它更 Pythonic 吗?
      • 首先,Python 通常避免直接调用 dunder 项,并且几乎总是有方法或函数(或运算符)可以间接访问它。通常,dunder 属性和方法是一个实现细节,使用“包装器”功能可以将两者分开。其次,通过这种方式,您可以覆盖vars 函数并引入其他功能,而无需更改对象本身。
      • 如果你的班级使用__slots__,它仍然会失败。
      • 这是正确的,我一直认为将vars 扩展为一个很好的方向,即为“开槽”类返回等效的__dict__。目前,可以通过添加一个返回 {x: getattr(self, x) for x in self.__slots__}__dict__ 属性来模拟它(但不确定这是否会以任何方式影响性能或行为)。
      【解决方案9】:

      如果你想列出你的部分属性,覆盖__dict__:

      def __dict__(self):
          d = {
          'attr_1' : self.attr_1,
          ...
          }
          return d
      
      # Call __dict__
      d = instance.__dict__()
      

      如果您的instance 获得一些大块数据并且您想将d 推送到 Redis 之类的消息队列,这将很有帮助。

      【讨论】:

      • __dict__ 是一个属性,而不是一个方法,所以这个例子改变了接口(即你需要把它作为一个可调用对象来调用),所以它没有覆盖它。
      【解决方案10】:

      Python 3:

      class DateTimeDecoder(json.JSONDecoder):
      
         def __init__(self, *args, **kargs):
              JSONDecoder.__init__(self, object_hook=self.dict_to_object,
                               *args, **kargs)
      
         def dict_to_object(self, d):
             if '__type__' not in d:
                return d
      
             type = d.pop('__type__')
             try:
                dateobj = datetime(**d)
                return dateobj
             except:
                d['__type__'] = type
                return d
      
      def json_default_format(value):
          try:
              if isinstance(value, datetime):
                  return {
                      '__type__': 'datetime',
                      'year': value.year,
                      'month': value.month,
                      'day': value.day,
                      'hour': value.hour,
                      'minute': value.minute,
                      'second': value.second,
                      'microsecond': value.microsecond,
                  }
              if isinstance(value, decimal.Decimal):
                  return float(value)
              if isinstance(value, Enum):
                  return value.name
              else:
                  return vars(value)
          except Exception as e:
              raise ValueError
      

      现在你可以在你自己的类中使用上面的代码了:

      class Foo():
        def toJSON(self):
              return json.loads(
                  json.dumps(self, sort_keys=True, indent=4, separators=(',', ': '), default=json_default_format), cls=DateTimeDecoder)
      
      
      Foo().toJSON() 
      

      【讨论】:

        【解决方案11】:

        使用__dict__ 的一个缺点是它很浅;它不会将任何子类转换为字典。

        如果您使用的是 Python3.5 或更高版本,则可以使用jsons

        >>> import jsons
        >>> jsons.dump(f)
        {'bar': 'hello', 'baz': 'world'}
        

        【讨论】:

          【解决方案12】:

          vars() 很棒,但不适用于对象的嵌套对象

          将对象的嵌套对象转换为dict:

          def to_dict(self):
              return json.loads(json.dumps(self, default=lambda o: o.__dict__))
          

          【讨论】:

            【解决方案13】:

            one of the comments above 中所述,vars 目前并不通用,因为它不适用于带有__slots__ 而不是普通__dict__ 的对象。此外,一些对象(例如,像 strint 这样的内置函数)既没有 __dict__ 也不 __slots__

            目前,更通用的解决方案可能是:

            def instance_attributes(obj: Any) -> Dict[str, Any]:
                """Get a name-to-value dictionary of instance attributes of an arbitrary object."""
                try:
                    return vars(obj)
                except TypeError:
                    pass
            
                # object doesn't have __dict__, try with __slots__
                try:
                    slots = obj.__slots__
                except AttributeError:
                    # doesn't have __dict__ nor __slots__, probably a builtin like str or int
                    return {}
                # collect all slots attributes (some might not be present)
                attrs = {}
                for name in slots:
                    try:
                        attrs[name] = getattr(obj, name)
                    except AttributeError:
                        continue
                return attrs
            

            例子:

            class Foo:
                class_var = "spam"
            
            
            class Bar:
                class_var = "eggs"
                
                __slots__ = ["a", "b"]
            
            >>> foo = Foo()
            >>> foo.a = 1
            >>> foo.b = 2
            >>> instance_attributes(foo)
            {'a': 1, 'b': 2}
            
            >>> bar = Bar()
            >>> bar.a = 3
            >>> instance_attributes(bar)
            {'a': 3}
            
            >>> instance_attributes("baz") 
            {}
            
            

            咆哮:

            很遗憾,vars 还没有内置它。 Python 中的许多内置函数承诺是解决问题的“最佳”方案,但总会有一些特殊情况没有得到处理……而且无论如何最终都不得不手动编写代码。

            【讨论】:

              【解决方案14】:

              在 2021 年,对于嵌套对象/dicts/json,使用 pydantic BaseModel - 将嵌套 dicts 和嵌套 json 对象转换为 python 对象和 JSON,反之亦然:

              https://pydantic-docs.helpmanual.io/usage/models/

              >>> class Foo(BaseModel):
              ...     count: int
              ...     size: float = None
              ... 
              >>> 
              >>> class Bar(BaseModel):
              ...     apple = 'x'
              ...     banana = 'y'
              ... 
              >>> 
              >>> class Spam(BaseModel):
              ...     foo: Foo
              ...     bars: List[Bar]
              ... 
              >>> 
              >>> m = Spam(foo={'count': 4}, bars=[{'apple': 'x1'}, {'apple': 'x2'}])
              

              要听写的对象

              >>> print(m.dict())
              {'foo': {'count': 4, 'size': None}, 'bars': [{'apple': 'x1', 'banana': 'y'}, {'apple': 'x2', 'banana': 'y'}]}
              

              对象转 JSON

              >>> print(m.json())
              {"foo": {"count": 4, "size": null}, "bars": [{"apple": "x1", "banana": "y"}, {"apple": "x2", "banana": "y"}]}
              

              听写对象

              >>> spam = Spam.parse_obj({'foo': {'count': 4, 'size': None}, 'bars': [{'apple': 'x1', 'banana': 'y'}, {'apple': 'x2', 'banana': 'y2'}]})
              >>> spam
              Spam(foo=Foo(count=4, size=None), bars=[Bar(apple='x1', banana='y'), Bar(apple='x2', banana='y2')])
              

              JSON 到对象

              >>> spam = Spam.parse_raw('{"foo": {"count": 4, "size": null}, "bars": [{"apple": "x1", "banana": "y"}, {"apple": "x2", "banana": "y"}]}')
              >>> spam
              Spam(foo=Foo(count=4, size=None), bars=[Bar(apple='x1', banana='y'), Bar(apple='x2', banana='y')])
              

              【讨论】:

                【解决方案15】:

                试试:

                from pprint import pformat
                a_dict = eval(pformat(an_obj))
                

                【讨论】:

                  【解决方案16】:

                  Python3.x

                  return dict((key, value) for key, value in f.__dict__.items() if not callable(value) and not key.startswith('__'))
                  

                  【讨论】:

                    猜你喜欢
                    • 2016-03-06
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2021-11-05
                    • 1970-01-01
                    • 1970-01-01
                    • 2016-01-31
                    相关资源
                    最近更新 更多