python OOP - child variable updating set parent - python

I have a parent class with 3 items in it. I am trying to create a child class that when called updates a set item in the parent class.
class NOS:
def __init__(self):
self.Bike = 0
self.car = 0
self.plane = 0
class buy(NOS):
def __init__(self, mode):
NOS.__init__(self)
self.mode = mode
def buy_comp(self, value):
self.mode += value
if i called it like below
a = buy('bike')
a.buy_comp(4)
I am trying to get to a situation where bike would equal 4. The above did not work. Neither did the below where i tried to use buy as a function instead of a class.
def buy(self, mode, value):
self.mode += value
a= NOS()
a.buy('bike', 5)
Here i got the error - AttributeError: 'NOS' object has no attribute 'bike'

In the first example you posted, your child class "buy" is not actually a child class, because it is not inheriting from "NOS".
Not exactly sure what you're trying to achieve. Maybe this is helpful?
class Parent:
def __init__(self):
self.foo = "Parent Foo"
class Child(Parent):
def __init__(self):
Parent.__init__(self)
def set_foo(self, new_foo):
self.foo = new_foo
child = Child()
print(child.foo)
child.set_foo("New Foo")
print(child.foo)
Output:
Parent Foo
New Foo
EDIT - Oh, I think I get it now. Something like this maybe?
class NOS:
def __init__(self):
self.bike = 0
self.car = 0
self.plane = 0
class Buy(NOS):
def __init__(self, item_name):
NOS.__init__(self)
self.item_name = item_name
def buy_comp(self, amount):
try:
old_value = getattr(self, self.item_name)
except NameError:
# No such item exists
pass
else:
setattr(self, self.item_name, old_value + amount)
a = Buy("bike")
print(a.bike)
a.buy_comp(4)
print(a.bike)
However, I think that if you're relying on getattr and setattr, there's bound to be a better way. I have a feeling that this may be an instance of an XY problem. Can you tell us more about the actual use case? I'm sure there's a more elegant solution you could benefit from.

Related

how to access outer class properties inside the inner classes?

class Remote:
aa=7
def __init__(self):
self.name="Lenovo"
self.b=self.Battery()
print("this is outer",self.b.t)
class Battery:
def __init__(self):
self.name="Hp"
self.t="df"
self.c=self.Cover()
class Cover:
def __init__(self):
self.name="Arplastic"
c1=Remote()
I knew today about inner class but i don't know how to i access properties and methods of outer class into inner class please let me know anyone.
Change the constructor(s) of the inner class(es) to accept a parent argument and have the creating instance pass itself to it:
class Remote:
aa=7
def __init__(self):
self.name="Lenovo"
self.b=self.Battery(self)
print("this is outer",self.b.t)
class Battery:
def __init__(self,parent):
self.name="Hp"
self.t="df"
self.c=self.Cover(self)
self.parent=parent
class Cover:
def __init__(self,parent):
self.name="Arplastic"
self.parent=parent
c1=Remote()
print(c1.b.c.parent.parent.name) # prints 'Lenovo'
One approach is to make a metaclass that automatically creates self.parent attributes for nested classes. Note that there is a trade-off between readability and boilerplate here - many programmers would rather you just manually pass parents as arguments and add them to __init__ methods. This is more fun though, and there is something to be said for having less cluttered code.
Here is the code:
import inspect
def inner_class(cls):
cls.__is_inner_class__ = True
return cls
class NestedClass(type):
def __new__(metacls, name, bases, attrs, parent=None):
attrs = dict(attrs.items())
super_getattribute = attrs.get('__getattribute__', object.__getattribute__)
inner_class_cache = {}
def __getattribute__(self, attr):
val = super_getattribute(self, attr)
if inspect.isclass(val) and getattr(val, '__is_inner_class__', False):
if (self, val) not in inner_class_cache:
inner_class_cache[self, val] = NestedClass(val.__name__, val.__bases__, val.__dict__, parent=self)
return inner_class_cache[self, val]
else:
return val
attrs['__getattribute__'] = __getattribute__
attrs['parent'] = parent
return type(name, bases, attrs)
class Remote(metaclass=NestedClass):
aa = 7
def __init__(self):
self.name = "Lenovo"
self.b = self.Battery()
print("this is outer", self.b.t)
#inner_class
class Battery:
def __init__(self):
self.name = "Hp"
self.t = "df"
self.c = self.Cover()
#inner_class
class Cover:
def __init__(self):
self.name = "Arplastic"
print(f'{self.parent=}, {self.parent.parent=}')
c1 = Remote()
print(f'{c1.b.c.parent.parent is c1=}')
print(f'{isinstance(c1.b, c1.Battery)=}')
Output:
self.parent=<__main__.Battery object at 0x7f11e74936a0>, self.parent.parent=<__main__.Remote object at 0x7f11e7493730>
this is outer df
c1.b.c.parent.parent is c1=True
isinstance(c1.b, c1.Battery)=True
The way this works is by storing the parent as a class attribute (which is None by default), and replacing the __getattribute__ method so that all inner classes are replaced with NestedClasses with the parent attribute correctly filled in.
The inner_class decorator is used to mark a class as an inner class by setting the __is_inner_class__ attribute.
def inner_class(cls):
cls.__is_inner_class__ = True
return cls
This is not strictly necessary if all attributes that are classes should be treated as inner classes, but it's good practice to do something like this to prevent Bar.foo being treated as an inner class in this example:
class Foo:
pass
class Bar(metaclass=NestedClass):
foo = Foo
All the NestedClass metaclass does is take the description of the class and modify it, adding the parent attribute:
class NestedClass(type):
def __new__(metacls, name, bases, attrs, parent=None):
attrs = dict(attrs.items())
...
attrs['parent'] = parent
return type(name, bases, attrs)
...and modifying the __getattribute__ method. The __getattribute__ method is a special method that gets called every time an attribute is accessed. For example:
class Foo:
def __init__(self):
self.bar = "baz"
def __getattribute__(self, item):
return 1
foo = Foo()
# these assert statements pass because even though `foo.bar` is set to "baz" and `foo.remote` doesn't exist, accessing either of them is the same as calling `Foo.__getattribute(foo, ...)`
assert foo.bar == 1
assert foo.remote == 1
So, by modifying the __getattribute__ method, you can make accessing self.Battery return a class that has its parent attribute equal to self, and also make it into a nested class:
class NestedClass(type):
def __new__(metacls, name, bases, attrs, parent=None):
attrs = dict(attrs.items())
# get the previous __getattribute__ in case it was not the default one
super_getattribute = attrs.get('__getattribute__', object.__getattribute__)
inner_class_cache = {}
def __getattribute__(self, attr):
# get the attribute
val = super_getattribute(self, attr)
if inspect.isclass(val) and getattr(val, '__is_inner_class__', False):
# if it is an inner class, then make a new version of it using the NestedClass metaclass, setting the parent attribute
if (self, val) not in inner_class_cache:
inner_class_cache[self, val] = NestedClass(val.__name__, val.__bases__, val.__dict__, parent=self)
return inner_class_cache[self, val]
else:
return val
attrs['__getattribute__'] = __getattribute__
attrs['parent'] = parent
return type(name, bases, attrs)
Note that a cache is used to ensure that self.Battery will always return the same object every time rather than re-making the class every time it is called. This ensures that checks like isinstance(c1.b, c1.Battery) work correctly, since otherwise c1.Battery would return a different object to the one used to create c1.b, causing this to return False, when it should return True.
And that's it! You can now enjoy nested classes without boilerplate!

How do I pass the same variable(iterable) between instances of different classes(that inherits from the same parent)

I want to pass a variable(iterable )between instances of different classes. I have a structure similar with the one below.
Each class has its own module(so no globals) and needs to work in python 3 and 2.
class O:
pass
class A(O):
pass
class B(O):
def __init__(self, cache):
self.cache = cache
class B1(B):
def p(self):
self.cache.add_to_cache("32", "something something")
class B2(B):
def p(self):
self.cache.get_from_cache("52", "something else")
For B and its sub-classes I want to create a cache. All instances of this classes(B, B1, B2) to use the same cache.
To keep it simple, let's say that the cache is just a dict.
c = {}
a = A(c)
b1 = B() - needs c
b1.p()
b2 = C() - needs c
b2.p()
print(cache)
Off course the example above, is wrong because the cache is different for each instance.
The chache should be :
{
"32", "something something"
"52": "something else"
}
Another approach to this is using CacheService as an injectable Singleton service, which I consider a better practice.
Read this first for a code/syntax solution to your direct question, or continue reading for a solution with better design.
class O(object):
pass
class CacheService(object):
__instances = {}
#staticmethod
def getinstance(owner_id):
if owner_id not in CacheService.__instances:
CacheService.__instances[owner_id] = CacheService(owner_id)
return CacheService.__instances[owner_id]
def __init__(self, owner_id):
self._owner_id = owner_id
self._owner_query = CacheService.__name__ + self._owner_id
self._cache = {}
def put_in_cache(self, key, value):
self._cache[self._owner_query + str(key)] = value
def get_from_cache(self, key):
return self._cache.get(self._owner_query + str(key), "the_default")
class B(O):
def __init__(self):
self._cache = CacheService.getinstance(B.__name__)
class B1(B):
def __init__(self):
super(B1, self).__init__()
def p(self):
val1 = self._cache.get_from_cache("a")
print(val1)
class B2(B):
def __init__(self):
super(B2, self).__init__()
def p(self):
self._cache.put_in_cache("a", 2)
if __name__ == "__main__":
b1 = B1()
b2 = B2()
b2.p()
b1.p()
out:
2
This still uses a class variable, but hides it from your "everyday code", and moves it to the "infrastructure level".
I see this as cleaner, as now your class hierarchy shouldn't handle its own global variables.
To directly answer the programming question, Use class variables.
As a side note, it would be much better to use some kind of "CacheService" and inject that to the constructor, rather than use inheritance and class variables.
For this, see my other answer.
Code for using class variables follows:
class O(object):
pass
class B(O):
__cache = {} # use your cache class if you want, I am using dict just for show
def __init__(self):
pass
def _get_from_cache(self, key):
return self._cache.get(key, "default1")
def _put_in_cache(self, key, value):
self._cache[key] = value
class B1(B):
def __init__(self):
super(B1, self).__init__()
def p(self):
val1 = self._get_from_cache("a")
print(val1)
class B2(B):
def __init__(self):
super(B2, self).__init__()
def p(self):
self._put_in_cache("a", 2)
if __name__ == "__main__":
b1 = B1()
b2 = B2()
b2.p()
b1.p()
out:
2
Notice _get_from_cache and _put_in_cache are methods, but they can be #staticmethods, as they only ever access class variables, and their self isn't "really" ever being used. __cache could theoretically be accessed directly by children, but the _get_from_cache and _put_in_cache makes __cache private, and gives a protected API to it.

How to have the same updated value of a Parent class be passed down to a inner class?

I need to access the value of an attribute defined at the parent class inside an inner class, here's the code:
class main(object):
def __init__(self):
self.session_id = None
self.devices = self.Devices(self.session_id)
class Devices(object):
def __init__(self, session_id):
self.session_id = session_id
And here's how I would like to use it:
>>> m = main()
>>> m.session_id = 1
>>> m.session_id
1
>>> m.devices.session_id
>>>
My expectation is that m.devices.session_id will always have the exact same value as m.session_id. I understand that at this point when I instantiate the inner class the session_id value is passed down as None because that's how it was initiated but I'm not sure how I can keep both values the same without doing something very ugly like:
m.devices.session_id = m.session_id
outside the class code.
How can I accomplish that inside the class itself ?
The other answer works, but I think this is a better design: lose the nested class, and add a getter on the device object to lookup a backref:
class Main(object):
def __init__(self):
self.session_id = None
self.devices = Devices(main_obj=self)
class Devices(object):
def __init__(self, main_obj):
self.main_obj = main_obj
...
#property
def session_id(self):
return self.main_obj.session_id
The difference here is that you're not storing the same data twice, so they can not get out of sync - there is only one "source of truth" for the session_id (on main object).
In the earlier answer, the data is actually stored in two different namespaces and will get out of sync as easily as m.devices.session_id = 123.
You can do it like this:
class main(object):
def __init__(self):
self._session_id = None
self.devices = self.Devices(self._session_id)
#property
def session_id(self):
return self._session_id
#session_id.setter
def session_id(self, value):
self._session_id = self.devices.session_id = value
class Devices(object):
def __init__(self, session_id):
self.session_id = session_id

Why #property broke synchronizing with source object on proxy class

I want to create a proxy in Python because of function and attributes access (something like private). I create the proxy with references to functions in the source object. But I have a problem, that functions have no problem with changing attributes but property yes. Here is an example:
A working example
class A:
def __init__(self):
self.value = 1
def get_value(self):
return self.value
class Proxy:
def __init__(self, cls):
self.get_value = cls.get_value
# del cls
a = A()
p = Proxy(a)
print(a.get_value(), p.get_value())
a.value = 2
print(a.get_value(), p.get_value())
Output:
1 1
2 2
Not working:
class A:
def __init__(self):
self.value = 1
#property
def get_value(self):
return self.value
class Proxy:
def __init__(self, cls):
self.get_value = cls.get_value
# del cls
a = A()
p = Proxy(a)
print(a.get_value, p.get_value)
a.value = 2
print(a.get_value, p.get_value)
Output:
1 1
2 1
Can someone explain me where the problem is and if there is any solution for this? I could use functions, but I think #property is more Python solution. And I really want to know what is the difference. Thank you
In Proxy.__init__, you end up executing:
self.get_value = a.get_value
and very different things happen in your two examples.
In the first case, a.get_value is a method of a. So, calling p.get_value() is the same as calling a.get_value(). You get identical results.
In the second case, you have already defined a.get_value as a property, so self.get_value = a.get_value is basically self.get_value = 2, it is just an int attribute of p.

How to use super() to update a value in a specific instance of the parent?

I want to use the super function to write a string into a list of a specific instance of a parent object. Here's my code so far:
#!/usr/bin/env python
# test.py
class Master(object):
def __init__(self):
pass
class Sub1(Master):
list = []
def __init__(self):
pass
class Sub2(Sub1):
def __init__(self, name):
super(Sub2, self).list.append(name)
m = Master()
m.foo = Sub1()
m.foo.bar = Sub2('something')
m.bla = Sub1()
m.bla.blub = Sub2('else')
print(m.foo.list)
In this case the output is of course
['something', 'else']
However I want it to be just 'something'.
How can I achieve this?
I tried:
class Sub1(Master):
def __init__(self):
self.list = []
Which yields:
AttributeError: 'super' object has no attribute 'list'
Is there an easy solution?
As you have noted, if you define lst as a class attribute in Sub1, it is shared among all instances, which is not what you want. So you have to define lst as an instance attribute in Sub1 but then it has to be managed by an instance of Sub1 and not an instance of Sub2. Here is a modified code that offers what you expect:
class Master(object):
def __init__(self):
super().__init__()
class Sub1(Master):
def __init__(self):
super().__init__()
self.lst = []
def add(self, item):
self.lst.append(item.name)
class Sub2(Sub1):
def __init__(self, name):
super().__init__()
self.name = name
m = Master()
m.foo = Sub1()
m.foo.add(Sub2('something'))
m.bla = Sub1()
m.bla.add(Sub2('else'))
print(m.foo.lst)
print(m.bla.lst)
Here is the ouput:
['something'] <-- m.foo.lst
['else'] <-- m.bla.lst
Rem1: When using super() the whole class hierarchy has to be collaborative (i.e. use super() in the constructor).
Rem2: In Python3, super() is equivalent to super(classname, self)
Rem3: Don't use list as a variable name (it is a builtin type)
Rem4: My code stores only the name attribute in the list to mimic the example your gave, but I guess that in real life you would rather store instances of Sub2 in that list. To do so, simply remove the .name in the addfunction.
EDIT : Thinking a bit more about my answer, I came to another solution that may be closer to your initial attempt. Let me know which one works best for your actual problem...
class Master(object):
def __init__(self):
super().__init__()
class Sub1(Master):
def __init__(self):
super().__init__()
self.lst = []
class Sub2(Sub1):
def __init__(self, parent, name):
super().__init__()
parent.lst.append(name)
m = Master()
m.foo = Sub1()
m.foo.bar = Sub2(m.foo, 'something')
m.bla = Sub1()
m.bla.blub = Sub2(m.bla, 'else')
print(m.foo.lst)
print(m.bla.lst)
Your actual problem seems to be in the way you initialize list.
You need to assign it in __init__(), not within the class body, to avoid it being shared between all instances of the class (see Static class variables in Python).
class Sub1(Master):
def __init__(self):
self.list = []

Categories