【问题标题】:Literate way to index a list where each element has an interpretation?有文字的方法来索引每个元素都有解释的列表?
【发布时间】:2017-12-27 22:01:31
【问题描述】:

Tl;dr 是粗体字。

我正在使用带有布尔“one-hot”图像注释的图像数据集(具体来说是 Celeba)。注释编码面部特征,如bald、male、young。现在我想制作一个自定义的 one-hot 列表(来测试我的 GAN 模型)。我想提供一个识字界面。即,我想要features[male]=True 或features.male=True 之类的东西,而不是指定features[12]=True 知道12 - 从零开始计数 - 对应于male 功能。

假设我的.txt 文件的标题是

Arched_Eyebrows Attractive Bags_Under_Eyes Bald Bangs Chubby Male Wearing_Necktie Young

我想将 Young、Bald 和 Chubby 编码。 预期的输出是

[ 0.  0.  0.  1.  0.  1.  0.  0.  1.]

因为 Bald 是标题的第四个条目,Chubby 是第六个,依此类推。 在不期望用户知道 Bald 是第四个条目等的情况下,最清晰的方法是什么?

我正在寻找一种 Pythonic 方式,而不是一定最快的方式。

理想功能

按重要性的粗略顺序:

  1. 实现我既定目标且已成为 Python 社区标准的方法将优先考虑。
  2. 用户/程序员不需要计入.txt 标头中的属性。这就是我要设计的重点。
  3. 不应期望用户拥有像 aenum 这样的非标准库。
  4. 用户/程序员不需要为属性名称/可用属性引用.txt 标头。一个例子:如果用户想指定性别属性,但不知道是使用male还是female,应该很容易找到。
  5. 用户/程序员应该能够通过文档找到可用的属性(最好由 Sphinx api-doc 生成)。也就是说,第 4 点应该尽可能少读代码。以dir() 进行属性曝光就足以满足这一点。
  6. 程序员应该自然而然地发现索引工具。具体来说,零索引应该优于从一索引中减去。
  7. 在两个完全相同的解决方案之间,性能更好的解决方案会胜出。

示例:

我将比较和对比我立即想到的方式。 所有示例都使用:

import numpy as np
header = ("Arched_Eyebrows Attractive Bags_Under_Eyes "
          "Bald Bangs Chubby Male Wearing_Necktie Young")
NUM_CLASSES = len(header.split())  # 9

1:听写理解

显然我们可以使用字典来完成:

binary_label = np.zeros([NUM_CLASSES])
classes = {head: idx for (idx, head) in enumerate(header.split())}
binary_label[[classes["Young"], classes["Bald"], classes["Chubby"]]] = True
print(binary_label)

值得一提的是,它的代码行数最少,并且是唯一一个不依赖于标准库而不依赖于内置函数的代码。至于底片,它并不完全是自我记录。要查看可用选项,您必须 print(classes.keys()) - 它不会与 dir() 一起公开。这接近于不满足功能 5,因为它要求用户知道 classes 是暴露功能 AFAIK 的字典。

2:枚举:

由于我现在正在学习 C++,所以首先想到的是Enum:

import enum
binary_label = np.zeros([NUM_CLASSES])
Classes = enum.IntEnum("Classes", header)
features = [Classes.Young, Classes.Bald, Classes.Chubby]
zero_idx_feats = [feat-1 for feat in features]
binary_label[zero_idx_feats] = True
print(binary_label)

这给出了点表示法,图像选项显示为dir(Classes)。但是,enum 默认使用单索引(原因是documented)。变通方法让我觉得enum 不是这样做的 Pythonic 方式,并且完全无法满足功能 6。

3:命名元组

这是标准 Python 库中的另一个:

import collections
binary_label = np.zeros([NUM_CLASSES])
clss = collections.namedtuple(
    "Classes", header)._make(range(NUM_CLASSES))
binary_label[[clss.Young, clss.Bald, clss.Chubby]] = True
print(binary_label)

使用namedtuple,我们再次使用dir(clss) 获得点符号和自文档。但是,namedtuple 类比enum 重。我的意思是,namedtuple 具有我不需要的功能。这个解决方案似乎是我的示例中的佼佼者,但我不知道它是否满足功能 1,或者替代方案是否可以通过功能 7 “获胜”。

4:自定义枚举

我真的可以折断我的背部:

binary_label = np.zeros([NUM_CLASSES])
class Classes(enum.IntEnum):
    Arched_Eyebrows = 0
    Attractive = 1
    Bags_Under_Eyes = 2
    Bald = 3
    Bangs = 4
    Chubby = 5
    Male = 6
    Wearing_Necktie = 7
    Young = 8
binary_label[
    [Classes.Young, Classes.Bald, Classes.Chubby]] = True
print(binary_label)

这具有 Ex 的所有优点。 2. 但是,它有明显的缺点。我必须写出所有的特征(真实数据集中有40)只是为了零索引!当然,这是在 C++ (AFAIK) 中创建枚举的方法,但在 Python 中它不应该是必需的。这是功能 6 的轻微故障。

总结

有很多方法可以在 Python 中完成文字零索引。 您能否提供一个代码 sn-p,说明您将如何完成我所追求的目标并告诉我为什么您的方式是正确的?

(编辑:)或者解释一下为什么我的一个例子是适合这项工作的工具?


状态更新:

我还没有准备好接受答案,以防有人想要解决以下反馈/更新,或者出现任何新的解决方案。也许再过24小时?所有的回复都很有帮助,所以到目前为止我都赞成每个人。您可能想查看我用来测试解决方案的repo。 如果我的以下言论(不)准确或不公平,请随时告诉我:

零枚举:

奇怪的是,Sphinx 错误地记录了这一点(文档中只有一个索引),但它确实记录了它!我想这个“问题”不会失败任何理想的功能。

dotdict:

我觉得Map 有点矫枉过正,但dotdict 可以接受。感谢让这个解决方案与dir() 一起工作的两位回答者。但是,它似乎不能与 Sphinx “无缝协作”。

Numpy 记录:

正如所写,此解决方案比其他解决方案花费的时间要长得多。它比 namedtuple 慢 10 倍(在纯 dict 之后最快),比标准 IntEnum 慢 7 倍(在 numpy 记录之后最慢)。这在目前的规模上并不严重,也不是优先事项,但快速的 Google 搜索表明 np.in1d 实际上很慢。让我们坚持

_label = np.zeros([NUM_CLASSES])
_label[[header_rec[key].item() for key in ["Young", "Bald", "Chubby"]]] = True

除非我在链接的仓库中实现了错误。这将执行速度带入了与其他解决方案相比的范围内。再说一次,没有狮身人面像。

namedtuple(和 rassar 的批评)

我不相信您对enum 的批评。在我看来,您认为我处理问题的方法是错误的。可以告诉我这个问题,但我看不出使用namedtuple 与“Enum [which] 将为每个常量提供单独的值”有何根本不同。我误会你了吗?

无论如何,namedtuple 出现在 Sphinx 中(正确编号,因为它的价值)。在“理想功能”列表中,这与零枚举和在零枚举之前的配置文件相同。

接受的理由

我接受了零枚举的答案,因为这个答案给了我namedtuple 的最佳挑战者。 按照我的标准,namedtuple 勉强算是最好的解决方案。但是 salparadise 写的答案让我对这个评估充满信心。感谢所有回答的人。

【问题讨论】:

  • 您给namedtuple 的唯一缺点是它“太重”。您并没有说它缺少您需要的功能,也没有说额外的功能会主动妨碍。所以对我来说,这是显而易见的赢家。我认为没有什么比采用一个众所周知的、易于理解的标准库功能来满足您的所有要求并直接使用它更符合 Python 的了。
  • 在你的例子中,3 是最pythonic - 1 不明确,4 过于明确,通常枚举对于这样的事情不是pythonic,尤其是索引差异。 3 更具可读性,使用众所周知的标准库,并且没有真正的缺点。
  • @JohnY 查看编辑。您的评论将作为我打算提出问题的答案。
  • @rassar 请参阅上面我对 JohnY 的评论。
  • 这似乎是一个不完整/过于主观的问题。在每种情况下,您都会提出不同的,有时甚至是相互矛盾的论点。对于字典示例,您提到对标准库的内置插件的偏好。然后你提到需要使用dir()。对于 namedtuple 你提到太重了。如果你能从程序员(你)和用户的角度清楚地说明程序的目标是什么,即程序的使用方式,为什么用户必须使用dir()而不是classes.keys(),那么会更容易回答。也许还有所需功能的列表?

标签: python python-3.x


【解决方案1】:

工厂函数如何创建一个零索引的IntEnum,因为这是适合您需要的对象,而Enum 提供了构造的灵活性:

from enum import IntEnum

def zero_indexed_enum(name, items):
    # splits on space, so it won't take any iterable. Easy to change depending on need.
    return IntEnum(name, ((item, value) for value, item in enumerate(items.split())))

然后:

In [43]: header = ("Arched_Eyebrows Attractive Bags_Under_Eyes "
    ...:           "Bald Bangs Chubby Male Wearing_Necktie Young")
In [44]: Classes = zero_indexed_enum('Classes', header)

In [45]: list(Classes)
Out[45]:
[<Classes.Arched_Eyebrows: 0>,
 <Classes.Attractive: 1>,
 <Classes.Bags_Under_Eyes: 2>,
 <Classes.Bald: 3>,
 <Classes.Bangs: 4>,
 <Classes.Chubby: 5>,
 <Classes.Male: 6>,
 <Classes.Wearing_Necktie: 7>,
 <Classes.Young: 8>]

【讨论】:

    【解决方案2】:

    您可以使用我喜欢称之为DotMap 的自定义类,或者如这里提到的,这个SO 讨论是Map:

    关于Map:

    • 它具有字典的特性,因为 Map/DotMap 的输入是一个字典。您可以使用features['male'] 访问属性。
    • 此外,您可以使用点访问属性,即features.male,当您使用dir(features) 时,属性将被公开。
    • 它的重量与启用点功能所需的重量一样大。
    • 与namedtuple 不同,您无需预先定义它,您可以随意添加和删除密钥。
    • SO 问题中描述的Map 函数与Python3 不兼容,因为它使用iteritems()。只需将其替换为 items() 即可。

    关于dotdict:

    • dotdict 提供与Map 相同的优点,只是它不会覆盖dir() 方法,因此您将无法获得文档的属性。 @SigmaPiEpsilon 已为此 here 提供了修复。
    • 它使用dict.get 方法而不是dict.__getitem__,因此当您访问不存在的属性时,它将返回None 而不是抛出KeyError。
    • 它不会递归地将dotdict-iness 应用于嵌套字典,因此您将无法使用features.foo.bar。

    这是解决前两个问题的dotdict 的更新版本:

    class dotdict(dict):
        __getattr__ = dict.__getitem__  # __getitem__ instead of get
        __setattr__ = dict.__setitem__
        __delattr__ = dict.__delitem__
        def __dir__(self):              # by @SigmaPiEpsilon for documentation
            return self.keys()
    

    更新

    Map 和 dotdict 的行为与 @SigmaPiEpsilon 指出的不同,因此我为两者添加了单独的描述。

    【讨论】:

    • 定义一个全新的类对于这个问题可能是多余的。
    • @rassar OP 似乎没问题,因为问题中的方法 4 是一个自定义类。而第二个链接,即shorter lighter version,无论如何都非常小。
    • +1 以获得创造性的回应和讨论优势!我同意“更短更轻的版本”可能是合适的。
    • 这些方法本身是不够的,因为这些类都没有覆盖__dir__(),这是dir() 所调用的。在这些实例上使用dir() 只会给出python dict 的属性。据我了解,点访问的主要需求是通过 dir() 的 sphinx 简化文档。请参阅下面的答案。
    【解决方案3】:

    在您的示例中,3 是您问题的最 Pythonic 答案。

    1,正如您所说,甚至没有回答您的问题,因为名称不明确。

    2 使用枚举,尽管它在标准库中不是 Python 的,并且通常不会在 Python 中的这些场景中使用。 (编辑):在这种情况下,您实际上只需要两个不同的常量 - 目标值和其他常量。 Enum 将为每个常量提供单独的值,这不是您程序的目标,并且似乎是解决问题的一种迂回方式。

    如果客户想要添加选项,则 4 是不可维护的,即使这是一项艰苦的工作。

    3 以可读和简洁的方式使用标准库中的知名类。此外,它没有任何缺点,因为它非常明确。如果您不关心性能,那么太“重”也没关系,无论如何,您的输入大小不会引起延迟。

    【讨论】:

    • 我同意您的评估,并感谢您对我的示例提供更多反馈。我很好奇:为什么你说enums 不是 Pythonic?如果枚举不用于枚举列表,您能否为枚举提供一个简短的、很好的用途?我知道这是一个额外的问题,但它会支持您对namedtuple 的论点。
    【解决方案4】:

    如果我理解正确,您的要求可以分为两部分:

    1. 以最 Pythonic 的方式并尽可能减少外部依赖,按名称访问 .txt 中标题元素的位置

    2. 启用对包含标题名称的数据结构的点访问,以便能够调用 dir() 并设置与 Sphinx 的简单接口

    纯 Python 方式(无外部依赖)

    解决问题的最pythonic方法当然是使用字典的方法(字典是python的核心)。通过键搜索字典也比其他方法快得多。唯一的问题是这会阻止点访问。另一个答案提到 Map 和 dotdict 作为替代品。 dotdict 更简单,但它只启用点访问,它在 dir() 的文档方面没有帮助,因为 dir() 调用 __dir__() 方法,在这些情况下不会被覆盖。因此它只会返回 Python dict 的属性,而不是标题名称。见下文:

    >>> class dotdict(dict):
    ...     __getattr__ = dict.get
    ...     __setattr__ = dict.__setitem__
    ...     __delattr__ = dict.__delitem__
    ... 
    >>> somedict = {'a' : 1, 'b': 2, 'c' : 3}                                                                                                          
    >>> somedotdict = dotdict(somedict)
    >>> somedotdict.a
    1
    >>> 'a' in dir(somedotdict)
    False
    

    有两种方法可以解决这个问题。

    选项 1:覆盖 __dir__() 方法,如下所示。但这仅在您在类的实例上调用 dir() 时才有效。要使更改适用于类本身,您必须为该类创建一个元类。见here

    #add this to dotdict
    def __dir__(self):
        return self.keys()
    
    >>> somedotdictdir = dotdictdir(somedict)
    >>> somedotdictdir.a
    1
    >>> dir(somedotdictdir)
    ['a', 'b', 'c']
    

    选项 2:第二个选项更接近于用户定义的具有属性的对象,是更新创建对象的__dict__ 属性。这是Map 也使用的。普通的 python dict 没有这个属性。如果你添加它,那么你可以调用dir() 来获取属性/键以及python dict 的所有其他方法/属性。如果您只想要存储的属性和值,您可以使用vars(somedotdictdir),这对于文档也很有用。

    class dotdictdir(dict):
    
        def __init__(self, *args, **kwargs):
            dict.__init__(self, *args, **kwargs)
            self.__dict__.update({k : v for k,v in self.items()})
        def __setitem__(self, key, value):
            dict.__setitem__(self, key, value)
            self.__dict__.update({key : value})
        __getattr__ = dict.get #replace with dict.__getitem__ if want raise error on missing key access
        __setattr__ = __setitem__
        __delattr__ = dict.__delitem__
    
    >>> somedotdictdir = dotdictdir(somedict)
    >>> somedotdictdir
    {'a': 3, 'c': 6, 'b': 4}
    >>> vars(somedotdictdir)
    {'a': 3, 'c': 6, 'b': 4}
    >>> 'a' in dir(somedotdictdir)
    True
    

    麻木的方式

    另一个选择是使用允许点访问的 numpy record 数组。我在您的代码中注意到您已经在使用 numpy。在这种情况下,__dir__() 也必须被覆盖以获取属性。对于具有大量其他数值的数据,这可能会导致更快的操作(未经测试)。

    >>> headers = "Arched_Eyebrows Attractive Bags_Under_Eyes Bald Bangs Chubby Male Wearing_Necktie Young".split()
    >>> header_rec = np.array([tuple(range(len(headers)))], dtype = zip(headers, [int]*len(headers)))
    >>> header_rec.dtype.names                                                                                                                           
    ('Arched_Eyebrows', 'Attractive', 'Bags_Under_Eyes', 'Bald', 'Bangs', 'Chubby', 'Male', 'Wearing_Necktie', 'Young')
    >>> np.in1d(header_rec.item(), [header_rec[key].item() for key in ["Young", "Bald", "Chubby"]]).astype(int)
    array([0, 0, 0, 1, 0, 1, 0, 0, 1])
    

    在 Python 3 中,您将需要使用 dtype=list(zip(headers, [int]*len(headers))),因为 zip 已成为它自己的对象。

    【讨论】:

    • 我希望你不介意,我使用你的建议更新了我的答案以确保完整性。感谢您指出。
    • 在您的 numpy 解决方案中,Python 3 需要 list(zip(...))。我为你做了修改。
    • @DylanF 我添加了第二种方法来启用通过dir() 和vars() 访问键/属性。
    猜你喜欢
    • 2018-09-17
    • 2021-06-11
    • 2020-05-22
    • 2016-11-02
    • 2010-11-08
    • 2022-10-14
    • 2021-01-31
    • 1970-01-01
    • 2019-02-17
    相关资源
    最近更新 更多