Python inheritance not working as I expected - python

I have a class -
class Start(object):
def __init__(self):
self.flag = False
self.my_list = []
def set_up(self):
self.flag = True
def end_set_up(self):
self.my_list = [i*2 for i in self.my_list]
And another class which inherits from this class -
class Process(Start):
def __init__(self):
super(Process, self).__init__()
def check_flag(self):
if self.flag:
self.my_list = range(1, 10)
And in the third class, I want to do some operations on my_list
class Operation(Start):
def __init__(self):
super(Operation, self).__init__()
def perform_op(self):
self.my_list = [i*2 for i in self.my_list]
Now these classes are used in a code snippet as -
start_ob = Start()
start_ob.set_up()
process_ob = Process()
process_ob.check_flag()
op_ob = Operation()
op_ob.perform_op()
My understanding of classes is not that strong. What I thought of achieving with this was -
Set up class Start()
Inherit flag from class Start() into Process() which should be True now since I called set_up() function here start_ob.set_up()
Set my_list in base class to be [1,2....9]
Inherit Start() into Operation() and modify list [1,2....9] that I created in the object Process()
But things are not moving according to my understanding. my_list is empty as set_up is False for classes Process and Operation. How do I change my code to make it work according to what my understanding is?
Edit- In the base class, there are two methods, one has to run when the object is initialised, right at the beginning. It will set a flag to True. After which another method in the same base class needs to run according to that flag

What you’re doing here:
start_ob = Start()
start_ob.set_up()
process_ob = Process()
process_ob.check_flag()
op_ob = Operation()
op_ob.perform_op()
… is creating three entirely separate objects. Each one has its own my_list. Just like you can have three different int objects and they’re all separate values, you can have three different Start objects and they’re all separate values.
What you probably wanted is:
ob = Operation()
ob.set_up()
ob.check_flag()
ob.perform_op()
Now you have a single object, which is an Operation, and therefore a Process, and therefore a Start, so you can call methods from any of those three types and they will affect your object’s value. And now you’re using inheritance.

Related

How to access class variables when class instances are stored in array

I started object-oriented programming in python and in my program, I decided to store class instances in array, but I don't know how to access class variables now.
class apixel(object):
def __init__(self):
self.posx = random.randint(0, 300)
self.posy = random.randint(0, 200)
def do_i_exist():
print("Yes i do!")
p = [apixel] * 10
for obj in p:
obj.do_i_exist() #that works
print(obj.posx) #but that does not work
The issue is that you need to actually instantiate the objects, otherwise __init__ is never called.
Try:
p = [apixel() for _ in range(10)]
Per Craig's comment below, the list comprehension calls the constructor 10 times so you get 10 independent objects.
But, the reason your apixel didn't have a self.posx is that the constructor was never called by your code. You weren't creating a list of instances of your class, but rather a list of references to the class definition.
Per DanielPryden's comment on your OP, you should either change the signature of your do_i_exist method to accept self, or annotate it as static:
# as an instance method, which requires "self":
do_i_exist(self):
...
# as a static class method that is the same for all instances:
#staticmethod
do_i_exist():
... method body contains NO references to "self"

Trouble with specific class inheritance behaviour

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!

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

Python class inheritance - Base is modified by a subclass

Let's say I have a class called Test with an attribute items. Then I create a subclass called Best. Which has a method that modifies the classes attribute items. But it even modifies Test's items and I what it to modify items only for Best.
class Test():
items = []
class Best(Test):
def method(self):
type(self).items.append("a test")
>>> Best().method()
>>> Best.items
["a test"]
>>> Test.items
["a test"] # This is what I don't want.
You have declared items as an attribute of the superclass itself, so all instances of Test and it's subclasses will share the same list. Instead declare it in Test's __ init __ method, so there is one list per instance.
In Best, just append to self.items, and only the Best instance's list will be updated.
class Test(object):
def __ init __(self)
self.items = []
class Best(Test): # Best must inherit from Test
def method(self):
self.items.append("a test")
In Python you can get what you are asking by using "private" members...
class Base(object):
def __init__(self):
self.__mine = 42 # note the double underscore
def baseMethod(self):
return self.__mine
class Derived(Base):
def __init__(self):
Base.__init__(self)
self.__mine = 99
def derivedMethod(self):
return self.__mine
obj = Derived()
print(obj.baseMethod(), obj.derivedMethod()) ## ==> 42, 99
this works because at compile time Python will replace the name __mine with _Base__mine when compiling Base and with _Derived__mine when compiling Derived.
Note however that in Python while this is possible in my experience it's not used very often. Deriving a class in many cases is just not needed thanks to "duck typing" and to delegation, something that is not possible in languages like C++ or Java.
The only possible way to do this is to create a new items on the subclass -- where else is this new list meant to come from? Also type(self) is redundant. The lookup machinery looks up attributes on the class if it cannot find the attribute on the instance. Better yet, if you don't need an instance then declare the method to be a class method.
eg.
class Test:
items = []
#classmethod
def method_test(cls):
cls.items.append('test')
class Best(Test):
items = []
#classmethod
def method_best(cls):
cls.items.append('best')
Test.method_test()
assert Test.items == ['test']
assert Best.items == []
Test.items = []
Best.method_test()
Best.method_best()
assert Test.items == []
assert Best.items == ['test', 'best']
Note that method_test works on the Best class when called from the Best class.
Your Best class is modifying Test (which I assume it's supposed to be inheriting from) because Best doesn't have its own items list. When you access Best.items, you're accessing the list where it is inherited from (i.e. from Test class). If you want a different list, you need to create it explicitly in the subclass Best:
class Best(Test):
items = [] # hide the inherited list with our own list
# ...
Your code doesn't do what you describe.
For one thing, Best is not a subclass of Test.
For another Best.method() produces
NameError: name 'self' is not defined
items is a Test class attribute.
t = Test()
t.items.append(1)
changes Test.items.
As defined B.items gives an AttributeError.
Even if I change:
class Best():
def method(self):
...
Best.method() does not run; method is an instance method. I need to use Best().method(). But then I get the items AttributeError.
class Best(Test):
def method(self):
...
does what you desribe. Best().method() modifies the Test.items - because the Test class attribute is shared with the subclass.
As shown in other answers, simply defining items for Best decouples its value from the Test class attribute
class Best(Test):
items = ['other']
...

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