Python中子类化set时定义__repr__的方法

8

我正在尝试在Python中使用类似下面的代码对set对象进行子类化,但我无法确定要使用的合理__repr__定义。

class Alpha(set):
    def __init__(self, name, s=()):
        super(Alpha, self).__init__(s)
        self.name = name

我希望定义__repr__以便我可以获得以下输出:
>>> Alpha('Salem', (1,2,3))
Alpha('Salem', set([1, 2, 3]))

然而,如果我不覆盖__repr__,那么我得到的输出会忽略name值...

>>> Alpha('Salem', (1,2,3))
Alpha([1, 2, 3])

…如果我重写__repr__,那么我就无法直接访问集合中的值,除非创建一个新的集合实例:

class Alpha(set):
    …
    def __repr__(self):
        return "%s(%r, %r)" % (self.__class__.__name__, self.name, set(self))

这个方法是可行的,但是为了__repr__创建一个新的set实例,然后将其处理掉,对我来说似乎有些笨拙和低效。

是否有更好的方法来定义这种类的__repr__?

编辑: 我想到了另一个解决方案:我可以将set存储在本地。这似乎比其他选项(为每次调用__repr__创建和销毁某些东西或使用某种形式的字符串操作)更整洁,但对我来说仍然不理想。

class Alpha(set):
    def __init__(self, name, s=()):
        super(Alpha, self).__init__(s)
        self.name = name
        self._set = set(s)
    def __repr__(self):
        return "%s(%r, %r)" % (self.__class__.__name__, self.name, self._set)

6
如果子类尝试调用__init__,那么你调用super的方式会导致无限递归。 super显式地接受一个类是因为它知道在方法解析顺序(MRO)中应该继续执行什么。传递Alpha(如果这是3.x版本,则可以使用super() - 它会以某种方式执行正确的操作)。 - user395760
@delnan:噔。谢谢。我还以为自己很聪明,避免了显式指定类的操作。 - me_and
@delnan:由于某种原因,Sven Marnach简要标记了这个问题为Python 3.x。我实际上使用的是Python 2.6。 - me_and
关于最后一次编辑:我认为你应该避免在私有属性中保留集合的副本,因为那样你需要保持同步。而要做到这一点,你仍然需要每次想要重新表示时进行复制。对于这种方法,你可能需要将所有的set方法调用转发到私有set并完全使用它作为你的数据。 - jdi
2个回答

12

我认为我有一些内容可以满足你的需求,并且还可以展示一些基准测试。虽然它们几乎都是等效的,但我相信在内存使用方面存在差异。

#!/usr/bin/env python

import time

class Alpha(set):
    def __init__(self, name, s=()):
            super(Alpha, self).__init__(s)
            self.name = name
    def __repr__(self):
            return '%s(%r, set(%r))' % (self.__class__.__name__, 
                                        self.name, 
                                        list(self))

class Alpha2(set):
    def __init__(self, name, s=()):
            super(Alpha2, self).__init__(s)
            self.name = name
    def __repr__(self):
            return '%s(%r, set(%r))' % (self.__class__.__name__, 
                                        self.name, 
                                        set(self))

class Alpha3(set):
    def __init__(self, name, s=()):
            super(Alpha3, self).__init__(s)
            self.name = name
    def __repr__(self):
            rep = super(Alpha3, self).__repr__()
            rep = rep.replace(self.__class__.__name__, 'set', 1)
            return '%s(%r, %s)' % (self.__class__.__name__, 
                                    self.name, 
                                    rep)

def timeit(exp, repeat=10000):
    results = []
    for _ in xrange(repeat):
        start = time.time()
        exec(exp)
        end = time.time()-start
        results.append(end*1000)
    return sum(results) / len(results)

if __name__ == "__main__":
    print "Alpha():  ", timeit("a = Alpha('test', (1,2,3,4,5))")
    print "Alpha2(): ", timeit("a = Alpha2('test', (1,2,3,4,5))")
    print "Alpha3(): ", timeit("a = Alpha3('test', (1,2,3,4,5))")

结果:

Alpha(): 0.0287627220154

Alpha2(): 0.0286467552185

Alpha3(): 0.0285225152969


1
我愿意打赌,在内存方面,第一和第二个示例效率较低,因为它们正在实例化并丢弃对象。但这只是一种假设,没有实际检查内存/CPU。第三个示例仅获取一个字符串并重新格式化它。 - jdi
list()比set()更高效。字符串替换比set()略好,但它可以提供他想要的确切格式。因此,这是一个权衡。 - jdi
这些都不太适合我:创建和丢弃实例,我敢打赌,对于更大的列表来说会更加昂贵,而字符串编辑感觉相当脆弱和“非Python风格”。 - me_and
无论如何,+1 是因为你提供了非常完整的答案并包含了基准测试。 - me_and
@jdi:说得好,做得好。我想那将是我选择的选项。谢谢! - me_and
显示剩余3条评论

3

我找不到比这更好的方法了。虽然,我认为这比扔掉一套要好。

(Python 2.x)

>>> class Alpha(set):
...     def __init__(self, name, s=()):
...             super(Alpha, self).__init__(s)
...             self.name = name
...     def __repr__(self):
...             return 'Alpha(%r, set(%r))' % (self.name, list(self))
... 
>>> Alpha('test', (1, 2))
Alpha('test', set([1, 2]))

如果你不喜欢硬编码的类名(虽然它真的不应该有什么问题)。

>>> class Alpha(set):
...     def __init__(self, name, s=()):
...             super(Alpha, self).__init__(s)
...             self.name = name
...     def __repr__(self):
...             return '%s(%r, set(%r))' % (self.__class__.__name__, self.name, list(self))
... 
>>> Alpha('test', (1, 2))
Alpha('test', set([1, 2]))

2
我不会硬编码类名,而是使用 self.__class__.__name__(就像 me_and 做的那样)。 - gecco
1
@gecco:个人偏好。我认为没有硬编码的意义不大(除非你要有子类继承它),特别是一些地方仍然需要它(super调用)。反正很容易更改。但是根据请求,我也添加了非硬编码版本。 - John Doe
1
我同意@JohnDoe的观点。当你在super()调用中已经需要显式使用类名时,我并不认为这很关键。但是确实会更加动态一些。 - jdi
1
@JohnDoe:我绝对会有类继承这个类,因此在这里使用动态名称更加合理。 - me_and

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接