The Context
I have a python application with a relatively involved class hierarchy. It needs to work with python 2.6 up to python 3.5 (a big range, I know!), and I've been having particular problems with ABCs.
I'm using the six
library's with_metaclass
to ease some of the hurt, but it's still problematic.
One particular set of classes has been giving me trouble. Here's what it looks like in a simplified form:
from abc import ABCMeta
from six import with_metaclass
# SomeParentABC is another ABC, in case it is relevant
class MyABC(with_metaclass(ABCMeta, SomeParentABC)):
def __init__(self, important_attr):
self.important_attr = important_attr
def gamma(self):
self.important_attr += ' gamma'
class MyChild1(MyABC):
def __repr__(self):
return "MyChild1(imporant_attr=%s)" % important_attr
def alpha(self):
self.important_attr += ' alpha'
class MyChild2(MyABC):
def __repr__(self):
return "MyChild2(imporant_attr=%s)" % important_attr
def beta(self):
self.important_attr += ' beta'
There's a lot of gamma
like functions bundled in MyABC
, and a few subclass specific functions like alpha
and beta
. I want all of the subclasses of MyABC
to inherit the same __init__
and gamma
attributes, and then pile on their own specific characteristics.
The Problem
The issue is that in order for MyChild1
and MyChild2
to share code for __init__
, MyABC
needs to have a concrete initializer.
In Python 3, everything is working just fine, but in Python 2, when the initializer is concrete, I fail to get TypeErrors
when instantiating MyABC
.
I have a segment in my testsuite that looks something like this
def test_MyABC_really_is_abstract():
try:
MyABC('attr value')
# ideally more sophistication here to get the right kind of TypeError,
# but I've been lazy for now
except TypeError:
pass
else:
assert False
Somehow, in Python 2.7 (I assume 2.6, but haven't bothered to check) this test is failing.
MyABC
doesn't have any other abstract properties, but it isn't meaningful to instantiate a class that has gamma
without also having either alpha
or beta
.
For now, I've been getting by with a DRY violation by just duplicating the __init__
function in MyChild1
and MyChild2
, but as time goes on this is becoming more and more burdensome.
How can I give a Python 2 ABC a concrete initializer without making it instantiable, while maintaining Python 3 compatibility?
In other words, I want trying to instantiate MyABC
to throw TypeError
s in Python 2 and Python 3, but it only throws them in Python 3.
with_metaclass
I believe it is relevant to see the code for with_metaclass
here.
This is provided under the existing License and Copyright of the six
project, (c) 2010-2014 Bejamin Peterson
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
# This requires a bit of explanation: the basic idea is to make a dummy
# metaclass for one level of class instantiation that replaces itself with
# the actual metaclass.
class metaclass(meta):
def __new__(cls, name, this_bases, d):
return meta(name, bases, d)
return type.__new__(metaclass, 'temporary_class', (), {})