Phillip B Oldham
Phillip B Oldham

Reputation: 19375

Set an object's superclass at __init__?

Is it possible, when instantiating an object, to pass-in a class which the object should derive from?

For instance:

class Red(object):
    def x(self):
        print '#F00'

class Blue(object):
    def x(self):
        print '#00F'

class Circle(object):
    def __init__(self, parent):
        # here, we set Bar's parent to `parent`
        self.x()

class Square(object):
    def __init__(self, parent):
        # here, we set Bar's parent to `parent`
        self.x()
        self.sides = 4

red_circle = Circle(parent=Red)
blue_circle = Circle(parent=Blue)
blue_square = Square(parent=Blue)

Which would have similar effects as:

class Circle(Red):
    def __init__(self):
        self.x()

without, however, affecting other instances of Circle.

Upvotes: 5

Views: 1614

Answers (5)

Alex Martelli
Alex Martelli

Reputation: 881595

As everybody else says, that's a pretty weird usage, but, if you really want it, it's surely feasible (except for the mysterious Bar that you pull out of thin air in comments;-). For example:

class Circle(object):
    def __init__(self, parent):
        self.__class__ = type('Circle', (self.__class__, parent), {})
        self.x()

This gives each instance of Circle its own personal class (all named Circle, but all different) -- this part is actually the key reason this idiom is sometimes very useful (when you want a "per-instance customized special method" with new-style classes: since the special method always gets looked up on the class, to customize it per-instance you need each instance to have a distinct class!-). If you'd rather do as much class-sharing as feasible you may want a little memoizing factory function to help:

_memo = {}
def classFor(*bases):
  if bases in _memo: return _memo[bases]
  name = '_'.join(c.__name__ for c in bases)
  c = _memo[bases] = type(name, bases, {})
  return c

(here I'm also using a different approach to the resulting class's name, using class names such as Circle_Red and Circle_Blue for your examples rather than just Circle). Then:

class Circle(object):
    def __init__(self, parent):
        self.__class__ = classFor(Circle, parent)
        self.x()

So the technique is smooth and robust, but I still don't see it as a good match to the use case you exemplify with. However, it might be useful in other use cases, so I'm showing it.

Upvotes: 0

Mark Roddy
Mark Roddy

Reputation: 27936

I agree with @AntsAasma. You should probably consider using dependency injection. Atleast in the example given (which I'm sure is greatly simplified to illustrate your problem), the color of a shape is better represented by via a has-a relationship rather than with a is-a relationship.

You could implement this via passing in the desired color object to the constructor, storing a reference to it, and delegating the function call to this object. This greatly simplifies the implementation while still retaining the desired behavior. See an example here:

class Red(object):
    def x(self):
        print '#F00'

class Blue(object):
    def x(self):
        print '#00F'

class Shape(object):
    def __init__(self,color):
        self._color=color
    def x(self):
        return self._color.x()

class Circle(Shape):
    def __init__(self, color):
        Shape.__init__(self,color)
        self.x()

class Square(Shape):
    def __init__(self, color):
        Shape.__init__(self,color)
        self.x()
        self.sides = 4

red_circle = Circle(color=Red())
blue_circle = Circle(color=Blue())
blue_square = Square(color=Blue())

Edit: Fixed names of constructor arguments in sample code

Upvotes: 4

Ants Aasma
Ants Aasma

Reputation: 54882

It sounds like you are trying to use inheritance for something that it isn't meant for. If you would explain why you want to do this, maybe a more idiomatic and robust way to achieve your goals can be found.

Upvotes: 1

Cat Plus Plus
Cat Plus Plus

Reputation: 129764

If you really need it, then you could use type constructor, e.g. within a factory function (or inside __new__ method, but this is probably safer approach):

class Foo(object):
    def x(self):
        print 'y'

class Bar(object):
    def __init__(self):
        self.x()

def magic(cls, parent, *args, **kwargs):
    new = type(cls.__name__, (parent,), cls.__dict__.copy())
    return new(*args, **kwargs)

obj = magic(Bar, parent = Foo)

Upvotes: 0

unutbu
unutbu

Reputation: 879421

Perhaps what you are looking for is a class factory:

#!/usr/bin/env python
class Foo(object):
    def x(self):
        print('y')

def Bar(parent=Foo):
    class Adoptee(parent):
        def __init__(self):
            self.x()
    return Adoptee()
obj=Bar(parent=Foo)

Upvotes: 7

Related Questions