Python3: Class Composition and derived methods - python

Good morning,
I'm trying something like this:
class Fish:
def __init__(self, name):
self.name = name
def swim(self):
print(self.name,"is swimming!")
My ourpose is to extend the class to Aquarium, which cointains a dictionary of fishes:
class Aquarium(Fish):
def __init__(self, **kwargs):
self.fishes ={}
for _, name in kwargs.items():
self.fishes[name] = Fish.__init__(self,name)
def how_many(self):
print("In acquarium there are",len(self.fishes),"fishes")
def all_swimming(self):
#???
Is it possible to implement something like Aquarium.swim() to use the method of all classes inserted? I tried it, but as result it prints out only of the last fish inserted. Any suggestion?
How can I collect many Fish() inside Aquarium()? Are there better methods?

It looks like you are confusing the idea of "is a kind of" and "contains". Writing class Aquarium(Fish) suggests that Aquarium is a kind of Fish, which it is not. An Aquarium contains fish. So, the Aquarium should not be derived from Fish.
I think this is more like your intentions:
class Fish:
def __init__(self, name):
self.name = name
def swim(self):
print(self.name, "is swimming!")
class Aquarium: # An aquarium is not a kind of fish, rather it contains fish
def __init__(self, **kwargs):
self.fishes = [] # list of all fishes in the aquarium
fishes = kwargs["fishes"]
for fish_name in fishes:
new_fish = Fish(fish_name)
self.fishes.append(new_fish) # add to your list
def how_many(self):
print("In aquarium there are " + str(len(self.fishes)) + " fishes")
def all_swimming(self):
print("The list of all fishes in the aquarium:")
for fish in self.fishes:
print(" " + fish.name)
a = Aquarium(fishes=["Nemo", "Dory"])
print(a.how_many())
a.all_swimming()

Yes, it is possible. But I think this is a better way.
class Fish:
def __init__(self, name:str):
self.name = name
def swim(self):
print(self.name,"is swimming!")
class Aquarium():
def __init__(self, fishes:Fish):
self.fishes = []
for fish in fishes:
self.fishes.append(fish)
def how_many(self):
print("In acquarium there are",len(self.fishes),"fishes")
def all_swimming(self):
for fish in self.fishes:
fish.swim()
Here is a list of suggestions that you may correct:
An aquarium is not a fish. Do not inherit from it! If you need an aspect of the Fish class then split that class and make an composite.
A dictionary is used to store a key and a value. But fish already knows that key. So why don't you use a list? Do you need the dictionary? If not use a list, it is easier to use (this is just my personal opinion).
You used **kwargs. While this is usable nobody can clearly understand what exactly you want these parameters to be. Usually it is better to use a clearly defined set of parameters.
Use typing. At least for the parameters. This is really helpful to understand your code better. Also you IDE might become more helpful if you do.

Related

python polymorphism class and function

i am start to learn how to write python code
There is an option to write code ones onthis situation?
i want to crate class and 2 class how extend from her and i want to check if i can loop on only ones my example:
class animal:
def printDetail(self):
print(self.name)
class bird(animal):
def printDetail(self):
super(bird, self).printName()
print(self.wingsSize)
class fish(animal):
def printDetail(self):
super(fish, self).printName()
print(self.weight)
fishList = []
birdList = []
animalList = []
def main():
for a in (animalList,fishList,birdList):
a.printDetail()
main()
when i try to do it i got an error that AttributeError: 'list' object has no attribute 'printDetail' like this is an unknow function. i understand that it try to take the attribute of the list class but there is any option that i can do it more esear then:
for a in animalList:
a.printDetail()
for a in fishList:
a.printDetail()
for a in birdList:
a.printDetail()
that is work fine but to long?
The first code snippet creates a 3-tuple of lists. You're invoking .printDetail() on every list in that tuple.
To create a list that contains the elements from each list (as opposed to a list that contains the lists themselves), you can use for a in (animalList + fishList + birdList):
As others have already answered, there are a variety of quick ways to do this. I prefer the unpacking method that Wups uses in his answer.
However, I also wanted to check if we needed to add initializations to each of these classes in order for the print to work. Further, I was thinking when you called printName in some methods, you meant printDetail instead (maybe I am wrong?). Thus I also revised the class code also, I hope it may benefit you and others who may want to run the code and see a result:
class animal:
def __init__(self, name):
self.name=name
def printDetail(self):
print(self.name)
class bird(animal):
def __init__(self, name, wingsSize):
self.name=name
self.wingsSize = wingsSize
def printDetail(self):
super(bird, self).printDetail()
print(self.wingsSize)
class fish(animal):
def __init__(self, name, weight):
self.name=name
self.weight=weight
def printDetail(self):
super(fish, self).printDetail()
print(self.weight)
fishList = [fish("salmon",12)]
birdList = [bird("eagle",4)]
animalList = [animal("bear")]
def main():
for a in (*animalList, *birdList, *fishList):
a.printDetail()
main()
Output:
bear
eagle
4
salmon
12

python multiple nested classes

This is going to look like class inheritance but I think it is not and there must be an easy way of doing the following. Take a look at this simple code:
class Land:
def __init__(self):
print "a new Land"
self.farms = []
def addfarm(self):
self.farms.append(Farm())
class Farm:
def __init__(self):
print "a new farm"
self.animals = []
def addanimal(self,name):
self.animals.append(Animal(name))
class Animal:
def __init__(self, name):
print "hi, I am %s" % name
self.name = name
USA = Land()
USA.addfarm()
USA.farms[0].addanimal('George')
USA.farms[0].addanimal('Martin')
USA.addfarm()
USA.farms[1].addanimal('Polly')
USA.farms[1].addanimal('Ralph')
Is there an easy way of getting all animals without doing?:
for eachfarm in USA.farms:
for each in eachfarm.animals:
print each.name
I am asking this because if for instance the user wants to add a new George to farm 0 I would like to quickly be able to say that name is taken. I would also be able to quickly run a function that gives me all animals in the land or all farms. Should I be writing functions for all that or Python got its own?
I am also interested on knowing if my nested class structure is not correct and could end up causing issues.
For instance, lets say I have a function that given an animal tells me the perfect food mix for it. I would like to be able to run that function on all my animals and write back into their object. If they are nested I am afraid the function may get confused!
Thanks!
Using nested classes like this is perfectly fine and is not about inheritance at all. However you may want to choose slightly different data structures.
You say that in each farm you only want to be able to have one animal of each name. However, you use a list to store them. A list allows you to have as many animals of the same name inside as you want to, so you'd need to perform that check yourself when you add another one.
However, you could use a dict. A dict is an unordered data structure that links a key to a value. In your case you could use the name of the animal as the key and the Animal object for the value. Checking if a key exists can be done in constant time (as compared to linear time with a loop), since internally a dict is a hash table.
Example code might look like this:
class Land:
def __init__(self):
print "a new Land"
self.farms = []
def addfarm(self):
self.farms.append(Farm())
class Farm:
def __init__(self):
print "a new farm"
self.animals = {}
def addanimal(self,name):
if not name in self.animals:
self.animals[name] = Animal(name)
return True
return False
class Animal:
def __init__(self, name):
print "hi, I am %s" % name
self.name = name
USA = Land()
USA.addfarm()
USA.farms[0].addanimal('George')
USA.farms[0].addanimal('Martin')
USA.addfarm()
USA.farms[1].addanimal('Polly')
USA.farms[1].addanimal('Ralph')
This would prevent you from adding two animals of the same name to one farm, returning a boolean depending on whether the animal could be added to the farm or not.
To get all animals on all farms you will still need nested loops. But enabling iteration over the objects itself can be much nicer. If you do the following:
class Land(object):
def __init__(self):
print "a new Land"
self.farms = []
def addfarm(self):
self.farms.append(Farm())
def __iter__(self):
for farm in self.farms:
yield farm
class Farm(object):
def __init__(self):
print "a new farm"
self.animals = {}
def addanimal(self,name):
if not name in self.animals:
self.animals[name] = Animal(name)
return True
return False
def __iter__(self):
for name, animal in self.animals.iteritems():
yield animal
class Animal(object):
def __init__(self, name):
print "hi, I am %s" % name
self.name = name
You could then:
for farm in USA:
for animal in farm:
pass #do something here
According to your comment, you also want to be able to do land.getAllAnimals() and farm.getAllAnimals(). The latter is easily accomplished because farm works as an iterator over all animals. If you want a list you can simply call list(farm).
For land.getAllAnimals() there are two nice options. Both are to be added to the previous declaration.
Option 1
class Land(object):
def getAllAnimals(self):
for farm in self:
for animal in farm:
yield animal
Option 2
from itertools import chain
class Land(object):
def getAllAnimals(self):
return chain(*self)
Both will return iterators over all animals. To cast these into a list, simply call list on them. The former is easier to understand, but the latter is more concise and, in my opinion, nicer.
There is nothing wrong with nesting your loops, and it's just the way to do it. You might want to look into a more declarative approach, or you might want to store your data differently, but that's all just implementation detail and primarily a matter of taste.

Python - Getting subclass from input. Do I have to write a separate function?

I'm working in Python 2.7.8. What follows is a slight variant of the problem I'm working on.
I have a large number of custom classes that I've written where the inheritance is like a tree. The behavior is well encapsulated by the following example:
import random
class Animal(object):
def __init__(self, name):
self.name = name
self.can_own_pets = False #most Animals cannot own pets
self.get_features()
def give_pet(self, pet):
if not self.can_own_pets:
print(self.name+' cannot own a pet!')
else:
self.pets.append(pet)
def is_hungry(self):
return random.choice([True, False])
def get_features(self):
"""
In some classes, get features will be a function
that uses self.name to extract features.
In my problem, the features are extracted
with regular expressions that are determined by
by the class.
"""
pass
class Human(Animal):
def __init__(self, name):
super(Human, self).__init__(name)
self.can_own_pets = True
self.pets = []
class Dog(Animal):
def __init__(self, name):
super(Dog, self).__init__(name)
def bark(self):
print 'WOOF'
def get_features(self):
if 'chihuahua' in self.name:
self.is_annoying = True
elif 'corgi' in self.name:
self.adorable = True
My program needs to take in a large number of animals and delegate them to the correct classes -- I need the correct attributes and methods. What I would like to do is modify the Animal constructor so that if the name argument is something like "Finn the Dog" or "Jake the Human", it (the constructor) returns an instance of the class "Dog" or "Human", complete with the appropriate methods and attributes. Now, I know that I could easily write a function that takes a string and class as arguments, constructs a dictionary where the keys are the names of the subclasses of the given class, looks up the element of the dictionary that is contained in the string, and returns an object of that class. My question is whether or not there is a way to code this into the Animal class itself, which seems more elegant to me (as well as easier to maintain).
Here's an implementation --
def _get_all_subclasses(cls):
for scls in cls.__subclasses__():
yield scls
for scls in _get_all_subclasses(scls):
yield scls
class Animal(object):
#staticmethod
def from_string(s):
for cls in _get_all_subclasses(Animal):
# Somehow pick the class based on the string... This is a really simple example...
if cls.__name__ in s:
return cls()
raise ValueError('Bummer. Animal has not been discovered.')
class Dog(Animal):
pass
class Cat(Animal):
pass
class Lion(Cat):
pass
print Animal.from_string('is a Dog')
print Animal.from_string('is a Cat')
print Animal.from_string('Lions!!!')
print Animal.from_string('Lockness Monster')
There are limitations here
All of the constructors need to be pretty much the same which means that Cat.__init__ needs to basically do the same thing that Human.__init__ does.
After you create the instance, your code needs to have logic to handle Cat, Human, Dog, etc. In some cases that's Ok (e.g. the code really only cares that it is working with an Animal), but frequently it isn't (after all, Cats can walk on fences, but Humans can't).
Generally, the principle that I like to live by is to try to make the inputs to my functions permissive (is it a list or a tuple? Who cares! Duck Typing FTW!) but to try to have really well defined outputs. I think that this makes interfaces easier to use in the long haul and the code that I wrote above would probably not pass a code review if I was the reviewer :-).
To build upon mgilson's answer
You can override the __new__ method so that you can instantiate the classes like normal without a static method.
class Animal(object):
#classmethod
def _get_all_subclasses(cls):
for scls in cls.__subclasses__():
yield scls
for scls in scls._get_all_subclasses():
yield scls
def __new__(cls, name):
cls_ = cls
for subcls in Animal._get_all_subclasses():
if subcls.__name__ in name:
cls_ = subcls
break
instance = object.__new__(cls_)
if not issubclass(cls_, cls):
instance.__init__(name)
return instance

Is there a "pythonic" approach to required properties (OOP)?

I'm struggling to find a "pythonic" approach to the following class organization:
I have a base class with properties initialized in its constructor, for example:
class Animal(object):
def __init__(self, class_, species, is_domesticated):
self.class_ = class_
self.species = species
self.is_domesticated = is_domesticated
Then, when I subclass, I would like to "hard-code" one or more of these properties, like so:
class Mammal(Animal):
def __init__(self, species, is_domesticated):
Animal.__init__(self, 'Mammal', species, is_domesticated)
A Mammal is thus instantiated like so:
monkey = Mammal('Primate', false)
The problem is, I would like to use *args so as to leave any derived classes alone when altering the base class definition. Thus the definition of Mammal becomes:
class Mammal(Animal):
def __init__(self, *args):
Animal.__init(self, *(args + (class_='Mammal',)))
Which (needless to say) looks horrible. Some tips would be appreciated =)
If you only have a fixed set of arguments in the base class, there isn't much need to worry about variable arguments. Just do what you did in your first example and it's fine. If you want to be able to randomly add arguments to the base class, but add them as positional arguments and without defaults, there's no hope; you can't just change the base class willy-nilly and expect all derived classes to keep working.
However, there is a fairly common intermediate case where you might have a large set of attributes, various combinations of which may be passed to any class in the hierarchy. You might want to add new arguments to the base class, but they'll have defaults so that derived classes don't need to know about them explicitly; they'll just gracefully degrade to the base-class default. In such a case it's usually a better idea to use **kwargs rather than *args.
class Animal(object):
def __init__(self, **kwargs):
self.class_ = kwargs['class_']
self.species = kwargs['species']
# etc.
class Mammal(Animal):
def __init__(self, **kwargs):
Animal.__init__(self, class_="Mammal", **kwargs)
This requires the arguments to passed by keyword:
>>> Animal(class_='Fish', species='barracuda', is_domesticated=False)
4: <__main__.Animal object at 0x0177ABF0>
>>> Mammal(species="monkey", is_domesticated=False)
5: <__main__.Mammal object at 0x0177AFB0>
. . . but this is better if there are a lot of them, because no one will remember which order to pass them in if you have 10 different things getting passed in positionally. It also means that you can add new arguments easily; no one has to know where in the list to put the new ones, they can just add them anywhere by keyword.
In Python 2 you have to manually extract the kwargs as I did above. In Python 3 you can use keyword-only arguments to make this even easier.
Well, why don't you just do what you said you want? Make Mammal.__init__() take a *args argument, then use that. Here's the code:
class Animal(object):
def __init__(self, class_, species, is_domesticated):
self.class_ = class_
self.species = species
self.is_domesticated = is_domesticated
def __str__(self):
s_dom = "dom" if self.is_domesticated else "wild"
return ("Animal(%s, %s, %s)" % (self.class_, self.species, s_dom))
class Mammal(Animal):
def __init__(self, *args):
Animal.__init__(self, 'Mammal', *args)
cat = Mammal("cat", True)
print(cat)
lion = Mammal("lion", False)
print(lion)
The output:
Animal(Mammal, cat, dom)
Animal(Mammal, lion, wild)

Access class instance "name" dynamically in Python

In plain english: I am creating class instances dynamically in a for loop, the class then defines a few attributes for the instance. I need to later be able to look up those values in another for loop.
Sample code:
class A:
def __init__(self, name, attr):
self.name=name
self.attr=attr
names=("a1", "a2", "a3")
x=10
for name in names:
name=A(name, x)
x += 1
...
...
...
for name in names:
print name.attr
How can I create an identifier for these instances so they can be accessed later on by "name"?
I've figured a way to get this by associating "name" with the memory location:
class A:
instances=[]
names=[]
def __init__(self, name, attr):
self.name=name
self.attr=attr
A.instances.append(self)
A.names.append(name)
names=("a1", "a2", "a3")
x=10
for name in names:
name=A(name, x)
x += 1
...
...
...
for name in names:
index=A.names.index(name)
print "name: " + name
print "att: " + str(A.instances[index].att)
This has had me scouring the web for 2 days now, and I have not been able to find an answer. Maybe I don't know how to ask the question properly, or maybe it can't be done (as many other posts seemed to be suggesting).
Now this 2nd example works, and for now I will use it. I'm just thinking there has to be an easier way than creating your own makeshift dictionary of index numbers and I'm hoping I didn't waste 2 days looking for an answer that doesn't exist. Anyone have anything?
Thanks in advance,
Andy
Update: A coworker just showed me what he thinks is the simplest way and that is to make an actual dictionary of class instances using the instance "name" as the key.
Sometimes keeping it simple is best. Having a dict that stores your instances with their names as the keys would be both straightforward and fairly simple to implement.
class A:
instances={}
def __init__(self, name, attr):
self.name=name
self.attr=attr
A.instances[name] = self
and then to get the proper instance, just...
instance = A.instances[name]
No need to put the instance dict inside the class. Just create a dict, inst in the local scope:
class A:
def __init__(self, name, attr):
self.name=name
self.attr=attr
inst={}
names=("a1", "a2", "a3")
x=10
for name in names:
inst[name]=A(name, x)
x += 1
Then, whenever you want to access a certain instance by name, just use inst[name]:
for name in names:
print inst[name].attr
Yes, the dictionary approach should work well, and can be dovetailed into the class itself.
class A:
_instances = {}
#classmethod
def get(cls, name):
return A._instances[name]
def __init__(self, name, attr):
self.name=name
self.attr=attr
A._instances[name] = self
a = A('foo', 10)
aa = A.get('foo')
If you want to play around with __new__, you can even make this transparent:
a = A('foo', 10)
aa = A('foo') # 'a' and 'aa' refer to the same instance.
This is a bit complicated, so I'll leave it to you to research (and of course ask another question on SO if you get stuck).

Categories

Resources