10

I want to define a mix-in of a namedtuple and a base class with defines and abstract method:

import abc
import collections

class A(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def do(self):
        print("U Can't Touch This")

B = collections.namedtuple('B', 'x, y')

class C(B, A):
    pass

c = C(x=3, y=4)
print(c)
c.do()

From what I understand reading the docs and other examples I have seen, c.do() should raise an error, as class C does not implement do(). However, when I run it... it works:

B(x=3, y=4)
U Can't Touch This

I must be overlooking something.

Community
  • 1
  • 1
plok
  • 705
  • 7
  • 30
  • 1
    What version of python are you using? Metaclasses are invoked in different, incompatible ways in python 2 versus python 3. If you are using python 3 your class isn't actually an abstract class. – Dunes Feb 08 '16 at 11:44
  • I tried this with both Python 2 and 3. Same behavior. – plok Feb 15 '16 at 07:35

1 Answers1

6

When you take a look at the method resolution order of C you see that B comes before A in that list. That means when you instantiate C the __new__ method of B will be called first.

This is the implementation of namedtuple.__new__

def __new__(_cls, {arg_list}):
    'Create new instance of {typename}({arg_list})'
    return _tuple.__new__(_cls, ({arg_list}))

You can see that it does not support cooperative inheritance, because it breaks the chain and simply calls tuples __new__ method. Like this the ABCMeta.__new__ method that checks for abstract methods is never executed (where ever that is) and it can't check for abstract methods. So the instantiation does not fail.

I thought inverting the MRO would solve that problem, but it strangely did not. I'm gonna investigate a bit more and update this answer.

Wombatz
  • 4,958
  • 1
  • 26
  • 35
  • 1
    Probably inverting MRO doesn't matter because `B` implements `__new__` and `A` does not. – Robin Davis Feb 08 '16 at 11:29
  • 1
    but both `namedtuple` and `ABCMeta` have a `__new__` method. Isn't MRO depth first search like? – Wombatz Feb 08 '16 at 11:31
  • 1
    Kinda. See this example: `>>> class X(B,A): ... pass ... >>> X.__mro__ (, , , , ) >>> >>> class Y(A,B): ... pass ... >>> Y.__mro__ (, , , , )`. Either way tuple is called first, because ultimately they both derive from object. – Robin Davis Feb 08 '16 at 11:40
  • 1
    ABCMeta is a type. It's `__new__` function is only called when a class is created, not when an object is created. – Dunes Feb 08 '16 at 12:01
  • 1
    The check for abstract methods occurs in `object.__new__`. Since the class also derives from tuple (which derives from object), then `tuple.__new__` is always invoked in preference to `object.__new__`. And `tuple.__new__` doesn't check for abstract methods. So it's impossible to have an abstract tuple subclass. A possible bug. – Dunes Feb 08 '16 at 12:10
  • 2
    Yeah, I got confused, of course `ABCMeta.__new__` is invoked at a different time. But I still don't understand how the check for abstract methods can be done in `object.__new__`. Is there code to check that? And why is it in `object.__new__` and not in `ABCMeta.__call__` which would make sense to me. – Wombatz Feb 08 '16 at 12:37
  • It is possible to have abstract classes, just not to instantiate them. As such, the check can't be done by `ABCMeta` -- as it is only invoked at class creation time. At class creation, `ABCMeta` sets a flag on the class saying that it is abstract -- https://github.com/python-git/python/blob/715a6e5035bb21ac49382772076ec4c630d6e960/Objects/typeobject.c#L321 . This is checked for by `object.__new__` at object creation time -- https://github.com/python-git/python/blob/715a6e5035bb21ac49382772076ec4c630d6e960/Objects/typeobject.c#L2876 – Dunes Feb 15 '16 at 10:40