Is there a way to hide or nest the Cat attributes within Animal while still being able to run the jump function? Specifically I would like to the output of vars(test) to only be the age. I'm sure I could hard code which specific attributes to not output by defining a custom __str__ but I will have a lot of attributes for Cat/Animal and I don't want to manually add an exception for each individual attribute. I also won't have access to the Cat class.
def Jump():
print('Jumped!')
class Cat:
def __init__(self):
self.feet = 4
self.jump = Jump
class Animal(Cat):
def __init__(self):
Cat.__init__(self)
self.age = 3
test = Animal()
test.jump()
print(vars(test))
Output:
Jumped {'feet': 4, 'jump': , 'age': 3}
This is just code to illustrate what I'm trying to do. In reality Cat represents a published python module and Animal represents my custom one.
If you change your relationship so that Animal no longer is-a Cat, but instead has-a Cat, then you're right, animal.jump() will no longer work.
There are many ways around this. Given how odd this design already is, in so many different ways, I have no idea which is most appropriate, so I'll just list a whole bunch of them.
The first group are ways to explicitly delegate just jump.
The "normal" way:
class Animal:
def __init__(self):
self.cat = Cat()
def jump(self):
return self.cat.jump()
Copy the per-instance function:
class Animal:
def __init__(self):
self.cat = Cat()
self.jump = self.cat.jump
Delegate to the per-instance function:
class Animal:
def __init__(self):
self.cat = Cat()
self.jump = lambda: self.cat.jump()
Per-instance bound method:
class Animal:
def __init__(self):
self.cat = Cat()
self.jump = (lambda self: self.cat.jump()).__get__(self)
Dynamic lookup:
class Animal:
def __init__(self):
self.cat = Cat()
def __getattr__(self, name):
if name == 'jump':
return getattr(self.cat, name)
raise AttributeError
Dynamic bound method generation:
class Animal:
def __init__(self):
self.cat = Cat()
def __getattr__(self, name):
if name == 'jump':
return (lambda self: getattr(self.cat, name)()).__get__(self)
raise AttributeError
Of course all of these only delegate jump specifically. What if you wanted to delegate to all Cat functions, methods, and maybe other attributes, without necessarily knowing what they are in advance? Well, it should be obvious how to adapt most of these, so I'll just show two.
Dynamic lookup:
class Animal:
def __init__(self):
self.cat = Cat()
def __getattr__(self, name):
return getattr(self.cat, name)
Semi-static inspection that does complicated reflection on the different possible kinds of things we might want to delegate:
class Animal:
def __init__(self):
self.cat = cat
for name, value in inspect.getmembers(self.cat):
if name.startswith('_'): continue
if inspect.ismethod(value):
value = (lambda self: value()).__get__(self)
elif callable(value):
value = lambda: value()
else:
value = copy.copy(value)
setattr(self, name, value)
Related
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)
I've been running into a weird little smell in my python code lately and I think it has something to do with parallel inheritance. Here is a small example I concocted:
class DogHabits:
def __init__(self):
self.habits = ['lick butt']
class GermanShepherdHabits(DogHabits):
def __init__(self):
super().__init__()
self.habits.extend(['herd sheep'])
class LabradorHabits(DogHabits):
def __init__(self):
super().__init__()
self.habits.extend(['hunt', 'pee on owner'])
class Dog:
def __init__(self):
self.type = 'generic_dog'
self.my_habits = DogHabits()
def do_stuff(self):
for habit in self.my_habits.habits:
print(habit)
class GermanShepherd(Dog):
def __init__(self):
self.type = 'german shepherd'
self.my_habits = GermanShepherdHabits()
class Labrador(Dog):
def __init__(self):
self.type = 'labrador'
self.my_habits = LabradorHabits()
if __name__ == "__main__":
german_shepherd = GermanShepherd()
print('\n{}'.format(german_shepherd.type))
german_shepherd.do_stuff()
labrador = Labrador()
print('\n{}'.format(labrador.type))
labrador.do_stuff()
I have a generic dog class from which concrete dog implementations inherit. Every dog class (including the generic/abstract one) has a set of habits, itself represented by another class hierarchy for the habits.
I am annoyed by the fact that I have to have both hierarchies exactly the same at all times. Furthermore the inheritance between the DogHabits is useful within the habits hierarchy, but it is not useful within the dogs hierarchy, as I need to instantiate a separate habits object for each class in the dog hierarchy.
What is the antidote to this? I may want to add many implementations of the dog class, and updating the corresponding habits hierarchy sounds tedious and smells bad...
This might be going too far afield, but I don't see the need for a separate DogHabits class. habits should be a class attribute, not an instance attribute, and could be set by __init_subclass__.
class Dog:
habits = ['lick butts']
def __init_subclass__(cls, habits=None, **kwargs):
super().__init_subclass__(**kwargs)
if habits is not None:
cls.habits = cls.habits + habits
class GermanShepherd(Dog, habits=['herd sheep']):
def __init__(self):
self.type = 'german shepherd'
class Labrador(Dog, habits=['pee on owner']):
def __init__(self):
self.type = 'labrador'
type itself is also more of a class attribute than an instance attribute, as it's simply an (alternate) string representation of information already encoded by the class itself. Since you wouldn't append to an existing value, it's easier to just set the class attribute where necessary rather than going through __init_subclass:
class Dog:
habits = ['lick butts']
type = 'generic_dog'
def __init_subclass__(cls, habits=None, **kwargs):
super().__init_subclass__(**kwargs)
if habits is not None:
cls.habits = cls.habits + habits
class GermanShepherd(Dog, habits=['herd sheep']):
type = 'german shepard'
class Labrador(Dog, habits=['pee on owner']):
type = 'labrador'
class BlackLabrador(Labrador):
pass # E.g. if you are happy with inheriting Labrador.type
IF habits need to a class attribute, rather than instance attributes, this may actually be a good use for metaclasses.
Habits need not be a simple list, it could be something else, as long as there is the notion of addition to previous and return new. (__add__ or __radd__ on a Habits class would do the trick I think)
class DogType(type):
def __init__(cls, name, bases, attrs):
""" this is called at the Dog-class creation time. """
if not bases:
return
#pick the habits of direct ancestor and extend it with
#this class then assign to cls.
if "habits" in attrs:
base_habits = getattr(bases[0], "habits", [])
cls.habits = base_habits + cls.habits
class Dog(metaclass=DogType):
habits = ["licks butt"]
def __repr__(self):
return f"My name is {self.name}. I am a {self.__class__.__name__} %s and I like to {self.habits}"
def __init__(self, name):
""" dog instance can have all sorts of instance variables"""
self.name = name
class Sheperd(Dog):
habits = ["herds sheep"]
class GermanSheperd(Sheperd):
habits = ["bites people"]
class Poodle(Dog):
habits = ["barks stupidly"]
class StBernard(Dog):
pass
for ix, cls in enumerate([GermanSheperd, Poodle, StBernard]):
name = f"dog{ix}"
dog = cls(name)
print(dog)
output:
My name is dog0. I am a GermanSheperd %s and I like to ['licks butt', 'herds sheep', 'bites people']
My name is dog1. I am a Poodle %s and I like to ['licks butt', 'barks stupidly']
My name is dog2. I am a StBernard %s and I like to ['licks butt']
This answer assumes that the DogHabits is much more complex than a mere list and is really worth a dedicated class with its own inheritance.
On a design point of view, I can see a first question on whether habits and type should be class or instance members. Here again, this answer assumes that there are reasons to make them instance members.
I would make Habits an inner class of Dogs and state in the class documentation that is can be customized by building a subclass of it in a subclass of Dogs:
class Dog:
class Habits:
"""Represents the habits of a Dog.
It can be customized in a child class by creating in the subclass an
inner class named Habits that would be a subclass of Dog.Habits
"""
def __init__(self):
self.habits = ['lick butt']
def __init__(self, typ='generic_dog'):
self.type = typ
self.my_habits = self.__class__.Habits()
def do_stuff(self):
for habit in self.my_habits.habits:
print(habit)
class GermanShepherd(Dog):
class Habits(Dog.Habits):
def __init__(self):
super().__init__()
self.habits.extend(['herd sheep'])
def __init__(self):
super().__init__('german shepherd')
class Labrador(Dog):
class Habits(Dog.Habits):
def __init__(self):
super().__init__()
self.habits.extend(['hunt', 'pee on owner'])
def __init__(self):
super().__init__('labrador')
I'm using python 3.6.
My goal is to make a base class that would be able to somehow access through polymorphism - one of the child class variables.
I know it sounds somewhat 'not oop', so if what im describing can't be done with python - I would like to know what is the best practice for this case.
Following wikipedia's example:
class Animal:
def __init__(self, name): # Constructor of the class
self.name = name
def talk(self): # Abstract method, defined by convention only
raise NotImplementedError("Subclass must implement abstract method")
class Cat(Animal):
def talk(self):
return 'Meow!'
class Dog(Animal):
def talk(self):
return 'Woof! Woof!'
animals = [Cat('Missy'),
Cat('Mr. Mistoffelees'),
Dog('Lassie')]
for animal in animals:
print animal.name + ': ' + animal.talk()
Prints the following:
Missy: Meow!
Mr. Mistoffelees: Meow!
Lassie: Woof! Woof!
I would like to achieve the exactly same output - using
variable overloading (is that a thing?) instead of method overloading.
The reason is that in the programm im working on - dog, cat, and every other kind of animal will talk exactly the same way - influenced only by the data member, such as:
class Animal:
def __init__(self, name): # Constructor of the class
self.name = name
self.vocabulary = [] # so called abstract data member
def talk(self): # Non Abstract method, all animals would talk
for word in self.vocabulary: print (word)
class Cat(Animal):
vocabulary = ["Meow", "Muuuew", "Maow"]
class Dog(Animal):
vocabulary = ["Woof", "Waf", "Haw"]
animals = [Cat('Missy'),
Cat('Mr. Mistoffelees'),
Dog('Lassie')]
for animal in animals:
print animal.name + ': ' + animal.talk()
Prints the following:
Missy: Meow Muuuew Maow
Mr. Mistoffelees: Meow Muuuew Maow
Lassie: Woof Waf Haw
Clearly, this won't work since vocabulary will be empty, as it is in the base class.
I tried to find a solution using super, e.g:
class Cat(Animal):
vocabulary = ["Meow", "Muuuew", "Maow"]
def talk(self):
super(Animal,Cat).talk()
But the result would be AttributeError: 'super' object has no attribute 'talk'
Am I using super wrong?
There are a few unresolved issues in your code, but since python is so dynamic, it will find the subclass instance attribute through normal lookup:
class Animal:
def __init__(self, name):
self.name = name
def talk(self):
for word in self.vocabulary: print (word)
class Cat(Animal):
def __init__(self, name):
super().__init__(name)
self.vocabulary = ["Meow", "Muuuew", "Maow"]
class Dog(Animal):
def __init__(self, name):
super().__init__(name)
self.vocabulary = ["Woof", "Waf", "Haw"]
animals = [Cat('Missy'),
Cat('Mr. Mistoffelees'),
Dog('Lassie')]
for animal in animals:
print(animal.name, end=': ')
animal.talk()
If you want something to enforce this requirement more explicitly in the code, you can make Animal an abstract base class and make an abstruct property named vocabulary:
import abc
class Animal(abc.ABC):
def __init__(self, name):
self.name = name
#property
#abc.abstractmethod
def vocabulary(self):
...
def talk(self):
for word in self.vocabulary: print (word)
class Cat(Animal):
#property
def vocabulary(self):
return ["Meow", "Muuuew", "Maow"]
here is a live link
Python is dynamically typed. There is no need to somehow declare an "abstract data member" in Animal for Animal methods to refer to self.vocabulary; in fact, your attempts to declare an "abstract data member" are causing your problems.
Just remove self.vocabulary = [], and talk will automatically find the subclass vocabulary when it tries to access self.vocabulary.
class Human:
def __init__(self) -> None:
self.name = None # type: str
def introduce(self):
print("I'm " + self.name)
class Alice(Human):
def __init__(self) -> None:
super().__init__()
self.name = "Alice"
class Bob(Human):
def __init__(self, rude: bool) -> None:
super().__init__()
self.rude = rude
#property
def name(self) -> str:
return "BOB!" if self.rude else "Bob"
if __name__ == '__main__':
alice = Alice()
alice.introduce()
bob = Bob(rude=True)
bob.introduce()
In the code above, there is an abstract Human class (in reality it is not a human and has more complex methods, not related to the problem). Most of its implementations would set their names by simply assigning a string to the name attribute (just as Alice). But there are few exceptions, like Bob, when there is more complex logic assigned (the value depends on the object state in the moment of resolving).
Therefore in Bob class I created a custom getter for the name property. But as an effect, it is impossible to create a class instance, because invoking the superconstructor results in the following error.
AttributeError: can't set attribute
And it is impossible to add a naive setter as well.
#name.setter
def name(self, name: str):
self.name = name
Why? Because it would result in an infinite loop. How to solve that issue?
why not make a dummy setter
#name.setter
def name(self, value):
pass
When self.name = None is executed it will call this setter and actually do nothing
If you're certain that your subclasses will assign name, then you can leave out the assignment in the parent constructor. Right now, Human is attempting to set to name, when there is no setter. If you removed it from the Human constructor, then Human can look like this:
class Human:
def introduce(self):
print("I'm " + self.name)
For class BobI would have used something like this in this case:
#name.setter
def name(self, name: str):
self._name = name
Afterwards you can do whatever you want in the more complex getter with the internal value. Or did I get the question wrong?
Executing the code would give:
I'm Alice
I'm BOB!
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_