family ties

The tests from everything is an object show that in Python everything inherits. This allows me to make new objects that get their magic powers from other objects.

Making new objects can be easier with Inheritance because I do not have to write things that have already been written again, I can inherit them instead and change the new objects to do what I want.

It can also be more complicated because I can make new instances to inherit from one object and customize it for what I need instead of making new objects that require me to keep track of Python’s Method Resolution Order.


what is Python’s Method Resolution Order?

When an instance is made, Python calls every __init__ method of the parent and its parent going through every ancestor until it gets to the last one that is needed to make the instance.


how to make an object with a parent

To use inheritance I put the “parent” in parentheses when I make the new object (the child) to make the relationship.

class Child(Parent):

    attribute = SOMETHING

    def method():
        the body of the method
        ...

questions about family ties


preview

I have these tests by the end of the chapter

  1import src.family_ties
  2import unittest
  3
  4
  5class TestFamilyTies(unittest.TestCase):
  6
  7    def test_making_an_object_w_inheritance(self):
  8        person_object = src.person.Person
  9        doe_class = src.family_ties.Doe
 10        doe_instance = doe_class('the_first')
 11
 12        self.assertNotIsInstance(
 13            doe_class, person_object
 14        )
 15
 16        self.assertNotIsInstance(
 17            doe_class, doe_class
 18        )
 19
 20        self.assertIsSubclass(
 21            doe_class, person_object
 22        )
 23
 24        self.assertIsInstance(
 25            doe_instance, person_object
 26        )
 27
 28        self.assertEqual(
 29            dir(doe_class), dir(person_object)
 30        )
 31
 32    def test_objects_w_one_parent(self):
 33        doe = src.family_ties.Doe('the_first')
 34        self.assertEqual(doe.last_name, 'doe')
 35
 36        joe = src.family_ties.Blow('joe')
 37        self.assertEqual(joe.last_name, 'blow')
 38
 39        blow = src.person.Person('joe', last_name='blow')
 40        self.assertEqual(blow.last_name, joe.last_name)
 41
 42        jane = src.person.Person('jane')
 43        self.assertEqual(jane.last_name, doe.last_name)
 44
 45        john = src.family_ties.Smith('john')
 46        self.assertEqual(john.last_name, 'smith')
 47
 48        smith = src.person.Person('john', 'smith')
 49        self.assertEqual(smith.last_name, john.last_name)
 50
 51    def test_objects_w_multiple_parents(self):
 52        joe = src.family_ties.Joe()
 53        self.assertEqual(joe.first_name, 'joe')
 54        self.assertEqual(joe.last_name, 'blow')
 55        self.assertEqual(joe.eye_color, 'blue')
 56        self.assertIsSubclass(
 57            src.family_ties.Joe, src.family_ties.Blow
 58        )
 59
 60        jane = src.family_ties.Jane()
 61        self.assertEqual(jane.first_name, 'jane')
 62        self.assertEqual(jane.last_name, 'doe')
 63        self.assertEqual(jane.eye_color, 'green')
 64        self.assertIsSubclass(
 65            src.family_ties.Jane, src.family_ties.Doe
 66        )
 67
 68        mary = src.family_ties.Mary()
 69        self.assertEqual(mary.first_name, 'mary')
 70        self.assertEqual(mary.last_name, jane.last_name)
 71        self.assertEqual(mary.eye_color, joe.eye_color)
 72        self.assertIsSubclass(
 73            src.family_ties.Mary, src.family_ties.Jane
 74        )
 75        self.assertIsSubclass(
 76            src.family_ties.Mary, src.family_ties.Joe
 77        )
 78
 79        john = src.family_ties.John()
 80        self.assertEqual(john.first_name, 'john')
 81        self.assertEqual(john.last_name, 'smith')
 82        self.assertEqual(john.eye_color, 'brown')
 83        self.assertIsSubclass(
 84            src.family_ties.John, src.family_ties.Smith
 85        )
 86
 87        lil = src.family_ties.Lil()
 88        self.assertEqual(lil.first_name, 'lil')
 89        self.assertEqual(lil.last_name, john.last_name)
 90        self.assertEqual(lil.eye_color, mary.eye_color)
 91        self.assertIsSubclass(
 92            src.family_ties.Lil, src.family_ties.John
 93        )
 94        self.assertIsSubclass(
 95            src.family_ties.Lil, src.family_ties.Mary
 96        )
 97
 98
 99# Exceptions seen
100# AssertionError
101# NameError
102# AttributeError
103# ModuleNotFoundError
104# TypeError

requirements


open the project

  • I change directory to the person folder

    terminal
    cd person
    

    the terminal shows I am in the person folder

    .../pumping_python/person
    
  • I make a new file in the tests folder named test_family_ties.py

    terminal
    touch tests/test_family_ties.py
    
  • I make a new file in the src folder named family_ties.py

    terminal
    touch src/family_ties.py
    
  • I open test_family_ties.py

  • I add the first failing test to test_family_ties.py

    1import unittest
    2
    3
    4class TestFamilyTies(unittest.TestCase):
    5
    6    def test_failure(self):
    7        self.assertFalse(True)
    
  • I go back to the terminal to add the new files and folders to git for tracking

    terminal
    git add .
    

    the terminal goes back to the command line.

  • I use pytest-watcher to run the tests

    terminal
    uv run pytest-watcher . --now --patterns "**/*.py"
    

    the terminal is my friend, and shows AssertionError

    terminal
    ============================ FAILURES ==========================
    _________________ TestFamilyTies.test_failure __________________
    
    self = <tests.test_family_ties.TestFamilyTies testMethod=test_failure>
    
        def test_failure(self):
    >       self.assertFalse(True)
    E       AssertionError: True is not false
    
    tests/test_family_ties.py:7: AssertionError
    =================== short test summary info ====================
    FAILED tests/test_family_ties.py::TestFamilyTies::test_failure - AssertionError: True is not false
    ================= 1 failed, 6 passed in X.YZs ==================
    
  • I add AssertionError to the list of Exceptions seen, in test_functions.py

  • I change True to False in the assertion

    the test passes.


test_making_an_object_w_inheritance

I know from test_making_an_object_w_object that I can make objects with inheritance by stating the parent object and that an instance (a copy) and a subclass (child) are different.


RED: make it fail



GREEN: make it pass



REFACTOR: make it better


  • I add a call to assertIsSubclass

    the terminal is my friend, and shows AssertionError

    AssertionError:
        <class 'src.family_ties.Doe'> is not a
        subclass of <class 'src.person.Person'>
    

    because Doe is not a child of Person, yet.

  • I change the parent of Doe from object to Person in family_ties.py

    1# class Doe(object): pass
    2class Doe(person.Person): pass
    

    the terminal is my friend, and shows NameError

    NameError: name 'person' is not defined
    

    because there is no definition for person in family_ties.py

  • I add an import statement at the top of family_ties.py

    1import person
    2
    3
    4# class Doe(object): pass
    5class Doe(person.Person): pass
    

    the terminal is my friend, and shows ModuleNotFoundError

    E   ModuleNotFoundError: No module named 'person'
    

    because Python cannot find src/person/__init__.py in the main project folder (the parent of src and tests) where I run the tests from, so it cannot import the Module.

  • I add ModuleNotFoundError to the list of Exceptions seen, in test_family_ties.py

  • I add the path of src/person/__init__.py from the main project folder (the parent of src and tests) to the import statement, in family_ties.py

    1# import person
    2import src.person
    3
    4
    5# class Doe(object): pass
    6class Doe(person.Person): pass
    

    the terminal is my friend, it goes back to NameError

    NameError: name 'person' is not defined
    

    because there is no definition for person in this file.

  • I add src. to the parent of Doe

    • import src.person brings in an object for the src/person/__init__.py module from the src folder so I can use it in family_ties.py.

    • I have to use src.person.Person in family_ties.py because I am testing from the root folder of the project (the parent folder of src and tests).

    • The test needs to know where src/person/__init__.py is in relation to where I ran the tests from.

    • This is a problem because if family_ties.py is run from inside src the import statement will not be able to find src.person from inside src. Same thing if I run the tests from inside tests (a problem for another time).

    • The test passes because Doe is now a child (subclass) of Person.


more about instances vs subobjects



what happens when a child calls the parent?


  • I add a call to the assertIsInstance method, this time with an instance of Doe

    the terminal is my friend, and shows TypeError

    TypeError: Person.__init__() missing 1
               required positional argument: 'first_name'
    

    I called Doe to make an instance. How did Person.__init__ get called?

  • Here is what is happens when src.class.Doe() runs

    src.family_ties.Doe()
    Doe # has no __init__
    # call the parent of Doe (Person)
    Person.__init__()
    

    which raises TypeError since the __init__ method of the Person object takes one required argument for first_name.

  • I add TypeError to the list of Exceptions seen


how to call the parent from the child


  • I add the super built-in function to Doe to call the parent __init__ method (Parent.__init__) directly from Doe, in family_ties.py

    • the super built-in function calls the __init__ method of the parent object

    • super() is the parent - “super class” for parent, “subclass” for child

    • super is Person in this case

    • super().__init__() is Person.__init__() in this case

    • the terminal still shows TypeError

      TypeError: Person.__init__() missing 1
                 required positional argument: 'first_name'
      

      because this is what happens now when src.class.Doe() runs

      src.family_ties.Doe()
      Doe.__init__()
          super().__init__()
      # super is the parent (Person)
      Person.__init__()
      

      which raises TypeError since the __init__ method of the Person object takes one required argument for first_name.

  • I add a value for first_name to the call to src.family_ties.Doe() in test_making_an_object_w_inheritance in test_family_ties.py

    the terminal is my friend, and shows TypeError

    TypeError: Doe.__init__() takes
               1 positional argument but 2 were given
    

    because this happens when src.family_ties.Doe('the_first') runs

    src.family_ties.Doe('the_first')
    Doe.__init__('the_first')
    
  • I add a parameter for first_name to the __init__ method of Doe in family_ties.py

    the terminal is my friend, and shows TypeError

    TypeError: Person.__init__() missing 1
               required positional argument: 'first_name'
    

    because this happens when src.family_ties.Doe('the_first') runs

    src.family_ties.Doe('the_first')
    Doe.__init__('the_first')
        super().__init__() # call the parent
    Person.__init__()
    
  • I add the required parameter to super().__init__() in Doe

    the test passes because

    • an instance (a copy) of Doe is an an instance (a copy) of Person

    • Person is the parent of Doe

    • the test shows that this happens when src.family_ties.Doe('the_first') runs

      src.family_ties.Doe('the_first')
      Doe.__init__('the_first')
          super().__init__('the_first')
      Person.__init__('the_first')
      
  • I add a test for the attributes and methods of the Doe object

    the terminal is my friend, and shows AssertionError

    AssertionError: Lists differ:
        ['__class__', '__delattr__', '__dict__',
         '[370 chars]llo']
     != []
    
  • I change the expectation to the attributes and methods of the Person object

    the test passes because Doe has the same attributes and methods as Person because Doe is a child of Parent.

  • I add a variable

  • I use the variable to remove repetition of src.person.Person from the test

    still green.

  • I add a variable for src.family_ties.Doe('the_first')

  • I use the new variable

    green, because doe_class() and src.objects.Doe() are the same since doe_class = src.objects.Doe.

  • I remove the commented lines

  • I remove the commented lines from family_ties.py

    1import src.person
    2
    3
    4class Doe(src.person.Person):
    5
    6    def __init__(self, first_name):
    7        super().__init__(first_name)
    
  • I add a git commit message in the other terminal

    terminal
    git commit -am \
    'add test_making_an_object_w_inheritance'
    

I can make a class with inheritance.


test_objects_w_one_parent

I want to test how the attributes of objects are set if they have only one parent (super object).


RED: make it fail



GREEN: make it pass


I change the expectation

32    def test_objects_w_one_parent(self):
33        doe = src.family_ties.Doe('the_first')
34        # self.assertEqual(doe.last_name, '')
35        self.assertEqual(doe.last_name, 'doe')
36
37
38# Exceptions seen

the test passes because this happens when doe = src.family_ties.Doe('the_first') runs

doe = src.family_ties.Doe('the_first')
      Doe.__init__('the_first')
          super().__init__(first_name)
      Person.__init__('the_first')
          Person.__init__('the_first', last_name='the_first')
          self.last_name = 'the_first' # use the default value

the value for doe.last_name is doe because a method uses the default value for a parameter when it is called without the parameter.


REFACTOR: make it better


  • I add another assertion

    the terminal is my friend, and shows AttributeError

    AttributeError: module 'src.family_ties' has no attribute 'Blow'
    

    because there is no definition for Blow in family_ties.py

  • I add a new object definition to family_ties.py

    the terminal is my friend, and shows AssertionError

    AssertionError: 'doe' != 'blow'
    

    because this happens when joe = src.family_ties.Blow('joe') runs

    joe = src.family_ties.Blow('joe')
          Blow # has no __init__
          # call the parent of Blow (Person)
          Person.__init__('joe')
              Person.__init__('joe', last_name='doe')
              self.last_name = 'doe' # use the default value
    
  • I add an object attribute for last_name in the Blow object in family_ties.py

    the terminal does not feel like my friend, it still shows AssertionError because this happens when joe = src.family_ties.Blow('joe') runs

    joe = src.family_ties.Blow('joe')
          Blow # has no __init__
          # call the parent of Blow (Person)
              self.last_name = 'blow'
          Person.__init__('joe')
              Person.__init__('joe', last_name='doe')
              self.last_name = 'doe' # use the default value
    

    the value for last_name does not get sent to the parent (Person.__init__)

  • I add the __init__ method to customize the last name

    the terminal is my friend, and shows TypeError

    TypeError: Blow.__init__() takes
               1 positional argument but 2 were given
    

    because this happens when joe = src.family_ties.Blow('joe') runs

    joe = src.family_ties.Blow('joe')
          Blow.__init__('joe')
    
  • I add first_name to the parentheses of the __init__ method definition

    the test passes because this happens when joe = src.family_ties.Blow('joe') runs

    joe = src.family_ties.Blow('joe')
          Blow.__init__('joe')
              self.last_name = 'joe'
    

    I can make objects that are related and have their own defaults. In this test

  • I remove the commented lines

  • In this case there is a simpler way to make joe and doe. I could pass the values to the Person object directly, since all the Blow object does is customize the last_name attribute, there is nothing special about it or the Doe object. I add an assertion to test_objects_w_one_parent in test_family_ties.py

    the terminal is my friend, and shows AssertionError

    AssertionError: 'doe' != 'blow'
    
  • I add last_name='blow' to the call

    the test passes. I can make an instance and change the values of its attributes without making a new object.

  • I add an assertion for jane

    the terminal is my friend, and shows AssertionError

    AssertionError: 'doe' != 'blow'
    
  • I change the expectation

    the test passes.

  • I add an assertion for john

    the terminal shows AttributeError

    AttributeError: module 'src.family_ties'
                    has no attribute 'Smith'
    
  • I add an object definition for Smith to family_ties.py

    the terminal is my friend, and shows AssertionError

    AssertionError: 'doe' != 'smith'
    

    because this happens when john = src.family_ties.Smith('john') runs

    john = src.family_ties.Smith('john')
           Smith # has no __init__
           # call the parent of Smith (Person)
           Person.__init__('john')
               Person.__init__('john', last_name='doe')
               self.last_name = 'doe' # use the default value
    
  • I add the __init__ method to Smith

    16# class Smith(src.person.Person): pass
    17class Smith(src.person.Person):
    18
    19    def __init__(self):
    20        self.last_name = 'smith'
    

    the terminal shows TypeError

    TypeError: Smith.__init__() takes 1
               positional argument but 2 were given
    

    because this happens when john = src.family_ties.Smith('john') runs

    john = src.family_ties.Smith('john')
           Smith.__init__('john')
    
  • I add first_name to the parentheses of the __init__ method

    the test passes because this happens when john = src.family_ties.Smith('john') runs

    john = src.family_ties.Smith('john')
           Smith.__init__('john')
               self.last_name = 'smith'
    
  • I remove the commented lines

    the test passes.

  • I add another assertion to test_objects_w_one_parent in test_family_ties.py

    the terminal is my friend, and shows AssertionError

    AssertionError: 'smith' != 'doe'
    
  • I change the expectation

    the test passes.

  • I remove the commented lines

    it will use the default value for last_name if no value is given because a function uses the default value for the parameter because it is called without the parameter.

  • I add a git commit message in the other terminal

    terminal
    git commit -am 'add test_objects_w_one_parent'
    

I can customize child objects with the __init__ method.


test_objects_w_multiple_parents

Can an object have more than one parent? How are the attributes set if they have more than one parent (super object)?


RED: make it fail


  • I go back to the terminal where the tests are running

  • I add a test with an assertion for jane

    the terminal is my friend, and shows AttributeError

    AttributeError: module 'src.family_ties'
                    has no attribute 'Jane'
    

GREEN: make it pass


  • I add an object definition for Jane to family_ties.py

    the terminal shows TypeError

    TypeError: Person.__init__() missing 1
               required positional argument: 'first_name'
    

    because this happens when jane = src.family_ties.Jane() runs

    jane = src.family_ties.Jane()
           Jane # has no __init__
           # call the parent of Jane (Person)
           Person.__init__()
    

    which raises TypeError since the __init__ method of Person requires one positional argument (first_name) and it got called with zero

  • I add the __init__ method to the definition of Jane

    the terminal is my friend, and shows AttributeError

    AttributeError: 'Jane' object has no attribute 'first_name'
    
  • I add a value for first_name to the definition

the test passes.


REFACTOR: make it better


  • I add an assertion for the value of the last_name attribute of jane to test_objects_w_multiple_parents in test_family_ties.py

    the terminal is my friend, and shows AttributeError

    AttributeError:
        'Jane' object has no attribute 'last_name'.
        Did you mean: 'first_name'?
    
  • I add a value for last_name to Jane in family_ties.py

    the test passes. This is a repetition because

  • I add an assertion to test_objects_w_multiple_parents to make sure Jane is a Doe, in test_family_ties.py

    the terminal is my friend, and shows AssertionError

    AssertionError:
        <class 'src.family_ties.Jane'> is not
        a subclass of <class 'src.family_ties.Doe'>
    
  • I change the parent of Jane in family_ties.py

    the test passes.

  • I add a call to the super built-in function to use to remove the repetition of last_name

    the terminal shows TypeError

    TypeError: Doe.__init__() missing 1
               required positional argument: 'first_name'
    

    because this happens when jane = src.family_ties.Jane() runs

    jane = src.family_ties.Jane()
           Jane.__init__()
               super().__init__()
           Doe.__init__()
    

    which raises TypeError since the __init__ method of Doe requires two positional arguments (self and first_name) and it got called with one (self)

  • I add jane as the value for first_name in the call to the parent

    the test is green again because this happens when jane = src.family_ties.Jane() runs

    jane = src.family_ties.Jane()
           Jane.__init__()
               super().__init__('jane')
           Doe.__init__('jane')
               super().__init__(first_name)
           Person.__init__('jane')
               Person.__init__('jane', last_name='doe')
               self.first_name = 'jane'
               self.last_name = 'doe' # use the default value
    

    a method uses the default value for the parameter because it is called without the parameter.


  • I add an assertion for mary, another instance of Jane to test_objects_w_multiple_parents in test_family_ties.py

    the terminal is my friend, and shows TypeError

    TypeError: Jane.__init__() takes 1
               positional argument but 2 were given
    

    because this happens when mary = src.family_ties.Jane('mary') runs

    mary = src.family_ties.Jane('mary')
           Jane.__init__('mary')
    

    which raises TypeError since the __init__ method takes one positional argument (self) and it was called with two (self and 'mary')

  • I add first_name to the parentheses for the __init__ method of Jane in family_ties.py

    the terminal is my friend, and shows TypeError

    TypeError: Jane.__init__() missing 1
               required positional argument: 'first_name'
    

    I broke the assertion for jane because this happens when jane = src.family_ties.Jane() runs

    jane = src.family_ties.Jane()
           Jane.__init__()
    

    which raises TypeError since the __init__ method takes two required positional arguments (self and first_name) and the call only sends one (self).

  • I add a default value to make first_name optional

    the terminal is my friend, and shows AssertionError

    AssertionError: 'jane' != 'mary'
    

    because this happens when mary = src.family_ties.Jane('mary') runs

    mary = src.family_ties.Jane('mary')
           Jane.__init__('mary')
               super().__init__('jane')
           Doe.__init__('jane')
               super().__init__(first_name)
           Person.__init__('jane')
               Person.__init__('jane', last_name='doe')
               self.first_name = 'jane'
               self.last_name = 'doe'
    

    The parent of Jane (Doe) gets called with a different value for the first_name parameter.

  • I change the call to the super built-in function to use the name instead of a fixed value

    the test passes.

  • I remove the commented lines and return None

  • I add an assertion that will fail, for the last name of mary in test_objects_w_multiple_parents in test_family_ties.py

    the terminal is my friend, and shows AssertionError

    AssertionError: 'doe' != 'mary'
    
  • I change the expectation to match reality

    the test passes because mary and jane are instances of Jane


  • I add an assertion for joe

    the terminal is my friend, and shows AttributeError

    AttributeError:
        module 'src.family_ties' has no attribute 'Joe'.
        Did you mean: 'Doe'?
    
  • I add an object definition for the Joe object to family_ties.py

    the terminal is my friend, and shows TypeError

    TypeError: Person.__init__() missing 1
               required positional argument: 'first_name'
    

    because this happens when joe = src.family_ties.Joe() runs

    joe = src.family_ties.Joe()
          Person.__init__()
    
  • I add the __init__ method to Joe

    the terminal is my friend, and shows AttributeError

    AttributeError: 'Joe' object has no attribute 'first_name'
    
  • I add self.first_name to Joe with a value

    the test passes.

  • I add an assertion to test_objects_w_multiple_parents to make sure that joe is a Blow, in test_family_ties.py

    the terminal is my friend, and shows AttributeError

    AttributeError:
        'Joe' object has no attribute 'last_name'.
        Did you mean: 'first_name'?
    
  • I add last_name to the __init__ method of Joe in family_ties.py

    the test passes. I cheated, which means I need a better test.

  • I add assertIsSubclass to test_objects_w_multiple_parents to make sure Joe is a child (subclass) of Blow, in test_family_ties.py

    the terminal is my friend, and shows AssertionError

    AssertionError:
        <class 'src.family_ties.Joe'> is
        not a subclass of <class 'src.family_ties.Blow'>
    
  • I change the parent of Joe to Blow in family_ties.py

    the test passes.

  • I no longer need self.last_name = 'blow' because it is a repetition. I add a call to the super built-in function

    the terminal is my friend, and shows AttributeError

    AttributeError:
        'Joe' object has no attribute 'first_name'.
        Did you mean: 'last_name'?
    

    because this happens when joe = src.family_ties.Joe() runs

    joe = src.family_ties.Joe()
          Joe.__init__()
              super().__init__('joe')
          Blow.__init__('joe')
              self.last_name = 'blow'
    

    there is no assignment of a value to the first_name attribute in Blow.

  • I add self.first_name to Blow

    the test passes because this happens when joe = src.family_ties.Joe() runs

    joe = src.family_ties.Joe()
          Joe.__init__()
              super().__init__('joe')
          Blow.__init__('joe')
              self.first_name = 'joe'
              self.last_name = 'blow'
    
  • I remove the commented lines and return None, from Joe


  • I change mary to be an instance of Mary, a child (subclass) of Jane

    the terminal is my friend, and shows AttributeError

    AttributeError: module 'src.family_ties'
                    has no attribute 'Mary'
    
  • I add an object definition for Mary to family_ties.py

    the terminal is my friend, and shows AssertionError

    AssertionError: 'jane' != 'mary'
    

    because this happens when mary = src.family_ties.Mary() runs

    mary = src.family_ties.Mary()
           Mary # has no __init__
           # call the parent of Mary (Jane)
           Jane.__init__()
               # use the default value
               Jane.__init__(first_name='jane')
               super().__init__(first_name)
           Doe.__init__('jane')
               super().__init__(first_name)
           Person.__init__('jane')
               Person.__init__('jane', last_name='doe')
               self.first_name = 'jane'
               self.last_name = 'doe' # use the default value
    
  • I add the __init__ method to Mary

    the terminal shows AttributeError

    AttributeError:
        'Mary' object has no attribute 'last_name'.
        Did you mean: 'first_name'?
    

    because this happens when mary = src.family_ties.Mary() runs

    mary = src.family_ties.Mary()
           Mary.__init__()
               self.first_name = 'mary'
    
  • I add a value for last_name

    the test passes. This is a repetition because

    • Mary is a Jane

    • Jane is a Doe

    • Doe is a Person

    • the default value for last_name in Person is 'doe'

  • I add a call to the super built-in function to remove the repetition

    the test is still green because this happens when mary = src.family_ties.Mary() runs

    mary = src.family_ties.Mary()
           Mary.__init__()
               super().__init__('mary')
           Jane.__init__('mary')
                super().__init__(first_name)
           Doe.__init__('mary')
                super().__init__(first_name)
           Person.__init__('mary')
                Person.__init__('mary', last_name='doe')
                self.first_name = 'mary'
                self.last_name = 'doe' # use the default value
    
  • I add a call to the assertNotIsSubobject method to test_objects_w_multiple_parents in test_family_ties.py

    the terminal is my friend, and shows AssertionError

    AssertionError:
        <class 'src.family_ties.Mary'> is
        a subclass of <class 'src.family_ties.Jane'>
    
  • I change assertNotIsSubclass to the assertIsSubobject method

    the test passes.


what happens when a child has more than one parent?


  • I add an assertion to test if I can make Joe and Jane both be parents of Mary?

    the terminal is my friend, and shows AssertionError

    AssertionError:
        <class 'src.family_ties.Mary'> is not
        a subclass of <class 'src.family_ties.Joe'>
    

    because Mary is not a child (subclass) of Joe.

  • I add Joe as a parent of Mary in family_ties.py

    the terminal is my friend, and shows TypeError

    TypeError: Joe.__init__() takes 1
               positional argument but 2 were
    

    because this happens when mary = src.family_ties.Mary() runs

    mary = src.family_ties.Mary()
           Mary.__init__()
               super().__init__('mary')
           Jane.__init__('mary')
               super().__init__(first_name)
           Doe.__init__('mary')
               super().__init__(first_name)
           # call the next parent of Mary
           Joe.__init__('mary')
    
  • I change the __init__ method of Joe to take a first_name argument

    the terminal is my friend, and shows TypeError

    TypeError: Joe.__init__() missing 1
               required positional argument: 'first_name'
    

    I broke the joe = src.family_ties.Joe() call because the __init__ method now has two required positional arguments (self and first_name) and it was called with one (self)

  • I add a default value to make first_name optional

    the terminal is my friend, and shows AssertionError

    AssertionError: 'joe' != 'mary'
    

    for mary.first_name because this happens when mary = src.family_ties.Mary() runs

    mary = src.family_ties.Mary()
           Mary.__init__()
               super().__init__('mary')
           Jane.__init__('mary')
               super().__init__(first_name)
           Doe.__init__('mary')
               super().__init__(first_name)
           Joe.__init__('mary')
               super().__init__('joe') # the problem
           Blow.__init__('joe')
               self.first_name = 'joe'
               self.last_name = 'blow'
    
  • I use the parameter name in the call to super instead of a fixed value in Joe

    the terminal shows AssertionError

    AssertionError: 'blow' != 'doe'
    
  • I change the expectation of the assertion in test_objects_w_multiple_parents for the last name of mary in test_family_ties.py

    the test passes.


  • I change the order of the parents of Mary to see what it does to the value of last_name, in family_ties.py

    the test is still green because this happens when mary = src.family_ties.Mary() runs

    mary = src.family_ties.Mary()
           Mary.__init__()
               super().__init__('mary')
           Joe.__init__('mary')
               super().__init__(first_name)
           Blow.__init__('mary')
               self.first_name = 'mary'
               self.last_name = 'blow'
    

    the __init__ method of Jane did not get called even though it is a parent of Mary

  • I add an assertion to show this in test_objects_w_multiple_parents in test_family_ties.py

    the terminal is my friend, and shows AssertionError

    AttributeError: 'Jane' object has no attribute 'eye_color'
    
  • I add an object attribute to Jane in family_ties.py

    the test passes.

  • I add an assertion for the eye_color attribute of Mary in test_objects_w_multiple_parents in test_family_ties.py

    the terminal is my friend, and shows AttributeError

    AttributeError: 'Mary' object has no attribute 'eye_color'
    

    because the __init__ method of Jane did not get called.

  • I change the order of the parents of Mary from (Joe, Jane) back to (Jane, Joe) in family_ties.py

    the test passes because this happens when mary = src.family_ties.Mary() runs

    mary = src.family_ties.Mary()
           Mary.__init__()
               super().__init__('mary')
           Jane.__init__('mary')
               super().__init__(first_name)
               self.eye_color = 'green'
           Doe.__init__('mary')
               super().__init__(first_name)
           Joe.__init__('mary')
               super().__init__('joe')
           Blow.__init__('mary')
               self.first_name = 'mary'
               self.last_name = 'blow'
    

    The order of the parents matters.

  • I change the order of the parents to (Joe, Jane) again

    the terminal is my friend, and shows AttributeError

    AttributeError: 'Mary' object has no attribute 'eye_color'
    
  • I add the eye_color object attribute to Joe

    the terminal is my friend, and shows AssertionError

    AssertionError: 'blue' != 'green'
    
  • I change the expectation of the assertion for mary.eye_color in test_objects_w_multiple_parents in test_family_ties.py

    the test passes because this happens when mary = src.family_ties.Mary() runs

    mary = src.family_ties.Mary()
           Mary.__init__()
               super().__init__('mary')
           Joe.__init__('mary')
               super().__init__(first_name)
               self.eye_color = 'blue'
           Blow.__init__('mary')
               self.first_name = 'mary'
               self.last_name = 'blow'
    

    The order of the parents matters.

  • I remove the commented lines from Joe and Mary in family_ties.py


  • I add john to test_objects_w_multiple_parents in test_family_ties.py

    the terminal is my friend, and shows AttributeError

    AttributeError: module 'src.family_ties'
                    has no attribute 'John'
    
  • I add an object definition for John to family_ties.py

    the terminal is my friend, and shows TypeError

    TypeError: Person.__init__() missing 1
               required positional argument: 'first_name'
    

    because this happens when john = src.family_ties.John() runs

    john = src.family_ties.John()
           Person.__init__()
    

    which raises TypeError since the __init__ method of Person takes two positional arguments (self and first_name) and it got called with one (self)

  • I add the __init__ method to John in family_ties.py

    the test passes.

  • I add an assertion to make sure that John is a child (subclass) of Smith

    the terminal is my friend, and shows AssertionError

    AssertionError:
        <class 'src.family_ties.John'> is
        a subclass of <class 'src.family_ties.Smith'>
    

    no cheating this time.

  • I change the parent of John to Smith in family_ties.py

    the test passes.

  • I add an assertion for the last_name attribute

    the terminal is my friend, and shows AttributeError

    AttributeError:
        'John' object has no attribute 'last_name'.
        Did you mean: 'first_name'?
    
  • I add an object attribute for last_name to John in family_ties.py

    the test passes. This is a repetition because

  • I add a call to the super built-in function so instances of John can inherit the last_name object attribute

    the terminal is my friend, and shows AttributeError

    AttributeError:
        'John' object has no attribute 'first_name'.
        Did you mean: 'last_name'?
    
  • I add the first_name attribute to Smith

    the test passes because this happens when john = src.family_ties.John() runs

    john = src.family_ties.John()
           John.__init__()
               super().__init__('john')
           Smith.__init__('john')
               self.first_name = 'john'
               self.last_name = 'smith'
    
  • I remove the commented lines from John


  • I add lil, an instance of a child (subclass) of John to test_objects_w_multiple_parents in test_family_ties.py

    the terminal is my friend, and shows AttributeError

    AttributeError: module 'src.family_ties' has no attribute 'Lil'
    
  • I add an object definition for Lil to family_ties.py

    the test passes.

  • I add an assertion to test John and Mary as parents of Lil?

    the terminal is my friend, and shows AssertionError

    AssertionError: <class 'src.family_ties.Lil'> is not
                    a subclass of <class 'src.family_ties.Mary'>
    
  • I add Mary as a parent to Lil in family_ties.py

    the test passes.

  • I add an assertion for the first_name attribute of lil in test_family_ties.py

    the terminal shows AssertionError

    AssertionError: 'john' != 'lil'
    

    because this happens when lil = src.family_ties.Lil() runs

    lil = src.family_ties.Lil()
          Lil # has no __init__
          # call the parent of Lil (John)
          John.__init__()
              super().__init__('john')
          Smith.__init__('john')
              self.first_name = 'john'
              self.last_name = 'smith'
    
  • I add the __init__ method with a value for the first_name attribute into Lil in family_ties.py

    the test passes.

  • I add an assertion for the last_name attribute of lil, in test_objects_w_multiple_parents in test_family_ties.py

    the terminal is my friend, and shows AssertionError

    AttributeError:
        'Lil' object has no attribute 'last_name'.
        Did you mean: 'first_name'?
    
  • I add a value for last_name to Lil in family_ties.py

    the test passes. This is a repetition, and a problem if the value of the last_name attribute of the parent changes.

  • I add a call to the super built-in function to remove the repetition

    the terminal is my friend, and shows TypeError

    TypeError: John.__init__() takes 1
               positional argument but 2 were given
    

    because this happens when lil = src.family_ties.Lil() runs

    lil = src.family_ties.Lil()
          Lil.__init__()
              super().__init__('lil')
          John.__init__('lil')
    

    which raises TypeError since the __init__ method of John takes one positional argument (self) and it was called with two (self and lil)

  • I change the __init__ method in John to take in a parameter for first_name with a default value to make it optional

    the terminal shows AssertionError

    AssertionError: 'smith' != 'blow'
    

    because this happens when lil = src.family_ties.Lil() runs

    lil = src.family_ties.Lil()
          Lil.__init__()
              super().__init__('lil')
          John.__init__('lil')
              super().__init__(first_name)
          Smith.__init__('lil')
              super().__init__(first_name)
              self.first_name = first_name
              self.last_name = last_name
    

    the __init__ method of Mary did not get called.

  • I add a call to the super built-in function in Smith

    the terminal is my friend, and shows TypeError

    TypeError: Mary.__init__() got
               an unexpected keyword argument 'first_name'
    

    because this happens when lil = src.family_ties.Lil() runs

    lil = src.family_ties.Lil()
          Lil.__init__()
              super().__init__('lil')
          John.__init__('lil')
              super().__init__(first_name)
          Smith.__init__('lil')
              super().__init__(
                  first_name=first_name,
                  last_name='smith',
              )
          # call the next parent of Lil
          Mary.__init__(
              first_name='lil',
              last_name='smith'
          )
    
  • I add first_name with a default value to the __init__ method of Mary

    the terminal is my friend, and shows TypeError

    TypeError: Mary.__init__() got
               an unexpected keyword argument 'last_name'.
               Did you mean 'first_name'?
    
  • I add last_name to the __init__ method

    the terminal shows SyntaxError

    SyntaxError: parameter without a default
                 follows parameter with a default
    

    because parameters without default values must come before parameters with default values.

  • I add a default value for last_name

    the test passes because this happens when lil = src.family_ties.Lil() runs

    lil = src.family_ties.Lil()
          Lil.__init__()
              super().__init__('lil')
          John.__init__('lil')
              super().__init__(first_name)
          Smith.__init__('lil')
              super().__init__(
                  first_name=first_name,
                  last_name='smith',
              )
          Mary.__init__('lil', 'smith')
              super().__init__(first_name)
              # no last_name passed to Parent
          Joe.__init__('lil')
              super().__init__(first_name)
              self.eye_color = 'blue'
          Blow.__init__('lil')
              self.first_name = 'lil'
              self.last_name = 'blow'
    

    the __init__ method of Jane did not get called.

  • I change the order of the parents of Lil to (Mary, John) to see if the value will change to john.last_name

    the test is still green because this happens when lil = src.family_ties.Lil() runs

    lil = src.family_ties.Lil()
          Lil.__init__()
              super().__init__('lil')
          Mary.__init__('lil')
              Mary.__init__('lil', last_name=None)
              # use the default value
              super().__init__(first_name)
              # no last_name passed to Parent
          Joe.__init__('lil')
              super().__init__(first_name)
              self.eye_color = 'blue'
          Blow.__init__('lil')
              self.first_name = 'lil'
              self.last_name = 'blow'
    

    the __init__ method of Jane did not get called.

  • I add a call to the super built-in function in Blow

    the terminal is my friend, and shows TypeError

    TypeError:
        Jane.__init__() got
        an unexpected keyword argument 'last_name'.
        Did you mean 'first_name'?
    

    because this happens when lil = src.family_ties.Lil() runs

    lil = src.family_ties.Lil()
          Lil.__init__()
              super().__init__('lil')
          Mary.__init__('lil')
              Mary.__init__('lil', last_name=None)
              # use the default value
              super().__init__(first_name)
              # no last_name passed to Parent
          Joe.__init__('lil')
              super().__init__(first_name)
              self.eye_color = 'blue'
          Blow.__init__('lil')
              super().__init__(
                  first_name=first_name,
                  last_name='blow'
              )
          # call the next parent of Mary
          Jane.__init__('lil', last_name='blow')
    

    which raises TypeError since the __init__ method of Jane does not have a parameter named last_name. Confused?

  • I add last_name to Jane

    the terminal is my friend, and shows SyntaxError

    SyntaxError: parameter without a default
                 follows parameter with a default
    

    because parameters without default values must come before parameters with default values.

  • I give last_name a default value

    the terminal is my friend, and shows AssertionError

    AssertionError: 'doe' != 'blow'
    

    for the expectation of mary.last_name because this happens when mary = src.family_ties.Mary() runs, if the parents of Mary are (Joe, Jane)

    mary = src.family_ties.Mary()
           Mary.__init__(
               first_name='mary',
               last_name=None,
           )
               super().__init__(first_name)
           Joe.__init__('mary')
               super().__init__(first_name)
               self.eye_color = 'blue'
           Blow.__init__('mary')
               super().__init__(
                   first_name=first_name,
                   last_name='blow'
               )
           # call the next parent of Mary
           Jane.__init__('mary', last_name='blow')
               super().__init__('mary')
               self.eye_color = 'green'
           Doe.__init__('mary')
               super().__init__('mary')
           Person.__init__('mary')
               Person.__init__('mary', last_name='doe')
               self.first_name = 'mary'
               self.last_name = 'doe' # use the default value
    
  • I change the expectation of the assertion for mary.last_name in test_objects_w_multiple_parents

    the test passes because this happens when lil = src.family_ties.Lil() runs

    lil = src.family_ties.Lil()
          Lil.__init__()
              super().__init__('lil')
          Mary.__init__(
              first_name='lil',
              last_name=None,
          )
              super().__init__(first_name)
          Joe.__init__('lil')
              super().__init__(first_name)
              self.eye_color = 'blue'
          Blow.__init__('lil')
              super().__init__(
                  first_name=first_name,
                  last_name='blow'
              )
          # call the next parent of Mary
          Jane.__init__(
              first_name='lil',
              last_name='blow',
          )
              super().__init__('lil')
              # last_name does not get passed to parent
              self.eye_color = 'green'
          Doe.__init__('lil')
              super().__init__('lil')
          # call the next parent of Lil
          John.__init__('lil')
              super().__init__(first_name)
          Smith.__init__('lil')
              super().__init__(
                  first_name=first_name,
                  last_name='smith',
              )
          Person.__init__(
              first_name='lil',
              last_name='smith',
          )
              self.first_name = 'lil'
              self.last_name = 'smith'
    

    the order of the parents matters.

  • I remove the commented lines from family_ties.py



Note

All the instances could have been made with only the Person object because there was nothing unique about the objects I made in family_ties.py and it would not have given me the chance to practice making objects with multiple parents and seeing how Python resolves them.

joe = src.family_ties.Joe()
joe = src.person.Person('joe', last_name='blow')
joe.eye_color = 'blue'
jane = src.family_ties.Jane()
jane = src.person.Person('jane')
jane.eye_color = 'green'
mary = src.family_ties.Mary()
mary = src.person.Person('mary', joe.last_name)
mary.eye_color = joe.eye_color
john = src.family_ties.John()
john = src.person.Person('john', 'smith')
john.eye_color = 'brown'
lil = src.family_ties.Lil()
lil = src.person.Person('lil', john.last_name)
lil.eye_color = mary.eye_color

which would have just been Python making these calls to make instances (copies) of the Person object

a_name = src.person.Person(first_name, last_name=last_name)
         Person.__init__(first_name, last_name=last_name)
         self.first_name = first_name
         self.last_name = last_name
a_name.eye_color = color

I can make objects with multiple parents


review

I can make an object with


close the project

  • I close test_family_ties.py and family_ties.py

  • I click in the terminal where the tests are running

  • I use q on the keyboard to leave the tests. The terminal goes back to the command line.

  • I change directory to the parent of person

    terminal
    cd ..
    

    the terminal shows

    .../pumping_python
    

    I am back in the pumping_python directory.


code from the chapter

Do you want to see all the CODE I typed in this chapter?


what is next?

Would you like to use object attributes with the ‘functions’ project?


rate pumping python

If this has been a 7 star experience for you, please CLICK HERE to leave a 5 star review of pumping python. It helps other people get into the book too.