0

I would like to change the base class of a class at runtime using __new__. I looked as hard as I could, but I couldn't figure it out.

class A(object): pass

class B(object): pass

class C(B): 

    some_attribute_in_c='hello'

    def __new__(cls):
        cls.__bases=(A,) #some code here to change the base class
        return super(C,cls).__new__(A)

x=C()
x.some_attribute_in_c #return Error: x has no attribute 'some_attribute_in_c'

What is the proper code to put in __new__() so that the last line return 'hello' and x is an instance of C with base class A.

ADDED
My use case is the following.

class Pet(object):
    some_attribute_in_pet='I\'m a pet.'

class Dog(Pet):
    some_attribute_in_species='My species is dog.'

class Cat(Pet):
    some_attribute_in_species='My species is cat.'

class PetBuyer(Pet):

    def __new__(cls,desired_animal):
        animal_bought=globals[desired_animal]
        cls.__bases=(animal_bought,) #some code here to change the base class
        return super(PetBuyer,cls).__new__(animal_bought)

    def __init__(self,desired_animal):
        print self.some_attribute_in_pet
        print self.some_attribute_in_species

x = PetBuyer('Dog')

I would like the last line to print.

I'm a pet.
My species is dog.

My goal is to use __new__() in PetBuyer like a factory for the animals class. My reason for doing this are that the syntax PetBuyer('Dog') is convenient in my program.

ADDED2
My reason for doing this is the following. What I have to code is complex for me in the sense that, per see, I cannot infer the proper class design. So I resort to code my problem in anyway I can an refactor as I better understand it. However, with the situation that arise above, it will be too early for me to refactor. I have yet to understand the interaction between some components of my problem, and changing the base class at runtime will help me to do it. I will be more comfortable for refactoring afterward.

  • 1
    You don't. You name the correct bases the time you create the class. –  Apr 27 '12 at 20:34
  • a `PetBuyer` is almost certainly not a kind of `Pet`, you would normally compose the two types by adding the `Pet` instances as attributes on the `PetBuyer`. "Grandma *has-a* dog" rather than "Grandma *is-a* special kind of dog" – SingleNegationElimination Apr 27 '12 at 22:41
  • @TokenMacGuy Please see ADDED2 for my motivation for doing this. –  Apr 28 '12 at 00:01
  • `__new__` is a class method, so you should call its argument `cls` rather than `self`. – Chris Morgan Apr 28 '12 at 00:18
  • @ChrisMorgan Thanks. I'm not clear with when to use cls and self. I corrected my post. –  Apr 28 '12 at 00:59
  • @NicolasEssis-Breton: use `self` unless it's explicitly been made a `classmethod` or is `__new__`. – Chris Morgan Apr 28 '12 at 01:01
  • @delnan Thank you for pointing that the base is chosen at creation. It helped me found the solution posted below with the help of TokenMacGuy. –  Apr 29 '12 at 17:27

3 Answers3

4

When you are overriding __new__ that class is already in existence, anyway __new__ is used to create instances of a given class, what you want can be done via a metaclass, which can act like a factory for classes

e.g.

class A(object): pass

class B(object): pass

class M(type):
    def __new__(cls, clsname, bases, attribs):
        # change bases
        bases = (A,)
        return type(clsname, bases, attribs)

class C(B): 
    __metaclass__ = M    
    some_attribute_in_c='hello'

print C.__bases__
x = C()
print isinstance(x, A)
print x.some_attribute_in_c

output:

(<class '__main__.A'>,)
True
hello

After seeing OP's edit I will say forget all of the above, you don't need any metaclass, just a simple PetBuyer class which is composed of (has a) Pet , so question is why can't you just pass the pet to PetBuyer e.g.

class Pet(object):
    some_attribute_in_pet='I\'m a pet.'

class Dog(Pet):
    some_attribute_in_species='My species is dog.'

class Cat(Pet):
    some_attribute_in_species='My species is cat.'

class PetBuyer(Pet):

    def __init__(self, pet):
        self.pet = pet

        print self.pet.some_attribute_in_pet
        print self.pet.some_attribute_in_species

x = PetBuyer(Dog())

I also do not understand why you need to change the class of PetBuyer, is bad design IMO

Anurag Uniyal
  • 85,954
  • 40
  • 175
  • 219
  • I added my use case. I tried your solution, but I couldn't change your code to make my use case work. I couldn't pass any argument to the metaclass M. –  Apr 27 '12 at 22:38
  • I chose the post of TokenMacGuy as it answers more my immediate question (please see my post below). But as soon as I can, I will refactor my code per your post. Thank you. –  Apr 29 '12 at 17:25
1

Based on your new information, it sounds like you need to create types dynamically. You certainly are not obligated to create a class suite to describe those types, you can create them at run time by calling the type function directly:

def make_thingy(bases):
    new_thingy_class = type("???", bases, {})
    new_thingy_instance = new_thingy_class()
    print new_thingy_instance.some_attribute_in_pet
    print new_thingy_instance.some_attribute_in_species
    return new_thingy_instance

x = new_thingy(Dog)
SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
  • When I do `type('new_type', Dog, dict)` is it possible to merge the method of `PetBuyer` in `dict` so I can have the effect I want? –  Apr 28 '12 at 17:18
  • 1
    Just add it among the base classes, you can supply more than one.: `type('???', ((FooClass, BarClass) + tuple(other_classes), {})` – SingleNegationElimination Apr 28 '12 at 18:43
  • Thank you for your help. Please see the solution I found below with your input. –  Apr 29 '12 at 17:28
0

The immediate answer to my question as provided by TokenMacGuy and hinted by delnan is

class Pet(object):
    pet_attribute='I\'m a pet.'

class Dog(Pet):
    species_attribute='My species is dog.'

class Cat(Pet):
    species_attribute='My species is cat.'

class NewThingy(object):

    def __new__(cls,desired_base):
        x = type(desired_base.__name__+'NewThingy',
                 (NewThingy,desired_base),{})
        return super(NewThingy,cls).__new__(x,cls)

    def __init__(self,desired_base):
        print self.pet_attribute
        print self.species_attribute

x = NewThingy(Dog)

This prints

I'm a pet.
My species is dog.