如何使用变量作为字段名访问命名元组的字段?

113
我可以按照以下方式通过名称访问命名元组中的元素(*):
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() 遍历 pandas dataframe。我正在对特定列中的值进行操作,并且我想能够通过名称将要使用的列作为该循环所在方法的参数指定。

(*) 参考示例。本人使用的是Python 2.7。


15
getattr(my_car, field)my_car._asdict()[field]。这两个表达式都是用来获取对象属性的值,其中 getattr() 是一个内置函数,它接受一个对象和一个字符串作为参数,并返回该对象上具有指定名称的属性的值。而 _asdict() 是用于将命名元组转换为字典的方法,可以使用中括号索引符号来访问其中的字段,从而获得该字段的值。 - Ashwini Chaudhary
还可以尝试使用mycar[field],但这时你可能需要使用for row in data进行迭代。 - Asclepius
5个回答

142
你可以使用getattr
getattr(my_car, field)

11
'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

我认为,元组与命名元组之间的差异在于创建元组时的开销较低,而通过索引访问元组的开销也较低,但这些只是猜测。如果有人知道更好的请留言。
我还未探究列数与行数如何影响计时结果。

5
自 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'

1
这需要被认为是现代的答案。 - Kamuela Franco
Python 3.6并非必需,您可以使用collections.namedtuple类工厂来实现相同的功能。 - juanpa.arrivillaga
此外,tp.NamedTuple.__getitem__ 会导致属性错误。 tp.NamedTuple 不是一个真正的类... 它有点丑陋,实际上它只是作为元类魔法的载体,最终调用 collections.namedtuple 来返回从 tuple 派生的类。 - juanpa.arrivillaga
@juanpa.arrivillaga 謝謝指出,我已經修正了。 - Noortheen Raja

4

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

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

提取字段索引,然后使用它来索引namedtuple。


1
使用以下代码。
for i,x in enumerate(my_car._fields):
    print(x, my_car[i])

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接