使用[object instance].__self__有什么意义?

40

我在查看Python的toolz库中的groupby函数的代码时发现了这个:

def groupby(key, seq):
    """ Group a collection by a key function
    """
    if not callable(key):
        key = getter(key)
    d = collections.defaultdict(lambda: [].append)
    for item in seq:
        d[key(item)](item)
    rv = {}
    for k, v in d.items():
        rv[k] = v.__self__
    return rv

使用 rv[k] = v.__self__ 而不是 rv[k] = v 有什么原因吗?


我认为它是指“自身”的所有单独属性。使用__self__可以收集到个体对象的所有唯一值。 - 5idneyD
2
请参考以下链接中的Python官方文档,了解实例方法的相关信息:https://docs.python.org/3/reference/datamodel.html?highlight=__self__#index-32,位于“Instance methods”下面。 - deceze
5
d 是一个键到 lambda 表达式创建的列表的 append 方法的映射,所以 rv[k] = v.__self__ 构建了一个键到实际列表的映射。这种有些令人困惑的实现方式之所以被使用是为了提高速度,相关背景信息可以在这里找到(简而言之:速度)。 - jonrsharpe
2个回答

40

这是一个有些令人困惑的技巧,可以节省一点时间:

我们正在创建一个defaultdict,使用工厂函数创建一个新列表实例的绑定append方法[].append。然后我们可以通过d[key(item)](item)来操作它,而不是像我们如果创建包含列表的defaultdict那样使用d[key(item)].append(item)。如果我们不必每次查找append,我们就会节省一点时间。

但现在dict包含的是绑定方法而不是列表,因此我们必须通过__self__来获取原始的列表实例。

__self__是一个用于实例方法的属性,返回原始实例。例如,您可以使用以下方式验证:

>>> a = []
>>> a.append.__self__ is a
True

10
@don'ttalkjustcode 我坚持这个观点。在这里使用的代码并不是最清晰明了的代码(但也不是他们可能选择的最混乱的变体)。在我看来,任何需要在SO上提出一个非糟糕问题来解释的内容都可以被归类为令人困惑的。 - MegaIng
1
@不是多嘴,只是编程。混乱并不意味着难以理解,只是需要看几次才能明白他们做了什么,再看几次就能明白为什么了。 - Mad Physicist
@don'ttalkjustcode 因为你在 Python 方面有很多经验。这对我来说也很明显,但显然不是每个人都能看出来。 - MegaIng
3
@不要说话,只写代码。我曾经不得不查找__self__,因为以前从未使用过它。一旦我这样做了,它就变得微不足道了,并且在将来很可能仍然如此。这是一个经验问题。 - Mad Physicist
@MadPhysicist 我同意“convoluted”可能是更好的选择,因为它比“confusing”稍微弱一些。 - MegaIng
显示剩余3条评论

18

这是一种有点复杂但可能更高效的创建和使用defaultdict列表的方法。

首先,请记住默认项目是 lambda: [].append。这意味着创建一个新列表,并在字典中存储一个绑定的 append 方法。这样可以节省在每次附加到相同键时进行方法绑定和随之而来的垃圾回收。例如,下面更标准的方法不太高效:

d = collections.defaultdict(list)
for item in seq:
    d[key(item)].append(item)

问题变成如何从字典中提取原始列表,因为引用并没有显式地存储。幸运的是,绑定的方法有一个__self__属性,可以做到这一点。在这里,[].append.__self__是对原始[]的引用。

顺便说一句,最后的循环可以使用推导式:

return {k: v.__self__ for k, v in d.items()}

关于“垃圾回收”:我认为大多数情况下,如果不是全部情况,它不会到那个地步,而是会成为引用计数的受害者。 - no comment
1
@don'ttalkjustcode:引用计数是垃圾回收的一种形式(虽然比较原始)。在CPython参考解释器中,区别在于常规引用计数和循环GC,但两者都是GC。你说得对,绑定方法通常会通过引用计数立即释放,而不必等待循环垃圾收集器,但无论哪种方式,绑定和清理都是必须完成的(尽管LOAD_METHOD/CALL_METHOD有时可以在现代CPython中避免这种情况)。 - ShadowRanger
@ShadowRanger 是的,但我记得在Python术语中它是分开的,我查看了gc文档,其中“收集器补充引用计数”听起来更像是分开的。但我现在也查看了glossarydevguide,两者都明确说明它确实包含在内。 - no comment

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