【问题标题】:Convert dict attributes in multiple attributes in Python在Python中转换多个属性中的dict属性
【发布时间】:2014-12-08 13:27:33
【问题描述】:

我有一个带有 dict 属性的类,像这样:

class MyClass:

    def __init__(self):
        self.mydict = {'var1': 'value1', 'var2': 'value2', ...}

当我想获取值时,我必须这样做:

cls = MyClass()
print(cls.mydict['var1'])
print(cls.mydict['var2'])

直接在属性中获取值的解决方案是什么:

cls = MyClass()
print(cls.var1)
print(cls.var2)

【问题讨论】:

    标签: python oop dictionary


    【解决方案1】:

    使用setattr,可以动态设置属性:

    >>> class MyClass:
    ...     def __init__(self):
    ...         mydict = {'var1': 'value1', 'var2': 'value2'}
    ...         for key, value in mydict.items():
    ...             setattr(self, key, value)
    ...
    >>> instance = MyClass()
    >>> instance.var1
    'value1'
    >>> instance.var2
    'value2'
    

    【讨论】:

      【解决方案2】:

      您可以向该类添加一个附加函数,该函数将能够解析字典并插入相关属性:

      def assign_variables( self ):
        for key, val in self.mydict.items():
          setattr( self, key, val )
      

      我在这里使用内置的setattr() function 来设置具有动态名称/值的属性:

      这是getattr() 的对应物。参数是一个对象、一个字符串和一个任意值。该字符串可以命名现有属性或新属性。如果对象允许,该函数将值分配给属性。
      例如,setattr(x, 'foobar', 123) 等价于 x.foobar = 123

      您可以在定义 mydict 变量后在构造函数中调用此函数,甚至可以将循环放在构造函数中。

      【讨论】:

      • 在这种情况下,您现在有两个对象引用,用于存储在类对象实例中的mydict 的每个成员。似乎只有一个会更好。可以以非常小的方式更改此方法以避免这种情况(只需通过将mydict 传递给assign_variables 来完全避免self.mydict)。
      • @RickTeachey - 正确。然后人们不得不问为什么字典需要作为属性存在,如果它是预定义的,而不是简单地按原样分配类属性而没有这个转换代码。但随后我们将进入XY problem
      • 我假设它被预先定义为问题呈现的简化。想不出你在实践中真正想要这样做的原因。
      【解决方案3】:

      另一种解决方案是实现__getattr__

      class MyClass(object):
          def __init__(self):
              self.mydict = {'var1': 'value1', 'var2': 'value2', ...}
      
          def __getattr__(self, name):
              try:
                  return self.mydict[name]
              except KeyError:
                  raise AttributeError("object %s has no attribute '%s'" % (self, name))
      

      【讨论】:

      • 像这样在except 语句中引发新错误似乎会弄乱回溯 - 有两条痕迹,这非常令人困惑。没有更好的办法吗?
      • @RickTeachey:不确定你使用的是什么 python shell 或 python 实现,但这段代码在我记得的所有 CPython 版本上都可以正常工作(这意味着从 1.5.something 开始)。
      • 它工作正常,但它会产生消息During handling of the above exception, another exception occurred:。可能是我的经验不足,但它似乎不是很干净,而且似乎不是一种很好的做事方式。 if name in self.mydict: return self.mydict[name]else: raise AttributeError() 似乎更好。甚至:try:(等等)、except KeyError: passraise AttributeError()
      • 必须是 Python 3.x 的东西或你的 shell。捕获一个异常以引发另一个异常(来自 except 子句)是一种非常常见的习惯用法。
      • 我在 Anaconda 中使用 3.x(只是普通的 Windows CMD)。
      【解决方案4】:

      我相信你可以直接更新你的对象的__dict__属性:

      class MyClass:
      
          def __init__(self):
              self.mydict = {'var1': 'value1', 'var2': 'value2', ...}
              self.__dict__.update(self.mydict)
      

      【讨论】:

      • 警告:此解决方案完全绕过 __setattr__() 和所有描述符(属性或其他),并且可能会破坏一些事情。
      • 不是对象的"__setattr__()" 基本上还是直接赋值给__dict__ 吗? (真正的问题,并非相反)。在这个例子中,OP 没有继承任何东西,所以这会比使用 __setattr__() 更安全吗?
      • object.__setattr__() 负责描述符和插槽 - 分配给 __dict__ 仅在没有同名的绑定描述符并且类使用 __dict__(=> 不使用插槽)。您还可以在类层次结构中的某处自定义__setattr__。所以是的,在这种情况下(嗯,就我们可以看到的代码而言)你可以说它是“安全的”——直到有人试图添加描述符等......长话短说:只使用这个解决方案如果您的课程属于无脑数据包类型(iow 带有点语法的字典)。
      • 正如其他人所指出的:这种方法有很多陷阱,当您可以轻松使用 for k, v in self.mydict.items(): self.__setattr__(k,v) 时,真的没有理由使用它。
      【解决方案5】:

      另一种方法是将__getattr____setattr__ 一起覆盖,这样可以避免在类实例对象中对同一属性有两个对象引用(一个OR 到value1myobj.mydict['var1'] 中,另一个OR 到@ 987654325@myobj.__dict__['var1']):

      class MyClass():
          def __init__(self):
              self.__dict__['_mydict'] = {'var1': 'value1', 'var2': 'value2'}
          #Note: using the @property (descriptor) is optional. I think it makes 
          #things more convenient.
          @property
          def mydict(self):
              return self._mydict
          #NOTE: __getattr__ won't get called if att is found in self.__dict__ first! 
          def __getattr__(self,att):
              if att in self.mydict:       # or without using @property: if att in self._mydict:
                  return self.mydict[att]  #                                  return self._mydict[att]
              else:
                  raise AttributeError("'{n}' object has no attribute '{a}'".format(n = type(self).__name__, a = att))
          def __setattr__(self,att,val):
              super().__setattr__(att, val)
              if att in self.mydict:
                  self.mydict[att] = val   # or without using @property: self._mydict[att] = val
                  self.__delattr__(att)    # remove duplicate copy of object reference to att
      

      请注意,这样做意味着您不能向mydict 添加更多键值对,除非您直接调用该属性,例如:

      myobj = MyClass()
      myobj.x = 1
      myobj.mydict #note that result does not contain an entry for 'x'
      myobj.mydict['x'] = 2
      myobj.mydict #now it does
      

      另请注意,gettingdeleting mydict 成员将被任何现有的同名属性覆盖(不仅如此,您还不能删除mydict 成员完全,除非您覆盖__delattr__ 以启用此行为):

      #continuing the above code session
      assert myobj.x == 1 #myobj.x is coming from self.x, not from self._mydict['x']
      myobj.var1 = 'a new value'
      myobj.var1 #results in 'a new value' as expected, but where is it coming from? 
      assert myobj.mydict['var1'] == 'a new value' #Ah: it's coming from self._mydict as expected
      assert myobj.__dict__['var1'] #ERROR, as expected
      del myobj.x
      assert myobj.x == 2 #now myobj.x is coming from self._mydict['x'], self.x having been deleted
      del myobj.var1 #raises AttributeError; attributes in mydict can't be deleted (unless you also override __delattr__
      

      如果你想改变这种行为,你必须覆盖__getattribute__(编辑:正如下面的 bruno desthuilliers 所说,这通常不是一个好主意)。

      【讨论】:

      • 您对__setattr__ 的实现不遵守描述符协议。作为一般规则,覆盖 __setattr____getattribute__ 很少是一个好主意 - 它可能会很棘手并且可能导致性能不佳。
      • 首先:我没有覆盖__getattribute__,而是__getattr__。我只是说你必须这样做才能首先查看mydict 内部。如果这是投反对票的主要原因,我认为这是没有根据的。第二:它怎么不尊重描述符协议?使用[] 调用get 方法,并通过它的返回完成某些操作。这就是描述符的用途,不是吗?如果我错了,请指出我的地方,以便我了解你的意思。无论如何,描述符是附带的,使事情更方便;它仍然是解决 OP 问题的有用方法。
      • 鉴于我缺乏经验,并且相信您作为一个显然更有经验的人的判断,我已经编辑了我的答案以明确说明描述符是可选的。
      • 我不是在谈论 __getattr__ - 它是可选的并且仅作为最后的手段调用(如果已定义),而是关于 __setattr__ (与 __getattribute__ 一起)实现属性解析规则。而且我认为您并没有真正理解描述符的意义-这不是要添加mydict 属性,而是要查找类和mro 以查找绑定描述符(实现__set__ 的描述符)并调用它们before 回退到实例存储。
      • 我认为这是一个很好的例子,说明为什么覆盖 __setattr__ 通常不是一个好主意 - 除非你有一个真正令人信服的理由,也就是说,然后你最好做对(在您上次编辑中仍然不是这种情况 - 如果您有一个与 mydict 中的键同名的绑定描述符,您 不要 想要更新 mydictdel 这个属性)。
      猜你喜欢
      • 1970-01-01
      • 2019-07-16
      • 1970-01-01
      • 2017-08-28
      • 1970-01-01
      • 1970-01-01
      • 2021-06-04
      • 1970-01-01
      • 2020-11-01
      相关资源
      最近更新 更多