0

I'm using Python 3.7, and I get an error in the code below. It looks like the super() keyword doesn't work properly with list comprehensions. I want to know the cause.

class A(object):
    def hello(self):
        return 1

class B(A):
    def __init__(self):
        why_error = [
            super().hello() for i in range(2) for j in range(2)
        ]
        print(why_error)

class C(A):
    def __init__(self):
        why_no_error = [
            super().hello(),
            super().hello(),
            super().hello(),
            super().hello()
        ]
        print(why_no_error)

c = C()
b = B()

And the execution result is as follows.

Traceback (most recent call last):
  File "test.py", line 23, in <module>
    b = B()
  File "test.py", line 8, in __init__
    super().hello() for i in range(2) for j in range(2)
  File "test.py", line 8, in <listcomp>
    super().hello() for i in range(2) for j in range(2)
TypeError: super(type, obj): obj must be an instance or subtype of type
SherylHohman
  • 16,580
  • 17
  • 88
  • 94
한일석
  • 21
  • 2
  • `super(B, self).hello()` would work I think – Iain Shelvington Dec 29 '19 at 16:04
  • 2
    Does this answer your question? [Python3's super and comprehensions -> TypeError?](https://stackoverflow.com/questions/31895302/python3s-super-and-comprehensions-typeerror) – kopecs Dec 29 '19 at 16:04
  • 1
    beside finding this approach hideous ... `why_error = [ self.hello() for i in range(2) for j in range(2) ]` would work without the super() – Patrick Artner Dec 29 '19 at 16:05
  • You're right. But what I'm curious about is why class B and C should work differently. – 한일석 Dec 29 '19 at 16:07
  • Note that using ``super`` to access a different method (e.g. ``hello`` from ``__init__``) generally indicates that your class is violating [LSP](https://en.wikipedia.org/wiki/Liskov_substitution_principle), because it means ``self.hello`` cannot be substituted for ``super().hello``. – MisterMiyagi Dec 29 '19 at 16:20

1 Answers1

3

Comprehensions are run in a separate scope -- which is also why i and j are not leaked -- that is nested in the method. The automatic insertion of __class__ and self for zero-argument super does not work in such a nested scope.

Use the two-argument form of super instead:

class B(A):
    def __init__(self):
        why_error = [
            super(B, self).hello() for i in range(2) for j in range(2)
        ]
        print(why_error)

In the case of class C, a list literal is used, not a list comprehension. Literals do not use a separate scope.

Displays for lists, sets and dictionaries

For constructing a list, a set or a dictionary Python provides special syntax called “displays”, each of them in two flavors:

  • either the container contents are listed explicitly, or
  • they are computed via a set of looping and filtering instructions, called a comprehension.

[...]

However, aside from the iterable expression in the leftmost for clause, the comprehension is executed in a separate implicitly nested scope. This ensures that names assigned to in the target list don’t “leak” into the enclosing scope.

Community
  • 1
  • 1
MisterMiyagi
  • 44,374
  • 10
  • 104
  • 119
  • Simpler solution (without repeating class name or `self`): Put `s = super()` outside the listcomp, then call `s.hello()` inside it. The `super` magic (performed on construction, not use) now executes in the correct scope. – ShadowRanger Dec 29 '19 at 16:09
  • 1
    @ShadowRanger If one is inclined to avoid the overhead, ``hello = super().hello`` outside the scope works better. – MisterMiyagi Dec 29 '19 at 16:22