【问题标题】:Python equivalent of C++ begin() and end() for custom classes用于自定义类的 Python 等效于 C++ begin() 和 end()
【发布时间】:2015-11-28 20:33:11
【问题描述】:

假设您有一个字典,其键是整数。这些值也是字典,其键是字符串,其值是 numpy 数组。 比如:

custom = {1: {'a': np.zeros(10), 'b': np.zeros(100)}, 2:{'c': np.zeros(20), 'd': np.zeros(200)}}

我一直在代码中大量使用这种自定义数据结构,每次我需要遍历这个结构的 numpy 数组中的每一行时,我都必须这样做:

for d, delem in custom.items():
    for k, v in delem.items():
        for row in v:
            print(row)

是否可以将此行为封装在函数à la C++ 中,您可以在其中实际实现自定义begin()end()?此外,迭代器还应该具有有关其相应字典中的键的信息。我的设想是:

for it in custom:
    d, e, row = *it
    # then do something with these

【问题讨论】:

    标签: python loops iteration


    【解决方案1】:

    有多种方法可以做到这一点。 yield 可能是最简单的,因为它为您构建了一个交互器类的繁重工作。

    def custom_dict_iter(custom):
        for d, delem in custom.items():
            for k, v in delem.items():
                for row in v:
                    yield d, k, row
    
    for d, k, row in custom_dict_iter(my_custom_dict):
        print(d, k, row)
    

    【讨论】:

    • 你的意思是你会yield d,k,row我假设。这还是不是太冗长了?它比我的要好得多。
    • @aaragon 你是对的。我专注于print(row)。固定。
    • @aaragon 我不认为它太冗长,因为您想跟踪和返回键以及值。并将其与重载 C++ 迭代器进行比较,它相当不错。
    【解决方案2】:

    查找iterator protocol - 这更类似于Java 的Iterable 或C# 的IEnumerable,而不是C++ 的开始/结束。您可以通过将__iter__ 方法定义为generator 来更轻松地定义它。

    唯一的问题是,您需要使用这些方法让您的 custom 拥有自己的类,而不是普通的字典,但我认为在 C++ 中也是如此。

    【讨论】:

    • 您的解决方案看起来很有趣。所以我猜你的意思是我创建了一个从字典派生的自定义类,它覆盖了__iter__ 方法,对吧?
    • @aaragon 你可以这样做,我猜。我可能只是将字典作为一个字段。
    • @aaragon,你没有覆盖:你只需要在你的自定义类中定义__iter__()__next__()__iter__() 只有一行:return self。在__next__() 内部是所有动作发生的地方。而且,在__next__() 中,当您确定迭代器已到达数据末尾时,您需要raise StopIteration。应用于自定义类实例的 for-in 循环将自动捕获 StopIteration 异常并终止。这就是所有 for-in 循环终止的方式。
    【解决方案3】:
    import numpy as np
    
    custom = {
        1: {'a': np.zeros(10), 'b': np.zeros(100)}, 
        2:{'c': np.zeros(20), 'd': np.zeros(200)}
    }
    
    my_gen = (
        (key, subkey, np_array) 
        for (key, a_dict) in custom.items() 
        for subkey, np_array in a_dict.items() 
    )
    
    for key, subkey, np_array in my_gen:
        print(key, subkey, np_array)
    
    --output:--
    1 b [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
      0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
      0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
      0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
      0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
      0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
    1 a [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.]
    2 d [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
      0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
      0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
      0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
      0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
      0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
      0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
      0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
      0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
      0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
      0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
      0.  0.]
    2 c [ 0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.  0.
      0.  0.]
    

    或者,您可以将数据结构重构为对您的目的更有用的东西:

    import numpy as np
    
    custom = {
        1: {'a': np.zeros(10), 'b': np.zeros(100)}, 
        2:{'c': np.zeros(20), 'd': np.zeros(200)}
    }
    
    #Create a *list* of tuples:
    converted_data = [
        (np_array, subkey, key)
        for (key, a_dict) in custom.items() 
        for subkey, np_array in a_dict.items() 
    ]
    
    for np_array, subkey, key in converted_data:
        print(key, subkey, np_array)
    

    创建自定义迭代器:

    class Dog:
        def __init__(self, data):
            self.data = data
            self.max = len(data)
            self.index_pointer = 0
    
        def __next__(self):
            index = self.index_pointer
    
            if index < self.max:
                current_val = self.data[index]
                self.index_pointer += 1
                return current_val
            else:
                raise StopIteration
    
    
    class MyIter:
        def __iter__(self):
            return Dog([1, 2, 3])
    
    
    for i in MyIter():
        print(i)
    
    --output:--
    1
    2
    3
    

    __iter__() 只需要返回一个实现__next__() 方法的对象,因此您可以像这样组合这两个类:

    class MyIter:
        def __init__(self, data):
            self.data = data
            self.max = len(data)
            self.index_pointer = 0
    
        def __iter__(self):
            return self  #I have a __next__() method, so let's return me!
    
        def __next__(self):
            index = self.index_pointer
    
            if index < self.max:
                current_val = self.data[index]
                self.index_pointer += 1
                return current_val
            else:
                raise StopIteration
    
    for i in MyIter([1, 2, 3]):
        print(i)
    
    --output:--
    1
    2
    3
    

    更复杂的__next__() 方法:

    import numpy as np
    
    class CustomIter:
        def __init__(self, data):
            self.data = data
            self.count = 0
    
    
        def __iter__(self):
            return self
    
        def __next__(self):
            count = self.count
            self.count += 1
    
            if count == 0:  #On first iteration, retun a sum of the keys
                return sum(self.data.keys())
    
            elif count == 1: #On second iteration, return the subkeys in tuples
                subkeys =  [ 
                    a_dict.keys()
                    for a_dict in self.data.values()
                ]
    
                return subkeys
    
            elif count == 2: #On third iteration, return the count of np arrays
                np_arrays = [
                    np_array
                    for a_dict in self.data.values()
                    for np_array in a_dict.values()
                ]
    
                return len(np_arrays)
    
            else:  #Quit after three iterations
                raise StopIteration
    
    
    custom = {
        1: {'a': np.zeros(10), 'b': np.zeros(100)}, 
        2:{'c': np.zeros(20), 'd': np.zeros(200)}
    }
    
    for i in CustomIter(custom):
        print(i)
    
    
    --output:--
    3
    [dict_keys(['b', 'a']), dict_keys(['d', 'c'])]
    4
    

    【讨论】:

    • 感谢您的回复。在您的解决方案中使用my_gen 和在@tdelaney 的解决方案中使用custom_dict_iter 有什么区别?
    • @aarogon,您可以直接定义生成器——无需定义函数。但是,你不能倒带发电机,所以如果你想稍后再买一个发电机,我会选择 tdelaney 的解决方案
    • @aaragon,您总是可以围绕直接生成器语法包装一个函数,例如get_gen(...): return ( (key, subkey, np_array)....
    • @aaragon,将你的数据结构转换成更友好的东西怎么样?请参阅我的答案的底部。
    • @aaragon,如果您有兴趣并遇到麻烦,我在答案底部发布了一个创建自定义迭代器类的示例。
    【解决方案4】:

    作为一种更 Python 的方式,您可以使用嵌套列表推导,它在解释器中以 C 语言速度执行:

    >>> [[(i,key,t) for t in value] for i,j in custom.items() for key,value in j.items()]
    

    如果你想获得一个迭代器,你可以使用生成器表达式而不是列表推导。

    >>> ([(i,key,t) for t in value] for i,j in custom.items() for key,value in j.items())
    

    【讨论】:

    • 我认为这与@7stud 所做的很接近,只是您已将其构建到外部列表中。我的解决方案打破了循环......现在我很好奇它是否更慢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多