Trouble with specific class inheritance behaviour - python

So I am trying to get my data structure set up for an automated generator I am writing for a roleplaying game and I am having trouble with some specific inheritance quirks. Here is an excerpt of the data structure.
class data():
def __init__(self):
self.races = Races()
class Races(data):
def __init__(self):
self.humans = Humans()
class Humans(Races):
def __init__(self):
self.Characteristics = {
'Brawn':2,
'Agility':2,
'Intellect':2,
'Cunning':2,
'Willpower':2,
'Presence':2
}
There is a lot more in the structure but this is just a bottom to top overview. I also know it is indented weirdly but that is strictly stack overflow.
Now I wish to have two behaviors from this object.
The ability to call any characteristic with
data.races.humans.Characteristic['brawn']
as the calling format.
And too also be able to iterate through subclasses with a generator like:
(subclass for subclass in data.races.__subclasses__())
obviously after I have instantiated the object.
Now I have tried changing the structure several times and I can get it so EITHER I can call it with dot notation, but it returns AttributeError: 'Races' object has no attribute '__subclasses__'
Or vice versa by completely separating it into a more traditional structure but then I cannot call in dot notation and this makes it very hard to keep everything organized and readable.
Can anyone suggest what I am doing wrong or a more Pythonic way to approach the problem?

Let's start in the middle. Presumably, a character of any race has the same attributes, just different values for those attributes.
class Race:
def __init__(self):
self.life = 100 # 100% healthy
class Humanoid(Race):
def __init__(self):
super().__init__()
self.legs = 2
class Insectoid(Race):
def __init__(self):
super().__init__()
self.legs = 8
class Human(Humanoid):
def __init__(self):
super().__init__()
self.brawn = 2
self.agility = 2
self.intellect = 2
self.cunning = 2,
self.willpower = 2
self.presence = 2
class Elf(Humanoid):
def __init__(self):
super.__init__()
self.brawn = 1
self.agility = 3
self.intellect = 3
self.cunning = 2
self.willpower = 3
self.presence = 1
Now, any particular character would be instantiated as the correct class:
some_elf_1 = Elf()
some_human_1 = Human()
some_human_2 = Human()
for character in [some_elf_1, some_human_1, some_human_2]:
print("Brawn: ", character.brawn)
In the preceding, it doesn't matter what the actual type of each character is; as long as you know that it is some subclass of Race (or an instance of Race itself), it will have a brawn attribute that you can access.
You data class doesn't really seem necessary without more detail.

So, While the answer given put me on the right track I realized what I needed and am just throwing in my lot for any poor souls.
Firstly - I realized what was wrong with my generator, I was calling on the initialized object instead of the class object. Objects do not have a subclasses attrib and I was mis-informed by most of the guides I read!
Secondly, I considered using a metaclass to get the iterating behavior I wanted from my objects can simply be achieved with a registry attribute that is a dict of all the initialized subclasses.
lass Races(data):
def __init__(self):
self.humans = Humans()
self.droids = Droids()
self.twileks = Twileks()
self.registry = {
'humans':self.humans,
'droids':self.droids,
'twileks':self.twileks
}
This allows me to iterate through certain values for different races after they have been initialized.
Thanks for all the great answers!

Related

Better way to pass default arguments to subclasses

Suppose I have some class which I subclass, that has some default (perhaps a flag-like) argument. What's the best way to handle passing such an argument around? I can think of doing
class Dog():
def __init__(self, noisy = False):
self.noisy = noisy
def bark(self):
if self.noisy:
print('YAP')
else:
print('yap')
class Beagle(Dog):
def __init__(self, noisy = False):
super().__init__(noisy)
dave = Beagle(noisy = True)
dave.bark()
But this uses noisy seven times, and I feel there must be a better way.
First of all, you can drop the noisy = in the instantiation of Beagle(), it's unneeded:
dave = Beagle(True)
Secondly, given your implementation, your Beagle class has no reason to exist. It does not add any functionality and does not specialize Dog in any way. If anything, possible subclasses of Dog that make sense would be:
class NoisyDog(Dog):
def __init__(self):
super().__init__(True)
class QuietDog(Dog):
def __init__(self):
super().__init__(False)
You could also keep the noisy= in the calls to super().__init__() for better readability, but again that's unneeded.
Other than that, there isn't really much else you can do. If you need a class property, you'll have to assign it to the class (self.foo = bar) and then reference it using its name...

How to initialise class attributes?

I currently have the following two ways:
class Venue:
store = Database.store()
ids = [vid for vid in store.find(Venue.id, Venue.type == "Y")]
def __init__(self):
self.a = 1
self.b = 2
OR
class Venue:
#classmethod
def set_venue_ids(cls):
store = Database.store()
cls.ids = [vid for vid in store.find(Venue.id, Venue.type == "Y")]
def __init__(self):
self.a = 1
self.b = 2
And before using/instantiating the class I would call:
Venue.set_venue_ids()
What would be the correct way of achieving this?
If it's the first way, what would I do if the instantiation of the attribute required more complex logic that could be done more simply through the use of a function?
Or is there an entirely different way to structure my code to accomplish what I'm trying to do?
From a purely technical POV, a class is an instance of its metaclass so the metaclass initializer is an obvious candidate for class attributes initialization (at least when you have anything a bit complex).
Now given the canonical lifetime of a class object (usually the whole process), I would definitly not use an attribute here - if anyone adds or removes venues from your database while your process is running, your ids attributes will get out of sync. Why don't you use a classmethod instead to make sure your data are always have up to date ?
Oh and yes, another way to construct your Venue.ids (or any other class attribute requiring non-trivial code) without having complex code at the class top-level polluthing the class namespace (did you noticed that in your first example store becomes a class attributes too, as well as vid if using Python 2.x ?) is to put the code in a plain function and call that function from within your class statement's body, ie:
def list_venue_ids():
store = Database.store()
# I assume `store.find()` returns some iterator (not a `list`)
# if it does return a list, you could just
# `return store.find(...)`.
return list(store.find(Venue.id, Venue.type == "Y"))
class Venue(object):
ids = list_venue_ids()
def __init__(self):
self.a = 1
self.b = 2

Is using the __getattr__ method as a composition pattern good Python practice?

First - please accept my apologies if this is a duplicate - I have the feeling that I have seen some sort of similar discussion before, but I really cannot find it.
My question regards object composition in Python that should look like inheritance from within each minor of the composite classes. The use case is that multiple object instances share a common core of attributes and their values (and not only a common structure, which would be a classic inheritance case instead).
I could do this with a simple attribute, i.e. by simply having each Class having one attribute called "shared_attributes", which is in itself a class storing all the values:
class CoreClass(object):
def __init__(self):
self.attr = 'asdf'
class CompClass1(object):
def __init__(self, core):
self.core_attr = core
class CompClass2(object):
def __init__(self, core):
self.core_attr = core
But this requires me to access each shared attribute through the class.core_attr attribute, which I do not want (for several reasons, one of which is that this would require an extensive rewrite of large sections of code).
So, instead I would like to use a composite pattern relying on Python's built-in __getattr__ object method, as such:
class TestClass1(object):
def __init__(self):
self.attr1 = 1
def func_a(self):
return 'a'
class CompClassBase(object):
def __init__(self, test_class):
self.comp_obj = test_class
def __getattr__(self, item):
return getattr(self.comp_obj, item)
class CompClass1(CompClassBase):
def __init__(self, test_class):
CompClassBase.__init__(self, test_class)
self.attr2 = 13
def func_b(self):
return '1b'
class CompClass2(CompClassBase):
def __init__(self, test_class):
CompClassBase.__init__(self, test_class)
self.attr2 = 23
def func_b(self):
return '2b'
if __name__ == '__main__':
tc = TestClass1()
cc1 = CompClass1(test_class=tc)
cc2 = CompClass2(test_class=tc)
print cc1.attr1
print cc1.attr2
print cc1.func_a()
print cc1.func_b()
print cc2.attr1
print cc2.attr2
print cc2.func_a()
print cc2.func_b()
Which prints, as it should, the following:
1
13
a
1b
1
23
a
2b
This pattern fits my needs perfectly, but there is something about it that wants to make me be certain about it ...
EDIT: (to respond to some comments) It is essential that this pattern will share all attributes in the shared class (given the previous objects):
cc1.attr1 = 'this is a test'
cc2.attr1 # must now be 'this is a test' as well!
2nd EDIT: I have used this pattern now for several weeks, and it works beautifully. However, I'm still hoping for some discussion, since I want to include this pattern in my standard toolkit from now on :-)
So now my question to you is simple: Is this a good practice? Does this particular Python pattern have any disadvantages? Should I be aware of some dangers here?

modification of base class through "data defined overlays"

Not sure title is correct, but it's hard to ask a question about something when you don't know what to call it.
Imagine you have a class of something like
class Creature():
def __init__( self ):
self.str = 13
self.dex = 10
...
This creature will be given other definitions that affect it. In this case, imagine a creature object was given the race of Orc. Orc would increase it's str by 2. It could be possible for a creature to get multiple of these property modifying templates.
Is there a way to organize the code that would make this elegant, pythonic, easy to maintain, and possibly data driven?
First, here's how you'd do it without making it data-driven:
class Orc(Creature):
def __init__(self):
super().__init__()
self.str += 2
self.race = 'Orc'
But what if you wanted it to be data driven? Let's assume the data are just stored in a table that maps race names to dicts, which themselves map attribute names to bonuses. Like this:
races = {
'Human': {},
'Orc': {'str': +2, 'cha': -1},
'Elf': {'str': -1, 'dex': +1, 'int': +1}
}
That may be stored in a JSON file, or a SQLite database, or anything you want; it doesn't have to be a dict literal embedded in your source code. I'm just doing it that way to keep the example simple.
We could programmatically create Human, Orc, and Elf subclasses from this, but there's probably no good reason for that. All we really need is a factory function that creates an instance of the race, and after that they all act the same. So:
def create_creature(race):
bonuses = races.get(race, {})
creature = Creature()
for attr, bonus in bonuses.items():
setattr(creature, attr, getattr(creature, attr) + bonus)
creature.race = race
return creature
The only tricky part here is that setattr line. The key is that we don't know the name of the attribute at the time we're writing the code, we only know it at runtime, but we still want to be able to get or set the value of that attribute. Normally you don't want to use setattr and getattr—but when you need to dynamically access attributes by name, that's exactly what they're there for.
However, it's worth noting that there's another alternative. How much of your code actually relies on them being attributes of the Creature class? Could they just be members of a dict?
class Creature:
def __init__(self):
self.stats = {'str': 13,
'dex': 10,
# ...
}
If so, then that ugly setattr line becomes a lot nicer:
creature.stats[attr] += bonus
On the other hand, some other code in your program might become uglier. Maybe instead of this:
if player.str + roll(20) > enemy.str + roll(20):
… you have to write:
if play.stats['str'] + roll(20) > enemy.stats['str'] + roll(20)
This tradeoff pretty much always comes up with data-driven objects: one part of your program wants to treat them like data, another part wants to treat them like objects, and you have to balance the two.
Here, Orc inherits from Creature and increases its str by 2:
class Creature():
def __init__( self ):
self.str = 13
self.dex = 10
class Orc(Creature):
def __init__( self ):
Creature.__init__( self )
self.str += 2
Here is one class that would rule them all:
class Creature():
d_str = {None:13, "Orc":15}
d_dex = {None:10, "Orc":10}
def __init__( self, race=None ):
self.race = race
self.str = self.d_str[race]
self.dex = self.d_dex[race]

Composite and Hierarchy without instance shared variables

I've written a good chunk of code that relies heavily on both inheritances and composition. Now i've run into a problem where my hierarchy classes need variables to be shared to work between each other, but that causes the composited classes to be shared too, meaning my separate instances of these classes will share values where i don't want them to. I obviously suck at explaining this with words so i wrote some code.
class First(object):
def __init__(self):
self.subvar1 = 0
self.subvar2 = 10
class Second(object):
variable3 = First()
class Third(Second):
def __init__(self):
self.variable4 = self.variable3.subvar2
Firstinstance = Third()
Secondinstance = Third()
Firstinstance.variable3.subvar1 = 50
Secondinstance.variable3.subvar2 = 0
print Firstinstance.variable3.subvar2 #<-prints 0 but i want 10
print Secondinstance.variable3.subvar1 #<-prints 50 but i want 0
Except for dumping the hierarchy system and writing one massive class where i can prevent composited classes from having their valued shared, is there any other way for me to work around this?
The problem comes from Second.variable3 being a class attribute - that is, shared by all instances of the class and it's subclasses. You want:
class Second(object):
def __init__(self):
self.variable3 = First()
class Third(Second):
def __init__(self):
super(Third, self).__init__()
self.variable4 = self.variable3.subvar2
which yields the desired results.
One way is to add
self.variable3 = First()
into the __init__ of Third, so you can keep the First and Second unchanged and prevent these values to be shared.

Categories

Resources