关于python:将一个自定义类的序列映射到另一个自定义类的Pythonic方法是什么?

What is the Pythonic way to map a sequence of one custom class to another?

(或者,C’s Select(...)方法的Python式版本是什么?)

给定一个自定义类l的列表A是什么(最多?)将l的每个元素映射到不同的自定义类B的pythonic方法?

例如,下面的代码可以做到这一点,但这是最为Python式的方法吗?注意,实际类型有许多属性。

1
2
3
4
5
6
7
8
9
10
l = [A('Greg', 33), A('John', 39)]

def map_to_type_b(the_list):
    new_list = []
    for item in the_list:
        new_list.append(B(item.name, item.age))

    return new_list

l2 = map_to_type_b(l)

我来自一个C背景,在这里我将使用LinqselectSelect()扩展方法从源序列投影到一个新的B类型序列。


不仅在Python中,而且在大多数基于OO的语言中,只写数据对象是不受欢迎的。也许最为Python式的方法是传递平面数据,比如说,口述或口述列表:

1
2
3
{'Greg': 33, 'John': 39}

[{'name': 'Greg', 'age': 33}, {'name': 'John', 'age': 39}]

也就是说,假设您有类A和类B,并且您希望从现有的A实例实例化新的B:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class A(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        return '<{cls} name={s.name}, age={s.age}>'.format(
            cls=self.__class__.__name__,
            s=self
        )

class B(A):
    def __init__(self, name, age, born_as='male'):
        super(B, self).__init__(name, age)
        self.born_as = born_as

data = {'Greg': 33, 'John': 39}
list_of_a = [A(k, v) for k, v in data.items()]

你可以简单明了地说:

1
2
3
4
5
>>> list_of_a
[<A name=Greg, age=33>, <A name=John, age=39>]

>>> [B(a.name, a.age) for a in list_of_a]
[<B name=Greg, age=33>, <B name=John, age=39>]

如果涉及到很多属性,这可能会变得有点冗长。让我们教B如何克隆A:

1
2
3
4
5
6
7
8
class B(A):
    def __init__(self, name, age, born_as='male'):
        super(B, self).__init__(name, age)
        self.born_as = born_as

    @classmethod
    def clone(cls, instance, *args, **kwargs):
        return cls(instance.name, instance.age, *args, **kwargs)

既然B现在知道如何克隆A:

1
2
>>> [B.clone(a) for a in list_of_a]
[<B name=Greg, age=33>, <B name=John, age=39>]

为所有B类类编写克隆方法可能会变得单调乏味。反省是非常的Python,所以我们不要重复我们自己:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class CloneFromInstanceMixin(object):
    @classmethod
    def clone(cls, instance, **kwargs):
        constructor_args = inspect.getargspec(instance.__init__).args
        for attr_name in constructor_args:
            if attr_name in kwargs:
                continue # overrides instance attribute
            try:
                kwargs[attr_name] = getattr(instance, attr_name)
            except AttributeError:
                pass
        return cls(**kwargs)

class B(CloneFromInstanceMixin, A):
    def __init__(self, name, age, born_as='male'):
        super(B, self).__init__(name, age)
        self.born_as = born_as

>>> [B.clone(a) for a in list_of_a]
[<B name=Greg, age=33>, <B name=John, age=39>]

我可能有太多的空闲时间。


我想说,它是B类工作的一部分,用来确定如何将任意其他类的一个实例转换为B的一个实例,因此我将使用类方法替代构造函数方法,例如如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class A(object):

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        return 'A({0.name!r}, {0.age!r})'.format(self)


class B(A):

    def __repr__(self):
        return 'B({0.name!r}, {0.age!r})'.format(self)

    @classmethod
    def from_A(cls, inst):
        return cls(inst.name, inst.age)

然后,您可以使用简单的列表理解,甚至使用map将一个类的列表转换为另一个类,例如:

1
2
3
4
5
6
7
8
9
>>> l = [A('Greg', 33), A('John', 39)]
>>> l
[A('Greg', 33), A('John', 39)]

>>> map(B.from_A, l)  # will look different, but is more memory-efficient, in 3.x
[B('Greg', 33), B('John', 39)]

>>> [B.from_A(a) for a in l]  # works (nearly) identically in 2.x and 3.x
[B('Greg', 33), B('John', 39)]