Search code examples
pythonclassoopinheritancecomposition

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


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

Solution

  • 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'