【问题标题】:How to access a field of a namedtuple using a variable for the field name?如何使用字段名称的变量访问命名元组的字段?
【发布时间】:2017-11-21 22:19:25
【问题描述】:

我可以按如下方式访问命名元组的元素(*):

from collections import namedtuple
Car = namedtuple('Car', 'color mileage')
my_car = Car('red', 100)
print my_car.color

但是如何使用变量来指定我要访问的字段的名称?例如

field = 'color'
my_car[field] # doesn't work
my_car.field # doesn't work

我的实际用例是我使用for row in data.itertuples() 遍历熊猫数据框。我正在对特定列中的值进行操作,并且我希望能够通过名称指定要使用的列作为包含此循环的方法的参数。

(*)example taken from here。我正在使用 Python 2.7。

【问题讨论】:

  • getattr(my_car, field)my_car._asdict()[field].
  • 也可以试试mycar[field],但是你可以使用for row in data进行迭代。

标签: python namedtuple


【解决方案1】:

您可以使用getattr

getattr(my_car, field)

【讨论】:

    【解决方案2】:

    “getattr”答案有效,但还有另一种稍快的选项。

    idx = {name: i for i, name in enumerate(list(df), start=1)}
    for row in df.itertuples(name=None):
       example_value = row[idx['product_price']]
    

    说明

    制作一个将列名映射到行位置的字典。用“name=None”调用“itertuples”。然后使用 使用字典中的列名获得的索引。

    1. 制作字典以查找索引。

    idx = {name: i for i, name in enumerate(list(df), start=1)}

    1. 使用字典在行元组中按名称访问所需的值
    for row in df.itertuples(name=None):
       example_value = row[idx['product_price']]
    

    注意:如果您使用 index=False 调用 itertuples,请在 enumerate 中使用 start=0

    这是一个工作示例,显示了这两种方法以及两种方法的时间安排。

    import numpy as np
    import pandas as pd
    import timeit
    
    data_length = 3 * 10**5
    fake_data = {
        "id_code": list(range(data_length)),
        "letter_code": np.random.choice(list('abcdefgz'), size=data_length),
        "pine_cones": np.random.randint(low=1, high=100, size=data_length),
        "area": np.random.randint(low=1, high=100, size=data_length),
        "temperature": np.random.randint(low=1, high=100, size=data_length),
        "elevation": np.random.randint(low=1, high=100, size=data_length),
    }
    df = pd.DataFrame(fake_data)
    
    
    def iter_with_idx():
        result_data = []
        
        idx = {name: i for i, name in enumerate(list(df), start=1)}
        
        for row in df.itertuples(name=None):
            
            row_calc = row[idx['pine_cones']] / row[idx['area']]
            result_data.append(row_calc)
            
        return result_data
    
          
    def iter_with_getaatr():
        
        result_data = []
        for row in df.itertuples():
            row_calc = getattr(row, 'pine_cones') / getattr(row, 'area')
            result_data.append(row_calc)
            
        return result_data
        
    
    dict_idx_method = timeit.timeit(iter_with_idx, number=100)
    get_attr_method = timeit.timeit(iter_with_getaatr, number=100)
    
    print(f'Dictionary index Method {dict_idx_method:0.4f} seconds')
    print(f'Get attribute method {get_attr_method:0.4f} seconds')
    

    结果:

    Dictionary index Method 49.1814 seconds
    Get attribute method 80.1912 seconds
    

    我认为差异是由于创建元组与命名元组的开销较低,并且通过索引而不是 getattr 访问它的开销也较低,但这两者都只是猜测。如果有人知道更好,请发表评论。

    我还没有研究过列数与行数对计时结果的影响。

    【讨论】:

      【解决方案3】:

      由于python 3.6 版可以继承自typing.NamedTuple

      import typing as tp
      
      
      class HistoryItem(tp.NamedTuple):
          inp: str
          tsb: float
          rtn: int
          frequency: int = None
      
          def __getitem__(self, item):
              if isinstance(item, int):
                  item = self._fields[item]
              return getattr(self, item)
      
          def get(self, item, default=None):
              try:
                  return self[item]
              except (KeyError, AttributeError, IndexError):
                  return default
      
      
      item = HistoryItem("inp", 10, 10, 10)
      
      print(item[0])  # 'inp'
      print(item["inp"])  # 'inp'
      

      【讨论】:

      • 这需要被认为是现代答案。
      • Python 3.6 不需要这个,你可以使用collections.namedtuple 类工厂来实现同样的事情。
      • 另外,tp.NamedTuple.__getitem__ 会给你一个属性错误。 tp.NamedTuple 不是真正的类……它有点丑,实际上,它只是用作元类魔法的工具,最终调用 collections.namedtuple 以返回从 tuple 派生的类。
      • @juanpa.arrivillaga 感谢您的指出。我已经修好了。
      【解决方案4】:

      访问它们的另一种方式是:

      field_idx = my_car._fields.index(field)
      my_car[field_idx]
      

      提取字段的索引,然后用它来索引命名元组。

      【讨论】:

        【解决方案5】:

        使用以下代码

        for i,x in enumerate(my_car._fields):
            print(x, my_car[i])
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多