【问题标题】:Python: Make class iterablePython:使类可迭代
【发布时间】:2018-08-31 08:13:00
【问题描述】:

我继承了一个项目,其中包含许多仅由类对象(整数、字符串等)构成的大型类。我希望能够检查是否存在属性,而无需手动定义属性列表。

是否可以使用标准语法使 python class 自身可迭代?也就是说,我希望能够使用for attr in Foo:(甚至if attr in Foo)遍历类的所有属性,而无需先创建类的实例。我想我可以通过定义__iter__ 来做到这一点,但到目前为止,我还没有完全管理好我正在寻找的东西。

我已经通过像这样添加__iter__ 方法实现了一些我想要的:

class Foo:
    bar = "bar"
    baz = 1
    @staticmethod
    def __iter__():
        return iter([attr for attr in dir(Foo) if attr[:2] != "__"])

但是,这并不能完全满足我的要求:

>>> for x in Foo:
...     print(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'classobj' object is not iterable

即便如此,这仍然有效:

>>> for x in Foo.__iter__():
...     print(x)
bar
baz

【问题讨论】:

  • 如果你想检查一个属性是否存在,方法是hasattr。你不需要让你的类可迭代。

标签: python syntax attributes static-methods loops


【解决方案1】:

__iter__ 添加到元类而不是类本身(假设 Python 2.x):

class Foo(object):
    bar = "bar"
    baz = 1
    class __metaclass__(type):
        def __iter__(self):
            for attr in dir(self):
                if not attr.startswith("__"):
                    yield attr

对于 Python 3.x,使用

class MetaFoo(type):
    def __iter__(self):
        for attr in dir(self):
            if not attr.startswith("__"):
                yield attr

class Foo(metaclass=MetaFoo):
    bar = "bar"
    baz = 1

【讨论】:

  • 不错。请你解释为什么OP的方法不起作用?谢谢。
  • @aix:OP 的方法不起作用的原因是__iter__ 方法仅适用于类的instances。这会将__iter__ 方法提升到元类的实例,即类。
  • @aix:与其他魔术方法一样,__iter__ 在对象类型的名称空间中查找,而不是在对象的名称空间本身中查找。我在 Python 文档中并没有真正找到这个解释,但是在 source code 中可以很容易地看到它(搜索 PyObject_GetIter() 的定义)。
  • Foo 类上将__iter__() 方法编写为@classmethod 时,有人可以解释为什么它不起作用吗?我认为@nmichaels 的答案并没有解释这一点。 ` @classmethod def __iter__(cls): for x in xrange(10): yield x `
  • @trudolf:Special methods are looked up on the type of the instance, not on the instance itself。如果实例是一个类,这意味着特殊方法是在元类上查找的,而不是类本身。
【解决方案2】:

您可以使用for attr in (elem for elem in dir(Foo) if elem[:2] != '__') 遍历类的未隐藏属性。

一种不那么可怕的拼写方式是:

def class_iter(Class):
    return (elem for elem in dir(Class) if elem[:2] != '__')

然后

for attr in class_iter(Foo):
    pass

【讨论】:

  • 我必须承认我更喜欢这种看起来比 OP 更 Pythonic 的解决方案。但是有没有解决他的问题我没有+1
【解决方案3】:

这就是我们使类对象可迭代的方式。为类提供 iter 和 next() 方法,然后您可以迭代类属性或其值。如果您愿意,可以保留 next() 方法,或者您可以定义 next( ) 并在某些条件下引发 StopIteration。

例如:

class Book(object):
      def __init__(self,title,author):
          self.title = title
          self.author = author

      def __iter__(self):
          for each in self.__dict__.values():
              yield each

>>> book  = Book('The Mill on the Floss','George Eliot')
>>> for each in book: each
...
'George Eliot'
'The Mill on the Floss'

这个类迭代了类Book的属性值。 类对象也可以通过提供 getitem 方法使其可迭代。 例如:

class BenTen(object):
    def __init__(self, bentenlist):
        self.bentenlist = bentenlist
        
    def __getitem__(self,index):
        if index <5:
            return self.bentenlist[index]
        else:
            raise IndexError('this is high enough')

>>> bt_obj = BenTen([x for x in range(15)])
>>>for each in bt_obj:each
...
0
1
2
3
4

现在当 BenTen 类的对象在 for-in 循环中使用时,getitem 会以连续更高的索引值被调用,直到引发 IndexError。

【讨论】:

  • 这会迭代一个类的 instance 的属性(即book = Book(...) 中的book);问题是关于直接迭代 class 属性(即class Book(object): 中的Book)。
  • 虽然这不是 OP 问题的答案,但它帮助了我,因为我在搜索可迭代类时正在寻找这个。
【解决方案4】:
class MetaItetaror(type):
    def __iter__(cls):
        return iter(
            filter(
                lambda k: not k[0].startswith('__'),
                cls.__dict__.iteritems()
            )
        )


class Klass:
    __metaclass__ = MetaItetaror

    iterable_attr_names = {'x', 'y', 'z'}
    x = 5
    y = 6
    z = 7


for v in Klass:
    print v

【讨论】:

    【解决方案5】:

    enum.Enum 的实例恰好是可迭代的,虽然它不是通用解决方案,但对于某些用例来说是一个合理的选择:

    from enum import Enum
    
    class Foo(Enum):
        bar = "qux"
        baz = 123
    
    >>> print(*Foo)
    Foo.bar Foo.baz
    
    names = [m.name for m in Foo]
    >>> print(*names)
    bar baz
    
    values = [m.value for m in Foo]
    print(*values)
    >>> qux 123
    

    .__dict__ 一样,使用这种基于Enum 的方法的迭代顺序与定义顺序相同。

    【讨论】:

    • 这完全改变了类的行为及其所有属性。使类可迭代是一个次要的副作用。这不是使类可迭代的一般问题的解决方案。
    • @user2357112supportsMonica 首先,这是一个奇怪的用户名。其次,我现在更新了答案,提到这种方法不是通用解决方案。尽管如此,它对于某些用例还是有用的。
    猜你喜欢
    • 2020-09-12
    • 2012-03-23
    • 2018-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-12
    相关资源
    最近更新 更多