How do I design a class hierarchy for parent/child/grandparent? - python

This recursive function (search_Bases) would hopefully iterate through each base class and __init__ it. How do I refer to each class's self, without actually using self? I've tried a couple things but I can't figure it out. When I change the Child() class up to do something similar, it works. So I have no clue what to do next.
def search_Bases(child=0):
if child.__bases__:
for parent in child.__bases__:
parent.__init__(self) # <-----I can't figure out how to initiate the class
# without referring to 'self'....
search_Bases(parent)
class Female_Grandparent:
def __init__(self):
self.grandma_name = 'Grandma'
class Male_Grandparent:
def __init__(self):
self.grandpa_name = 'Grandpa'
class Female_Parent(Female_Grandparent, Male_Grandparent):
def __init__(self):
Female_Grandparent.__init__(self)
Male_Grandparent.__init__(self)
self.female_parent_name = 'Mother'
class Male_Parent(Female_Grandparent, Male_Grandparent):
def __init__(self):
Female_Grandparent.__init__(self)
Male_Grandparent.__init__(self)
self.male_parent_name = 'Father'
class Child(Female_Parent, Male_Parent):
def __init__(self):
Female_Parent.__init__(self)
Male_Parent.__init__(self)
#search_Bases(Child)
child = Child()
print child.grandma_name

I don't think you properly understand class inheritance. In Python,
class Female_Parent(Female_Grandparent, Male_Grandparent):
def __init__(self):
means that Female_Parent IS-A Male_Grandparent, which seems unlikely. What you meant to say was
class Female_Parent(object):
def __init__(self, female_grandparent, male_grandparent):
This also has problems, in that the role changes depending on who is asking - by definition, a Male_Grandparent (of his grandchildren) is also a Male_Parent (of his children) who is also a Child (of his parents).
You can boil all your classes down to
class Person(object):
def __init__(self, mother, father):
and derive further relationships from there. This gives a much simpler structure, without the point-of-view contradictions, but still results in problems evaluating further relationships because a given person's links only go "up" - a given person knows who their parents were, but can't identify their children.
You could keep a list of all your Persons and search the list each time (like a mother going around the kindergarten saying, "Are you my child? You? Are YOU my child?") but this seems very inefficient.
Instead, you can make each relationship two-way - each parent has a list of all their children and each child has a list of all their parents. It makes adding and removing people a little harder, but is well worth it.
The following is longer than I like but as short as I could make it; it should suit your needs much better!
class Person(object):
def __init__(self, name, sex, parents=None, children=None):
"""
Create a Person
"""
self.name = name
self.sex = sex # 'M' or 'F'
self.parents = set()
if parents is not None:
for p in parents:
self.add_parent(p)
self.children = set()
if children is not None:
for c in children:
self.add_child(c)
def add_parent(self, p):
self.parents.add(p)
p.children.add(self)
def add_child(self, c):
self.children.add(c)
c.parents.add(self)
def __str__(self):
return self.name
def __repr__(self):
return "Person('{}', '{}')".format(self.name, self.sex)
#
# Immediate relationships
#
# Each fn returns a set of people who fulfill the stated relationship
#
def _parent(self):
return self.parents
def _sibling(self):
return set().union(*(p.children for p in self.parents)) - set([self])
def _child(self):
return self.children
def _female(self):
if self.sex=='F':
return set([self])
else:
return set()
def _male(self):
if self.sex=='M':
return set([self])
else:
return set()
def relation(self, *rels):
"""
Find the set of all people who fulfill the stated relationship
Ex:
self.relation("parent", "siblings") # returns all aunts and uncles of self
"""
# start with the current person
ps = set([self])
for rel in rels:
# each argument is either a function or a string
if callable(rel):
# run the function against all people in the current set
# and collect the results to a new set
ps = set().union(*(rel(p) for p in ps))
else:
# recurse to evaluate the string
do = Person._relations[rel]
ps = set().union(*(p.relation(*do) for p in ps))
return ps
def print_relation(self, *rels):
print ', '.join(str(p) for p in self.relation(*rels))
#
# Extended relationships
#
# Supplies the necessary information for Person.relation() to do its job -
# Each key refers to a recursive function tree (nodes are string values, leaves are functions)
#
# (Unfortunately this table cannot be created until the Person class is finalized)
#
Person._relations = {
"parent": (Person._parent,),
"mother": (Person._parent, Person._female),
"father": (Person._parent, Person._male),
"sibling": (Person._sibling,),
"sister": (Person._sibling, Person._female),
"brother": (Person._sibling, Person._male),
"child": (Person._child,),
"daughter": (Person._child, Person._female),
"son": (Person._child, Person._male),
"grandparent": ("parent", "parent"),
"grandmother": ("parent", "mother"),
"grandfather": ("parent", "father"),
"aunt": ("parent", "sister"),
"uncle": ("parent", "brother"),
"cousin": ("parent", "sibling", "child"),
"niece": ("sibling", "daughter"),
"nephew": ("sibling", "son"),
"grandchild": ("child", "child"),
"grandson": ("child", "son"),
"granddaughter": ("child", "daughter")
}
and now, in action:
mm = Person('Grandma', 'F')
mf = Person('Grandpa', 'M')
m = Person('Mom', 'F', [mm, mf])
fm = Person('Nana', 'F')
ff = Person('Papi', 'M')
f = Person('Dad', 'M', [fm, ff])
me = Person('Me', 'M', [m, f])
s = Person('Sis', 'F', [m, f])
joe = Person('Brother-in-law', 'M')
s1 = Person('Andy', 'M', [s, joe])
s2 = Person('Betty', 'F', [s, joe])
s3 = Person('Carl', 'M', [s, joe])
me.print_relation("grandmother") # returns 'Nana, Grandma'
me.print_relation("nephew") # returns 'Andy, Carl'

Related

is there any way to get the object from its property?

I want to list the objects by its attribute, and get it with only the attributes that is in the list.
class foo:
def __init__(self,id):
self.id=id
a=foo(0)
b=foo(1)
ids=[a.id,b.id]
can I refer to a with only having ids ?
and if it is not possible this way, how can I ?
User a dictionary:
class foo:
def __init__(self,id):
self.id=id
a=foo(0)
b=foo(1)
ids={a.id:a, b.id:b}
print(ids[0])
An example without a dictionary
NOTE: This may be better achieved using a Meta-programming in Python, and your question may seem that can have an actual real world usage when creating Python Packages, Frameworks etc.
Still, in a clumsy way it does achieve this.
import random
class foo:
def __init__(self,id):
self.id=id
def create_counter():
count = 0
def internal():
nonlocal count
count += 1
return count
return internal
counter = create_counter()
def create_id():
"""Generate random id, uses a stateles Closure to keep track of counter"""
id_ = None
name = 'class_'
id_gen = str(hex(random.randrange(1000)))
id_ = name + str(counter()) + "_" + id_gen[2:]
return id_
def change_name_ref(inst_obj):
"""Change Instance Name to Instance ID"""
inst_obj.__name__ = inst_obj.id
a = foo(create_id()) # --> Assign a radnom Id
b = foo(create_id())
c = foo('class_1_15b')
change_name_ref(a)
change_name_ref(b)
change_name_ref(c)
ids = [a, b, c]
def get_instance(inst_list, target):
for idx, id_ in enumerate(inst_list):
if id_.__name__ == target:
inst = inst_list[idx]
print(f'Here The Class instance {inst}, ID: {inst.id}')
value = get_instance(ids, 'class_1_15b')
# Here The Class instance <__main__.foo object at 0x7f6988f016d0>, ID: class_1_15b

Evaluating tree without having to build an interpreter (Python)

Earlier this week I asked a generic question in a related SO community regarding constructing mathematical trees using OOP. The main takeaway was that the Composite and Interpreter patterns were the go-to patterns for this kind of application.
I then spent several days looking around online for resources on how these are constructed. I'm still convinced that I do not need to construct an entire interpreter and that a composite might be sufficient for my purposes.
From the other question I was trying to construct this tree:
Without using OOP, I'd probably do something like this:
import numpy as np
def root(B, A):
return B+A
def A(x,y,z):
return x*np.log(y)+y**z
def B(alpha, y):
return alpha*y
def alpha(x,y,w):
return x*y+w
if __name__=='__main__':
x,y,z,w = 1,2,3,4
result = root(B(alpha(x,y,w),y), A(x,y,z))
This would give a correct result of 20.693147180559947. I tried to use the composite pattern to do something similar:
class ChildElement:
'''Class representing objects at the bottom of the hierarchy tree.'''
def __init__(self, value):
self.value = value
def __repr__(self):
return "class ChildElement with value"+str(self.value)
def component_method(self):
return self.value
class CompositeElement:
'''Class representing objects at any level of the hierarchy tree except for the bottom level.
Maintains the child objects by adding and removing them from the tree structure.'''
def __init__(self, func):
self.func = func
self.children = []
def __repr__(self):
return "class Composite element"
def append_child(self, child):
'''Adds the supplied child element to the list of children elements "children".'''
self.children.append(child)
def remove_child(self, child):
'''Removes the supplied child element from the list of children elements "children".'''
self.children.remove(child)
def component_method(self):
'''WHAT TO INCLUDE HERE?'''
if __name__=='__main__':
import numpy as np
def e_func(A, B):
return A+B
def A_func(x,y,z):
return x*np.log(y)+y**z
def B_func(alpha,y):
return alpha*y
def alpha_func(x,y,w):
return x*y+w
x = ChildElement(1)
y = ChildElement(2)
z = ChildElement(3)
w = ChildElement(4)
e = CompositeElement(e_func)
A = CompositeElement(A_func)
B = CompositeElement(B_func)
alpha = CompositeElement(alpha_func)
e.children = [A, B]
A.children = [x, y, z]
B.children = [alpha, y]
alpha.children = [x, y, w]
e.component_method()
I got stuck in the last line, however. It seems that if I call the component_method at the level of composite class instance e, it will not work, since the architecture is not built to handle adding two Child or Composite objects.
How can I get this to work? What should the component_method for my CompositeElement class contain?
def component_method(self):
values = [child.component_method() for child in self.children]
return self.func(*values)
This will evaluate the child nodes and pass the values to the function of the node itself, returning the value.

Python classes and types

I think I'm misusing the concept of subclass. I'm working on a hobby project with Grids and Cells.
What I have, is the implementation of a Cell class, and its subclass HexCell which basically redefines many of the attributes/methods like so:
class Cell:
def __init__(self, row_loc, col_loc):
self.row = row_loc
self.col = col_loc
self.links = set()
self.neighbors = 4*[None]
def __repr__(self):
return f'Cell #({self.row},{self.col})'
def link(self, other, bidir = True):
self.links.add(other)
if bidir: other.links.add(self)
Then I have a subclass that is the HexGrid which follows a similar structure with new parameters.
class HexCell(Cell):
def __init__(self, r_out, th_around):
# I'm indexing Hex cells around a center cell
# instead of by rows and columns; Prefixed hex
# as they follow the hexagon, and not regular polar coordinates.
self.hex_r = r_out
self.hex_th = th_around
self.neighbors = 6*[None]
self.links = set()
def __repr__(self):
return f"HexCell #[{self.hex_r}, {self.hex_th}]"
def bind(self, other, to_dir):
to_dir = to_dir % 6
if (self.neighbors[to_dir] is None):
self.neighbors[to_dir] = other
other.neighbors[to_dir - 3] = self
# Hexagonal grids share neighbors.
other_1 = other.neighbors[to_dir - 2]
if (self.neighbors[to_dir - 1] is None) & (other_1 is not None):
self.bind(other_1, to_dir - 1)
other_5 = other.neighbors[to_dir - 4]
if (self.neighbors[to_dir - 5] is None) & (other_5 is not None):
self.bind(other_5, to_dir - 5)
In this case, the method self.link(other) is shared, but other attributes change from rectangular grid to hexagonal like the locaion from (row, col) to (hex_r, hex_th), or neighbors as a 4-list or 6-list. Thus I'd like these attributes to be dependent on a another cell-type attribute and transferred down to the subclass.
Correct use of subclassing needs to obey the following substitution principle:
If there are some objects x_1 of type T_1 and x_2 of type T_2 such that issubclass(T_2, T_1) == True, then any property that applies to x_1 must also apply for x_2.
In other words, you expect subclassing to implement new behaviours, not to change existing behaviours.
In you example, the change of coordinate system itself is a change of behaviour and thus HexCell should not inherit from Cell.
What you can do is create a base class BaseCell that encapsulates the common behaviour between Cell and HexCell and inherit from it.
class BaseCell:
def __init__(self):
self.links = set()
self.neighbors = []
def add_neighbor(self, other):
self.neighbors.append(other)
def link(self, other, bidirectional=True):
self.links.add(other)
if bidirectional:
other.link(self, bidirectional=False)
class Cell(BaseCell):
def __init__(self, row_loc, col_loc):
self.row = row_loc
self.col = col_loc
super().__init__()
def __repr__(self):
return f'Cell #({self.row},{self.col})'
class HexCell(Cell):
def __init__(self, r_out, th_around):
self.hex_r = r_out
self.hex_th = th_around
super().__init__()
def __repr__(self):
return f"HexCell #[{self.hex_r}, {self.hex_th}]"
def bind(self, other, to_dir):
...
Your Cell class is in fact not an abstract "Cell", but a square cell in two-dimensional space (has exactly 4 neighbours, has "row" and "col" position). Such cell may not be subclassed by a hex cell, because hex cell is just a different type of cell : )
As you noticed, the only common things are link() method and links attribute. If you insist on subclassing, you could create something like:
class LinkedObject():
def __init__(self):
self.links = set()
def link(self, other, bidir = True):
self.links.add(other)
if bidir: other.links.add(self)
class SquareCell(LinkedObject):
# "Cell" class here
class HexCell(LinkedObject):
# HexCell here

How can I recursively check if nested subclasses have each other as parent?

I know my question is somewhat confusing, but I'll try my best to make myself clear. Here's a sample code I've made to try to illustrate the problem:
class X:
def __init__(self, p=None):
self.parent = p
class Y(X):
def __init__(self, p=None):
X.__init__(self, p)
def recursive_check(self):
if (self.parent.parent.parent):
print(self.parent.parent.parent)
x0 = X()
x1 = X(x0)
x2 = X(x1)
y = Y(x2)
y.recursive_check()
As you can see, if you try to access self.parent.parent.parent.parent you'll get a None. The problem is that I don't know how nested the parents can be, so I can't directly test if they exist like that. I want to check recursively the depth of the nest, it could have 100 instances nesting on each other. For the sake of this example, I just need to print each parent address if it exists.
EDIT:
Considering the accepted answer, that's how I've achieved what I wanted:
class X:
def __init__(self, p=None):
self.parent = p
def recursive_check(self):
if self.parent:
print(self.parent)
self.parent.recursive_check()
class Y(X):
def __init__(self, p=None):
X.__init__(self, p)
x0 = X()
x1 = X(x0)
x2 = X(x1)
y = Y(x2)
y.recursive_check()
OBS: I'm developing a GUI for Pygame and this has to do with nested Panels (Layout Management). I don't know how nested the panels can be.
The following will do the memory address printing:
def recursive_check(self):
if self.parent:
print(id(self.parent))
self.parent.recursive_check()
If your ancestry can have circles, you'd have to collect each parent and pass that set on down the recursive calls to avoid infinite recursion.
Just use a loop, which avoids runtime errors when the stack of parents gets too large.
def recursive_check(self):
top = self
p = self.parent
while p is not None:
top = p
p = top.parent
print(top)

How to print leaves of a tree implemented as a list of subtrees in Python?

Basically I want to be able to have each node of type tree have a Data field and a list of branches. This list should contain a number of objects of type Tree.
I think I have the actual implementation of the list down, but I get strange behavior when I try using the getLeaves method. Basically it calls itself recursively and never returns, and the way that happens is somehow the second node of the tree gets it's first branch set as itself (I think).
class Tree:
"""Basic tree graph datatype"""
branches = []
def __init__(self, root):
self.root = root
def addBranch (self, addition):
"""Adds another object of type Tree as a branch"""
self.branches += [addition]
def getLeaves (self):
"""returns the leaves of a given branch. For leaves of the tree, specify root"""
print (len(self.branches))
if (len(self.branches) == 0):
return self.root
else:
branchSum = []
for b in self.branches:
branchSum += b.getLeaves()
return (branchSum)
Your 'branches' variable is a class member, not an instance member. You need to initialize the 'branches' instance variable in the constructor:
class Tree:
"""Basic tree graph datatype"""
def __init__(self, root):
self.branches = []
self.root = root
The rest of your code looks good.
Is self.root the parent of said tree? In that case, getLeaves() should return self if it has no branches (len(self.branches)==0) instead of self.root as you have it there. Also, if you do have child branches you should include self within branchSum.
Possible solution (your source code with small changes):
class Tree:
def __init__(self, data):
"""Basic tree graph datatype"""
self.data = data
self.branches = []
def addBranch (self, addition):
"""Adds another object of type Tree as a branch"""
self.branches.append(addition)
def getLeaves (self):
"""returns the leaves of a given branch. For
leaves of the tree, specify data"""
if len(self.branches) == 0:
return self.data
else:
branchSum = []
for b in self.branches:
branchSum.append(b.getLeaves())
return branchSum
## Use it
t0 = Tree("t0")
t1 = Tree("t1")
t2 = Tree("t2")
t3 = Tree("t3")
t4 = Tree("t4")
t0.addBranch(t1)
t0.addBranch(t4)
t1.addBranch(t2)
t1.addBranch(t3)
print(t0.getLeaves())
Output:
[['t2', 't3'], 't4']
Remarks:
Looks that some formatting is broken in your code.
Not really sure if this is what you want. Do you want all the leaves in one level of the list? (If so the source code has to be adapted.)

Categories

Resources