【问题标题】:Namedtuple like classNamedtuple 类
【发布时间】:2017-12-05 12:07:33
【问题描述】:

当我需要一个快速的一次性类时,我发现自己经常在我的 python 代码中编写这个类。

class Struct(object):
   def __init__( self, **kwargs ):
      for k in kwargs:
         setattr(self,k,kwargs[k])

基本的想法是这样我就可以快速做这样的事情:

foo = Struct( bar='one', baz=1 )
print foo.bar
foo.baz += 1
foo.novo = 42 # I don't do this as often.

当然,这不能很好地扩展,添加方法也很疯狂,但即便如此,我也有足够的纯数据一次性类,我一直在使用它。

这就是我认为namedtuple 将会成为的样子。但是namedtuple的语法大而笨重。

在标准库中我还没有找到可以做到这一点或更好的东西吗?

这是坏坏的风格吗?还是它有一些隐藏的缺陷?

更新

两个具体的例子来说明为什么我不只使用字典。这两个例子都可以用 dict 来完成,但它显然不是惯用的。

#I know an order preserving dict would be better but they don't exist in 2.6.
closure = Struct(count=0)
def mk_Foo( name, path ):
   closure.count += 1
   return (name, Foo( name, path, closure.count ))

d = dict([
   mk_Foo( 'a', 'abc' ),
   mk_Foo( 'b', 'def' ),
   # 20 or so more
   ] )


@contextmanager
def deleter( path ):
   control = Struct(delete=True,path=path)
   try:      
      yield control
   finally:
      if control.delete:
         shutil.rmtree(path)

with deleter( tempfile.mkdtemp() ) as tmp:
   # do stuff with tmp.path
  
   # most contexts don't modify the delete member
   # but occasionally it's needed
   if keep_tmp_dir:
      tmp.delete = False
  

【问题讨论】:

  • 只使用字典有什么问题? x = {'baz':1} x['baz'] += 1
  • namedtuple 是 tuple,因此您无法更改其内容。
  • @JochenRitzel,您可以致电_replace。例如:p = Point(x=11, y=22) ... p._replace(x=33) ... Point(x=33, y=22)
  • 查看answer 的类似问题。

标签: python


【解决方案1】:

有一个 python 配方(它只是更新实例的字典而不是调用 setattr) Recipe 52308

class Bunch(object):
    def __init__(self, **kwds):
        self.__dict__.update(kwds)

【讨论】:

  • 一个限制是它在使用__slots__ 时不起作用。
【解决方案2】:

从 Python 3.3 起,您可以使用types.SimpleNamespace:

>>> import types
>>> foo = types.SimpleNamespace(bar='one', baz=1)
>>> print(foo.bar)
one
>>> foo.baz += 1
>>> foo.novo = 42

内置类型大致相当于如下代码:

class SimpleNamespace:

    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

    def __repr__(self):
        keys = sorted(self.__dict__)
        items = ("{}={!r}".format(k, self.__dict__[k]) for k in keys)
        return "{}({})".format(type(self).__name__, ", ".join(items))

    def __eq__(self, other):
        return self.__dict__ == other.__dict__

更新

从 Python 3.7 开始,您可以使用 dataclass 模块:

from dataclasses import dataclass, field

@dataclass
class Struct:
    bar: str = field(default='one')
    baz: int = field(default=1)

您可以按如下方式使用它:

foo = Struct( bar='one', baz=1 )
print(foo.bar)
foo.baz += 1
foo.novo = 42

默认情况下,它包含相等测试和漂亮的 repr:

>>> foo == Struct(bar='one', baz=2)
True
>>> foo
Struct(bar='one', baz=2)

【讨论】:

    【解决方案3】:
    class t(dict):
    
       def __init__(self, **kwargs):
          for key, value in kwargs.items():
             dict.__setitem__(self, key, value)
       def __getattr__(self, key):
          return dict.__getitem__(self, key)
       def __setattr__(self, key, value):
          raise StandardError("Cannot set attributes of tuple")      
       def __setitem__(self, key, value):
          raise StandardError("Cannot set attributes of tuple")      
       def __delitem__(self, key):
          raise StandardError("Cannot delete attributes of tuple")
    
    point = t(x=10, y=500, z=-50)
    print point.x        # 10
    print point.y        # 500
    print point['z']     # -50
    print point          # {'z': -50, 'y': 500, 'x': 10}
    point.x = 100        # StandardError: cannot set attributes of tuple
    point.y += 5         # StandardError: cannot set attributes of tuple
    point.z = -1         # StandardError: cannot set attributes of tuple
    
    def hypo(x, y, z):
       return (x**2 + y**2 + z**2)**0.5
    
    print hypo(point)    # TypeError: unsupported operand type(s)
    print hypo(**point)  # 502.593274925   
    
    for k in point.items():
       print k           # ('y', 500)
                         # ('x', 10)
                         # ('z', -50)
    
    for k in point.keys():
       print k           # x
                         # y
                         # z
    
    for k in point.values():
       print k           # 500
                         # 10
                         # -50
    
    print len(point)     # 3
    
    print dict(point)    # {'y': 500, 'x': 10, 'z': -50}
    

    这是我对这个问题的解决方案。优美的语法,不可变的(至少不使用一些讨厌的对象。setattr() 体操),轻量级和漂亮的可打印性。虽然没有什么是你不能用 dict 做的,

    point = t(x=10, y=20, z=30)
    d = point.x ** 2 + point.y ** 2 + point.z ** 2
    

    与

    有很好的对称性
    point = (10, 20, 30)
    d = point[0] ** 2 + point[1] ** 2 + point[2] ** 2
    

    总体来说比

    干净多了
    point = {'x': 10, 'y': 20, 'z': 30}
    d = point['x'] ** 2 + point['y'] ** 2 + point['z'] ** 2
    

    【讨论】:

      【解决方案4】:

      你所拥有的是一个完全合理的原型,但你是对的,它不能扩展。

      如果您喜欢使用它们,但想在以后找到更好的代码,我的建议如下:

      • 每次你这样做,子类结构:

        class Control(Structure): pass

      • 1234563 /p>

      这样的规则只需要你在前面多花一行,如果你以后想要更多的力量,也不会破坏你的代码。

      【讨论】:

        【解决方案5】:

        您可能希望通过George Sakkis 查看Records。它作为“可变命名元组”对我来说效果很好。

        【讨论】:

        • 哇!与简单的 Bunch 配方相比,使用 exec 似乎有点极端。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-11-18
        • 1970-01-01
        • 1970-01-01
        • 2013-10-15
        • 2016-06-14
        • 2016-07-03
        • 2019-08-15
        相关资源
        最近更新 更多