3

I am making a python program which is using classes, I want one class to only selectively inherit from another e.g:

class X(object):
    def __init__(self):
        self.hello = 'hello'

class Y(object):
    def __init__(self):
        self.moo = 'moo'

class Z():
    def __init__(self, mode):
        if mode == 'Y':
             # Class will now Inherit from Y
        elif mode == 'X':
             # Class will now Inherit for X

How can I do this without making another class?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343

4 Answers4

3

In Python classes can be created at run-time:

class X(object):
    def __init__(self):
        self.hello = 'hello'

class Y(object):
    def __init__(self):
        self.moo = 'moo'

def create_class_Z(mode):
    base_class = globals()[mode]
    class Z(base_class):
        def __init__(self):
            base_class.__init__(self)
    return Z

ZX = create_class_Z('X')
zx = ZX()
print(zx.hello)

ZY = create_class_Z('Y')
zy = ZY()
print(zy.moo)
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
2

You can do this by overriding __new__ and changing the cls passed in (you're creating a new type by appending X or Y as a base class):

class X(object):
    def __init__(self):
        self.hello = 'hello'

class Y(object):
    def __init__(self):
        self.moo = 'moo'

class Z(object):
    def __new__(cls, mode):
        mixin = {'X': X, 'Y': Y}[mode]
        cls = type(cls.__name__ + '+' + mixin.__name__, (cls, mixin), {})
        return super(Z, cls).__new__(cls)
    def __init__(self, mode, *args, **kwargs):
        super(Z, self).__init__(*args, **kwargs)

Note that you need to bypass Z.__new__ using super to avoid infinite recursion; this is the standard pattern for __new__ special override methods.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • 1
    I think this solution is overly complicated compared to creating the new type in a function outside of class Z, avoiding the infinite recursion pitfall... – l4mpi Sep 19 '12 at 10:03
  • 1
    @l4mpi it's hardly a pitfall; it's the standard idiom. Writing a `__new__` has the advantage of keeping relevant code together and retaining compatibility with existing code. – ecatmur Sep 19 '12 at 10:08
  • Writing another function has the advantage of being able to reuse it - my solution is hardcoded to use class Z as the first base class, but you could just put it in a closure with a class and a mapping of base classes and use it for multiple classes. Doing this means you add about two lines of code to every class you want to use in this way - shouldn't be too hard to keep this together with the rest of the code. Also, could you elaborate on the compatibility part? – l4mpi Sep 19 '12 at 10:23
  • 1
    @l4mpi if your code refers to `Z` anywhere (e.g. in `Z` methods with `super`, in other code accessing static methods or static members, monkeypatching in tests) then all that code expects `Z` to be the actual class. – ecatmur Sep 19 '12 at 13:09
  • 1
    @l4mpi also, subclassing `Z` would break; my solution works absolutely fine for that usage. – ecatmur Sep 19 '12 at 13:23
  • You can't create a subclass that overrides `__init__` but not `__new__` (except when you keep the signature of the new `__init__` the same as the signature of `Z.__new__`, which can lead to errors if you didn't intend to use it this way). The point is, you can't just subclass `Z` like you would subclass any other class (which is the same with my solution as you would have to subclass `_Z` instead of `Z`). The cleanest way would still be creating different classes (maybe dynamically at runtime) and a factory method returning an object with the appropriate base classes. – l4mpi Sep 19 '12 at 13:57
  • @l4mpi if you write your `__init__` method normally i.e. using `super(cls, self).__init__(*args, **kwargs)` then everything will work fine. – ecatmur Sep 19 '12 at 14:02
0

I think you'd better define two members within Z,one is a class instance of X,another is a instance of Y.You can get the associated information stored in these instances while use different mode.

rpbear
  • 640
  • 7
  • 15
  • He wants two diferent classes, not two different instances. I don't see how your proposed solution would be of any use. – l4mpi Sep 19 '12 at 10:24
0

A solution using type:

class _Z(): pass #rename your class Z to this

def Z(mode): #this function acts as the constructor for class Z
    classes = {'X': X, 'Y': Y, 'Foo': Bar} #map the mode argument to the base cls
    #create a new type with base classes Z and the class determined by mode
    cls = type('Z', (_Z, classes[mode]), {})
    #instantiate the class and return the instance
    return cls()
l4mpi
  • 5,103
  • 3
  • 34
  • 54
  • This fails `isinstance(Z('X'), Z)`. – ecatmur Sep 19 '12 at 10:07
  • @ecatmur Yes, but you shouldn't use isinstance anyways: http://www.canonical.org/~kragen/isinstance/ – l4mpi Sep 19 '12 at 10:16
  • That's out of date; Python has ABC and virtual interfaces now. – ecatmur Sep 19 '12 at 10:39
  • @ecatmur I fail to see how that invalidates the criticisms of isinstance compared to the EAFP approach... but anyways, you could just use `isinstance(Z('X'), _Z)` if it is absolutely needed. – l4mpi Sep 19 '12 at 10:47
  • [PEP 3119](http://www.python.org/dev/peps/pep-3119/) discusses where interfaces and virtual subclasses are superior to EAFP. My real point was that you're violating the basic assumption that `Z` is a class. – ecatmur Sep 19 '12 at 13:21
  • @ecatmur, I've read that one (multiple times actually) and still don't like it exactly because it doesn't make a point against EAFP. "If asking 'is this object a mutable sequence container?'", why not just use it as such and see if this results in an exception? (But I think this discussion is somewhat off topic here ^^). As for the assumption that `Z` is a class: one could call the method `makeZ` and leave `Z` intact, but `Z` itself breaks the assumption that for `a` and `b` of class `Z` `a.__class__ == b.__class__`, so this is somewhat a moot point... – l4mpi Sep 19 '12 at 13:38