zeroraptor
zeroraptor

Reputation: 3

Python Inherited Classes all return same random number?

I have a few classes with almost identical contents, so I tried two methods to copy the classes and their attributes over. The classes copy correctly, but the randint function is only invoked in the main class, so the same number is output every time. Is there any way to recalculate the random number for each class?

class a:
    exampleData = random.randint(1,100)

b = type('b', a.__bases__, dict(a.__dict__))

class c(a):
    pass

For example if a.exampleData = 50, b.exampleData and c.exampleData would be the same. Is there any way around this?

Edit -- Part of my program displays characters with random stats each time, and the class contains the stats associated with each character. The random numbers pick the stats out of a list, but the same stats are being chosen, instead of being random in each class. I may not be explaining this right, so basically:

data = [stat1,stat2,stat3,ect,,]
data[random.randint(1,3)]

Upvotes: 0

Views: 361

Answers (2)

abarnert
abarnert

Reputation: 365767

While my other answer covers the question as asked, I suspect it's all completely unnecessary to the OP's actual problem.

If you just want to create a bunch of separate objects, which each have a separate value for exampleData, you just want a bunch of instances of a single class, not a bunch of separate classes.

A class is a special kind of object that, in addition to doing all the normal object stuff, also works as a factory for other objects, which are instances of that class. You don't need a, b, and c to all be factories for for different kinds of objects, you just need them to be different objects of the same type. So:

class RandomThing:
    def __init__(self):
        self.exampleData = random.randint(1,100)

a = RandomThing()
b = RandomThing() 

… or, if you want to make sure b is the same type of thing as a but don't know what type that is:

b = type(a)()

That's as fancy as you need to get here.

See the official tutorial on Classes (or maybe search for a friendlier tutorial, because there are probably better ones out there).

Upvotes: 0

abarnert
abarnert

Reputation: 365767

When you write this:

b = type('b', a.__bases__, dict(a.__dict__))

… you're just copying a.__dict__. Since a.__dict__ is just {'exampleData': 50}, the new copy that ends up as b.__dict__ is also going to be {'exampleData': 50}.

There are many ways you could get a new random number. The simplest is to just create a new random number for b explicitly:

bdict = dict(a.__dict__)
b['exampleData'] = random.randint(1,100)
b = type('b', a.__bases__, bdict)

If you want to create a bunch of classes this way, you can wrap that up in a function:

def make_clone(proto, name):
    clonedict = dict(proto.__dict__)
    clonedict['exampleData'] = random.randint(1,100)
    return type(name, proto.__bases__, clonedict)

You can make that factory function more complicated if you want to be (see namedtuple for a pretty extreme example).


You could wrap that behavior up in a decorator:

def randomize(cls):
    cls.exampleData = random.randint(1,100)

@randomize
class a:
    pass

b = randomize(type('b', a.__bases__, dict(a.__dict__)))

Notice that I had to call the decorator with normal function-call syntax here, because there's no declaration statement to attach an @decorator to.


Or you can wrap it up in a metaclass:

class RandomMeta(type):
    def __new__(mcls, name, bases, namespace):
        d = dict(namespace)
        d['exampleData'] = random.randint(1,100)
        return type.__new__(mcls, name, bases, d)

class a(metaclass=RandomMeta):
    pass

b = type(a)('b', a.__bases__, dict(a.__dict__))

Notice that we have to call type(a) here, the same way a class definition statement does, not the base metaclass type.

Also notice that I'm not taking **kwds in the __new__ method, and I'm calling type.__new__ directly. This means that if you try to use RandomMeta together with another metaclass (besides type), you should get an immediate TypeError, rather than something that may or may not be correct.


Meanwhile, I have a suspicion that what you're really trying to do here is build a prototype-based inheritance system, a la Self or JavaScript on top of Python's class-based system. While you can do that by using a special Prototype metaclass and a bunch of class objects, it's a whole lot simpler to just have a Prototype class and a bunch of instance objects. The only advantage to the metaclass approach is that you can use class statements (misleadingly, but conveniently) to clone prototypes, and you're explicitly not doing that here.

Upvotes: 2

Related Questions