有文化的方法来索引每个元素都有解释的列表吗?

时间:2017-12-27 22:01:32

标签: python python-3.x

Tl; dr是大胆的文字。

我正在处理一个带有布尔值的图像数据集" one-hot"图像注释(Celeba具体)。注释编码面部特征,如 bald 男性年轻。现在我想制作一个自定义的热门列表(以测试我的GAN模型)。我想提供识字界面。即,而不是指定features[12]=True知道12 - 从零开始计数 - 对应于男性功能,我希望features[male]=Truefeatures.male=True之类的内容

假设我的.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标头。例如:如果用户想要指定性别属性但不知道是否使用malefemale,则应该很容易找到。
  5. 用户/程序员应该能够通过文档找出可用的属性(理想情况下由Sphinx api-doc生成)。也就是说,第4点应该尽可能少地读取代码。 dir()的属性曝光足以满足这一点。
  6. 程序员应该找到自然的索引工具。具体而言,零索引应优先于从一个索引中减去。
  7. 在两个完全相同的解决方案之间,一个性能更好的解决方案将获胜。
  8. 示例:

    我要比较和对比我想到的方式。 所有示例均使用:

    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:Dict理解

    显然我们可以使用字典来实现这个目标:

    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的所有优点。但是,它有明显的明显缺点。我必须写出所有功能(在真实数据集中有 40 )只是零索引!当然,这是如何在C ++中创建枚举(AFAIK),但它在Python中不是必需的。这是功能6的轻微失败。

    摘要

    在Python中有很多方法可以实现文字零索引。 您是否会提供一个代码片段,说明如何完成我之后的工作并告诉我为什么您的方式正确?

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

    状态更新:

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

    零枚举:

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

    dotdict:

    我觉得Map有点矫枉过正,但dotdict是可以接受的。感谢两位使用dir()解决方案的回答者。然而,它并没有表现出它能够无缝地工作"与狮身人面像。

    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
    

    除非我在链接的回购邮件中实现了错误。这使执行速度进入与其他解决方案相比较的范围。再一次,没有Sphinx。

    namedtuple(和rassar的批评)

    我不相信你的enum批评。在我看来,你相信我接近这个问题是错误的。可以打电话给我,但我不知道如何使用namedtuple与" Enum [它]将为每个常数提供单独的值。&{34}。 #34;我误解了你吗?

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

    接受的理由

    我接受了零点回答,因为答案给了我namedtuple最好的挑战者。 按我的标准,namedtuple是最好的解决方案。但是salparadise写了答案,让我对评估有信心。感谢所有回答的人。

4 个答案:

答案 0 :(得分:3)

工厂函数如何创建索引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>]

答案 1 :(得分: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()

更新

Mapdotdict与@SigmaPiEpsilon指出的行为不同,所以我为两者添加了单独的描述。

答案 2 :(得分:1)

在你的例子中,3是对你的问题最狡猾的答案。

正如你所说,

1甚至没有回答你的问题,因为这些名字并不明确。

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

如果客户想要添加选项,那么

4就无法维护,即使这是一项艰苦的工作。

3以可读和简洁的方式使用标准库中的众所周知的类。此外,它没有任何缺点,因为它是完全明确的。如果你不关心性能,那么太“沉重”并不重要,无论如何,你输入的大小都不会引起滞后。

答案 3 :(得分:1)

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

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

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

  3. Pure Python Way(无外部依赖)

    解决问题的最pythonic方法当然是使用字典的方法(字典是python的核心)。通过密钥搜索字典也比其他方法快得多。唯一的问题是这可以防止点访问。另一个答案提到Mapdotdict作为替代方案。 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方式

    另一种选择是使用允许点访问的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成为了自己的对象。