Mutually Reference-able Instances in Python - python

Say I have a pair of instances that reference one another mutually. Is there a preferable manner to structure this relationship than the following.
class Human():
def __init__(self, name):
self.name = name
self.pet = Dog('Sparky', self)
def pet(self, animal):
self.pet.receive_petting()
class Dog(Pet):
def __init__(self, name, owner):
self.name = name
self.owner = owner
def receive_petting(self):
pass
def bark_at(self, person):
"do something"
The thing I don't like is that the relationship needs to be specified in two places. Any ideas on how to make this dryer?

I would break this into three classes:
class Human():
def __init__(self, name):
self.name = name
class Dog(Pet):
def __init__(self, name):
self.name = name
def bark_at(self, person):
"do something"
class OwnerPetRelation():
def __init__(self, dog, human):
self.owner=human
self.pet=dog
Now, one owner can also have many dogs, we just need to define as many OwnerPetRelations.
Similarly, a dog can also belong to multiple owners now.

I would create a method on Human that allows you to add pets (since a human might have many pets):
class Human():
def __init__(self, name):
self.name = name
self.pets = []
def add_pet(self, pet):
pet.owner = self
self.pets.append(pet)
def pet(self, animal):
for pet in self.pets:
pet.receive_petting()
class Dog(Pet):
def __init__(self, name):
self.name = name
self.owner = None
def receive_petting(self):
pass
def bark_at(self, person):
"do something"
This can be used as follows
human = Human('Jim')
human.add_pet(Dog('Woof'))
This approach can of course also be used for just a single pet and one could also extend it to allow pets to be owned by many humans.

There's nothing really Python-specific here; this is just a limitation of constructor-based dependency injection. It's hard to inject a reference to another object that cannot have been created yet. Instead, you can create an object that has a reference to something that will have a reference to the other object. For instance, you can pass a function to the constructor that will be able to return the value:
class Human():
def __init__(self,name,dog):
self.name = name
self._dog = dog
#property
def dog(self):
return self._dog()
class Dog():
def __init__(self,name,human):
self.name = name
self._human = human
#property
def human(self):
return self._human()
Then you can use it like this:
human = None
dog = Dog('fido',lambda: human)
human = Human('john',lambda: dog)
print(dog.human.name)
print(human.dog.name)
john
fido
It is not hard to update this so that the property function caches the value, of course. E.g.:
class Dog():
def __init__(self,name,human):
self.name = name
self._human = human
#property
def human(self):
try:
return self._human_
except AttributeError:
self._human_ = self._human()
return self._human_

Related

composition over inheritance, even is it a Is-a relationship

I try to use composition even the relationship is: is-a.
So I have a Animal class and I have a Zebra class:
class Name:
pass
class Age:
pass
class Zebra():
pass
class Animal:
def __init__(self, name_animal, age_animal) -> None:
self.name_animal = name_animal
self.age_animal = age_animal
self.name = Name()
self.age = Age()
self.zebra = Zebra()
def __repr__(self):
return "My name is {} and I am {} years old".format((self.name_animal), (self.age_animal))
zebra1 = Zebra('Zebra', 37)
print(zebra1)
but then of course it fails because Zebra has no arguments.
So is it possible to use the repr method also for Zebra without inheritcance but with compostion?
Because I get now this error:
TypeError: Zebra() takes no arguments
I don't recommend to use composition in this case. This is a use-case for inheritance. But academic questions also deserve an answer.
Add a constructor to Zebra that initializes and stores an Animal instance and delegate __repr__:
class Animal:
def __init__(self, name_animal, age_animal) -> None:
self.name_animal = name_animal
self.age_animal = age_animal
def __repr__(self):
return "My name is {} and I am {} years old".format((self.name_animal), (self.age_animal))
class Zebra():
def __init__(self, name_animal, age_animal) -> None:
self.animal = Animal(name_animal, age_animal)
def __repr__(self):
return self.animal.__repr__()
zebra1 = Zebra('Zebra', 37)
print(zebra1)

Problems with inheriting the correct property with the child class in python

I am needing to have the child class inherit from the parent class. I continue to either get "TypeError: init() missing 1 required positional argument: 'species'" or the name is often getting assigned to the name and name continues to return back as none.
import unittest
import time
class Mammal:
""" A Mammal class to further populate our animal kingdom """
def __init__(self, species, name):
""" mammal constructor can initialize class attributes """
self.species = species
self.name = None
def eat(self, food):
""" a method that will 'eat' in O(n) time """
i = food
print(self.name, "the", self.species, "is about to eat")
while i >= 1:
time.sleep(0.1)
i = i // 2
print(" ", self.name, "is done eating!")
def makeNoise(self):
""" a method that should be implemented by children classes """
raise NotImplementedError("this method should be implemented by child class")
ADD ANY OTHER BASE CLASS METHODS YOU NEED/WANT TO HERE
def __eq__(self, object):
return isinstance(object, Mammal) and object.species == self.species
THE FOLLOWING TWO CLASSES NEED TO BE COMPLETED, AND YOU
NEED TO REPLACE/DELETE ALL OF THE ELLIPSES SHOWN BELOW
class Hippo(Mammal):
def __init__(self, name, species):
self.name = name
self.species = 'hippo'
def getName(self):
return self.name
def setName(self, h):
self.name = h
def makeNoise(self):
return 'grunting'
class Elephant(Mammal):
def __init__(self, name, species):
self.name = name
self.species = 'elephant'
def getName(self):
return self.name
def setName(self, e):
self.name = e
def makeNoise(self):
return 'trumpeting'
class TestMammals(unittest.TestCase):
""" a class that is derived from TestCase to allow for unit tests to run """
def testInheritance(self):
""" confirm that Elephant and Hippo are children classes of Mammal """
self.assertTrue(issubclass(Elephant, Mammal) and issubclass(Hippo, Mammal))
def testEqOperator(self):
hip1 = Hippo('John')
hip2 = Hippo('Arnold')
self.assertEqual(hip1, hip2)
def main():
""" a 'main' function to keep program clean and organized """
print("-------------------- start main --------------------")
e = Elephant("Ellie")
h = Hippo("Henry")
if(e == h):
print(e.getName(), "and", h.getName(), "are of the same species")
else:
print(e.getName(), "and", h.getName(), "are *not* of the same species")
def listenToMammal(Mammal):
print(Mammal.makeNoise())
listenToMammal(e)
listenToMammal(h)
e.eat(100)
print("--------------------- end main ---------------------")
if __name__ == "__main__":
main()
unittest.main()
enter image description here
this is what the output should look like but im confused
You are still defining Hippo.__init__ to take 2 arguments, even though you ignore the species argument. You can drop that one. You should also use Mammal.__init__ rather than setting the attributes yourself.
class Hippo(Mammal):
def __init__(self, name):
super().__init__(self, name, species='hippo')
def getName(self):
return self.name
def setName(self, h):
self.name = h
def makeNoise(self):
return 'grunting'
getName and setName aren't necessary; you can access the name attribute directly

Python Inheritence from constructor

person.py
class Person:
"""---A class representing a person---"""
# Person constructor
def __init__(self,n,a):
self.full_name = n
self.age = a
class Student(Person):
# Student constructor
def __init__(self,n,a,s):
Person.__init__(self,n,a)
self.school = s
driver.py
from person import *
a = Student("Alice", 19, "Univ")
It throws TypeError: __init__() takes 3 positional arguments but 4 were given
I tried to change Student class to the following:
class Student(Person):
# Student constructor
def __init__(self,n,a,s):
super().__init__(n,a)
self.school = s
The error still exists.
Why does this happen? Is super() keyword required to add new attributes?
EDIT: The problem is solved. There was an indentation issue in the source code rendering this strange behavior, hence the question should be closed.
This line:
Person.__init__(self,n,a)
Is the problem. Recall that methods are automatically passed a reference to themselves, so you just passed a second one.
There's also a well-established pattern for this:
class Person
def __init__(self, name, age):
self.name = name
self.age = age
class Student(Person):
def __init__(self, school, *args):
super().__init__(*args)
self.school = school
student = Student('Washington Elementary', "Johnny Go'gettem", 10)
although note that simply removing your reference to self in the Person.__init__ call inside Student.__init__ would be sufficient.
Note that you can override the default method behavior with a couple of decorators that become quite useful in certain situations. Neither apply here, but just a bit of knowledge to tease your brain a bit:
def SomeClass:
attr = "class-scoped"
def __init__(self):
self.attr = "instance-scoped"
def some_method(self):
return self.attr == "instance-scoped"
#classmethod
def some_classmethod(cls):
return cls.attr == "class-scoped"
#staticmethod
def some_staticmethod():
return "I'm not given a \"self\" parameter at all!"
classmethods are particularly useful as alternate constructors
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
#classmethod
def from_tuple(cls, tup) -> "Person":
"""Expects a tuple of (name, age) and constructs a Person"""
name, age = tup
return cls(name, age)
#classmethod
def from_dict(cls, dct) -> "Person":
"""Expects a dictionary with keys "name" and "age" and constructs a Person"""
try:
name = dct['name']
age = dct['age']
except KeyError:
raise ValueError(f"Dictionary {dct} does not have required keys 'name' and 'age'")
else:
return cls(name, age)

Combining dict in super class's init and subclass's init automatically?

I'm creating an event system which uses the following class for events:
class Event(set):
def __init__(self, name, iterable=()):
super().__init__(iterable)
self.name = name
def __iadd__(self, listener):
self.add(listener)
return self
def __isub__(self, listener):
self.remove(listener)
return self
def fire(self, **eargs):
for listener in self:
listener(**eargs)
Now I'm trying to create some kind of a dict that would automatically create the events in its __init__ like so:
class EventDict(dict):
def __init__(self, prefix, *event_names):
super().__init__({
name: Event('%s.%s' % (prefix, name))
for name in event_names
})
And here's an example of usage:
class Player:
def __init__(self, name):
self._name = name
self.events = EventDict('Player', 'change_name')
#property
def name(self):
returns self._name
#name.setter
def name(self, value):
old_name = self.name
self.name = value
self.events['change_name'].fire(player=self, old_name=old_name)
Now the problem I'm facing is subclassing.
If I were to subclass my Player class to include also health attribute, I can't use the same way of creating an event dict, cause it would override the existing one and I couldn't access change_name anymore.
So I'm trying to find a way where I can just do something like this (ideal solution):
class Player:
events = EventDict('Player', 'change_name')
class Player2(Player):
events = EventDict('Player2', 'attack', 'kill')
p2 = Player2()
p2.events['change_name'] += my_event_listener # Still access Player class's events
Would something like this be possible?
I know I can do:
class Player2(Player):
def __init__(self, name):
super().__init__()
self.events.update(...)
But it's not the same :P
I think what you want is:
class Player:
EVENTS = ('change_name',)
def __init__(self, name):
self._name = name
self.events = EventDict(
self.__class__.__name__,
*self.EVENTS,
)
...
Then all you need in Player2 is:
class Player2(Player):
EVENTS = Player.EVENTS + ('attack', 'kill')
and the inherited __init__ will work fine.
Stop using EventDict.
The class itself has its own dict which supports inheritance like that.
class Player:
def __init__(self, name):
self._name = name
self.change_name_event = Event('Player.change_name')
class Player2(Player):
def __init__(self, name):
super().__init__(name)
self.attack_event = Event('Player2.attack')
self.kill_event = Event('Player2.kill')
All the events from the subclasses will be added no matter what.
I noticed that maybe you wanted to make it obvious that they're events, so I added 'event' to the names of the fields, but you don't need to if you don't want to.
If you wanted it so that the prefix is the same throughout, then you'd change the strings from something like 'Player.change_name' to self.__class__.__name__ + '.change_name'. That way, it always gets whatever the actual class for the object is. This is part of what #jonrsharpe's solution is trying to get at.
If you wanted to make it so others could add more events dynamically, they can simply do a line like playerObj.my_new_event = Event('Player.my_new_event') or you could provide a nice method in the Player class to make their lives easier:
def add_event(self, event_name):
setattr(self, event_name, Event(self.__class__.__name__ + '.' + event_name)

Difference between assigning values to attributes in child class and parent class

Choice A:
class Mammal(object):
def __init__(self, name):
self.name = name
def __str__(self):
return str(self.name)
class Human(Mammal):
def __init__(self, name):
self.name = name
me = Human("John")
print(me)
Choice B:
class Mammal(object):
def __init__(self, name):
self.name = name
def __str__(self):
return str(self.name)
class Human(Mammal):
def __init__(self, name):
super(Human, self).__init__(name)
me = Human("John")
print(me)
Both choices return the same result, but can someone please explain what's the difference between assigning the name to child class (Human) and the parent class (Mammal)? Is there a better one between these two choices?
Thank you very much!
This is really a question of class design and maintenance. Obviously in this case there are no characteristics that Humans have that Mammals don't and no other Mammals. But, for instance, let's say you later update Mammal to have a "feet" attribute:
class Mammal(object):
def __init__(self, name, feet):
self.name = name
self.feet = feet
Mammals now have a number of feet, and so you might expect Humans to also. But me.feet will throw an error, because the Human __init__ didn't initialize it, and the Mammal __init__ didn't run. Nor can you declare feet with me = Human('Joe', 2), because the Human __init__ doesn't take that argument. So you've created a maintenance problem -- Humans are now not really good Mammals, because some of their promised attributes are always undefined.
Using super avoids this problem:
class Human(Mammal):
def __init__(self, name):
super(Human, self).__init__(name, 2)
Of course, this requires you to subclass Human if you want a lot of pirates, but that's another problem.
The converse situation might be if you decided that most Mammals don't have names. Obviously, in this case, you would want to define name only in the Human __init__.
In the first example, you only say that Human inherits from Mammal ( the .__init__ on Mammal is not called), and if you try to use self.name you'll get an attribute error.
In the second example you are, in Human class, using the self.name from Mammal.

Categories

Resources