Change object attribute in class method without explicitly naming the attribute - python

I have a class Node that I built using python parent child relationship class. This class defines the parent/children relationship needed to build a tree. In my application, I am building a balance sheet.
class Node:
_parent = None
def __init__(self, name='', attributes=None, children=None, parent=None):
self.name = name
self.children = children if children is not None else []
self.parent = parent
if children is not None:
for child in children:
child.parent = self
#property
def parent(self):
return self._parent() if self._parent is not None else None
#parent.setter
def parent(self, newparent):
oldparent = self.parent
if newparent is oldparent:
return
if oldparent is not None:
oldparent.children.remove(self)
if self not in newparent.children:
newparent.children.append(self)
self._parent = weakref.ref(newparent) if newparent is not None else None
I also define a subclass LineItem that I use to populate the nodes of my tree. When a LineItem object with a given attribute (e.g. balance) gets added to the tree, I would like that attribute to be added and rolled up the tree. For example, if node 1 has two children node 2 and 3 with each a balance of 10 and 20 respectively, then Node 1 would get a balance of 30.
class LineItem(Node):
def __init__(self, enabled=True, balance=None, native_curr=None, reported_curr='USD', *args, **kwargs):
super().__init__(*args, **kwargs)
self.enabled = enabled
self.balance = balance
self.currency = native_curr
self.report_curr = reported_curr
# propagate the balance up the balance sheet
super().addrollup(self.balance)
I created a method in the class Node that works well if the attribute is always balance.
# propagate the quantity up
def addrollup(self, quantity):
curr_parent = self.parent
if quantity is not None:
while curr_parent is not None:
if curr_parent.balance is not None:
curr_parent.balance += quantity
else:
curr_parent.balance = quantity
curr_parent = curr_parent.parent
How would I write this function if I didn't want to explicitly call out "balance"? Is it possible to write a function generic enough that it takes an argument to define what attribute should be rolled up?

Thank you for your comment Željko Jelić. After looking at setattr and getattr, I realized that they could be used to pass the name as an argument instead of using the self.attribute = syntax.
I rewrote the function as
def addrollup(self, attr_name, value):
curr_parent = self.parent
if value is not None:
while curr_parent is not None:
if getattr(curr_parent, attr_name) is not None:
setattr(curr_parent, attr_name, getattr(curr_parent, attr_name)+value)
else:
setattr(curr_parent, attr_name, value)
curr_parent = curr_parent.parent

Related

python OOP - child variable updating set parent

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.

class attributes by name

The parameter dictionary is passed to the class. Also declared class attributes. in a cycle, I assign a value to the class attributes by name, but it does not come out ... the getattr writes that there is no such attribute ... it completely fell into despondency ...
def __init__(self, parent, **kwargs):
ttk.Frame.__init__(self, master=parent)
self.kwargs = kwargs
self.__background = None
self.__foreground = None
self.__master = parent
self.__child = None
self.__text = None
self.__border = None
self.__font = None
self.__bordercolor = None
self.__sticky = None
for key in self.kwargs.keys():
if key in CustomButton.__optionsList:
self.__key = self.kwargs.get(key)
# getattr(self,'__'+key) raise AttributeError: '__'+key not in class
else:
raise ValueError('Can\'t find the parameter: {0}'.format(key))
It is because of Private Variables.
You can fix your code in two ways:
Change attribute names to have only one underscore at the beginning like _master instead of __master.
Use getattr with the name of the class: getattr(self, '_' + self.__class__.__name__ + '__' + key)

Python class iterator

I have a class of the node which contain his parent and want to create iterator on it. Here is my try:
class Node:
def __init__(self, parent=None):
self._parent = parent
def __iter__(self):
self = self.parent
def __next__(self):
if self.parent is None:
raise StopIteration
else:
self = self.parent
return self
But when I try to loop over the instance, it's never stops and returns the same value, what I did wrong?
The reason your code doesn't work is that you're trying to keep track of the current node in the iterator by assigning to self, which is just a local variable, so nothing is actually updated.
The correct way would be to extract an iterator class and keep track of the current node there:
class Node:
def __init__(self, parent=None):
self.parent = parent
def __iter__(self):
return NodeIterator(self)
class NodeIterator:
def __init__(self, node):
self.next_node = node
def __iter__(self):
return self
def __next__(self):
if self.next_node is None:
raise StopIteration
else:
current_node = self.next_node
self.next_node = self.next_node.parent
return current_node
This can be used like so:
root = Node()
inner_1 = Node(root)
leaf_1 = Node(inner_1)
inner_2 = Node(root)
inner_2_1 = Node(inner_2)
leaf_2 = Node(inner_2_1)
for node in leaf_2:
# will loop through:
# leaf_2,
# inner_2_1;
# inner_2,
# root

python subclasses have general functions shared across them

I've created a base class and a subclass. I'll be creating more subclasses, however I have some general functions that will be used across all subclasses. Is this the proper way of setting it up? I'm assuming it would be easier to add the def to the base class and then call it within each subclass. Is that possible to do or recommended?
"""
Base class for all main class objects
"""
class Node(object):
def __init__(self, name, attributes, children):
self.name = name
self.attributes = attributes if attributes is not None else {}
self.children = children if children is not None else []
"""
contains the settings for cameras
"""
class Camera(Node):
def __init__(self, name="", attributes=None, children=None, enabled=True):
super(Camera, self).__init__(name=name, attributes=attributes, children=children)
self.enabled = enabled
# defaults
add_node_attributes( nodeObject=self)
# General class related functions
# ------------------------------------------------------------------------------
""" Adds attributes to the supplied nodeObject """
def add_node_attributes(nodeObject=None):
if nodeObject:
nodeObject.attributes.update( { "test" : 5 } )
# create test object
Camera()
You should add the general methods on the base class and call them from the subclass:
class Node(object):
def __init__(self, name, attributes, children):
self.name = name
self.attributes = attributes if attributes is not None else {}
self.children = children if children is not None else []
def add_node_attributes(self):
self.attributes.update( { "test" : 5 } )
This allows you to take maximum advantage of inheritance. Your subclasses will have the method add_node_attributes available to them:
c=Camera()
c.add_node_attributes()
You can also call it from within the child class:
class Camera(Node):
def __init__(self, name="", attributes=None, children=None, enabled=True):
super(Camera, self).__init__(name=name, attributes=attributes, children=children)
self.enabled = enabled
# defaults
self.add_node_attributes()

Python object instantiation

I am very new to python and need some help with instantiating an object. The python interpreter is giving me trouble when instantiating an object of a class I defined. There are two classes, BTNode and BST (which are stored in files bst_node.py and bst.py respectively):
# file: bst_node.py
class BTNode:
"""a binary search tree node implementation"""
def ___init___(self, value):
self.value = value
self.left is None
self.right is None
self.parent is None
def ___init___(self, value, left, right, parent):
"""set the parameters to corresponding class members"""
self.value = value
self.left = left
self.right = right
self.parent = parent
def is_leaf(self):
"""check whether this node is a leaf"""
if self.left.value is None and self.right.value is None:
return True
return False
# file: bst.py
from bst_node import *
class BST:
"""a binary search tree implementation"""
def ___init___(self, value):
self.root = BTNode(value)
def insert(self, curRoot, newValue):
if curRoot.is_leaf():
if newValue < curRoot.value:
newNode = BTNode(newValue, None, None, curRoot)
curRoot.left = newNode
else:
newNode = BTNode(newValue, None, None, curRoot)
curRoot.right = newNode
else:
if newValue < curRoot.value:
self.insert(curRoot.left, newValue)
else:
self.insert(curRoot.right, newValue)
So, in the interpreter I do:
import bst as b
t1 = b.BST(8)
and I get an error which says that this constructor takes no arguments
The constructor clearly takes an argument value so what is going wrong here? How can I fix this error?
Thanks, all help is greatly appreciated!
The first issue is that you called your functions ___init___ instead of __init__. All of the 'special methods' use two underscores.
A second issue in this code is that in BTNode you redefined __init__. You can't overload functions in python. When you reclare __init__ you effectively deleted the first constructor.
A third issue is your usage of is. is is an operator that checks whether two objects are exactly the same and returns True or False. In the constructor, you have a few self.left is None is examining the value of self.left (which wasn't declared yet), and examining whether or not it is None. To set it, use = as follows:self.left = None
To fix the second and third issue you should use default argument values. For example:
def __init__(self, value, left=None, right=None, parent=None):
In addition to the number of underscores problem, you should replace
def ___init___(self, value):
self.value = value
self.left is None
self.right is None
self.parent is None
def ___init___(self, value, left, right, parent):
"""set the parameters to corresponding class members"""
self.value = value
self.left = left
self.right = right
self.parent = parent
with
def __init__(self, value, left=None, right=None, parent=None):
"""set the parameters to corresponding class members"""
self.value = value
self.left = left
self.right = right
self.parent = parent
Because as #Moshe points out, you can't overload functions, you should use default arguments insted.
Changing ___init___ to __init__ should fix it. (2 underscores vs 3)

Categories