understanding this Python script - python

I got this code and i'm trying to figure out how this works
class Animal(object):
population = 0
def __init__(self, name):
self.name = name
def __str__(self):
return "I am an instance of {}. ".format(self.__class__, self.name)
def __repr__(self):
return self.__str__()
def make_sound(self):
return "{} is trying to speak, but its method doesn't do much".format(self.name)
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name)
self.breed = breed
def __str__(self):
print(super().__str__())
return "My breed is {}".format(self.breed)
def make_sound(self):
return "{} says woof!".format(self.name)
class Cat(Animal):
pass
animals = {'Felix': ('Cat', None), 'Fido': ('Dog', 'mutt'), 'Charlie': ('Dog', 'spaniel')}
animals_list = []
for k in animals:
if animals[k][1]:
animals_list.append(globals()[animals[k][0]](k, animals[k][1]))
else:
animals_list.append(globals()[animals[k][0]](k))
Animal.population+=1
for animal in animals_list:
print(animal)
print(animal.make_sound())
print("Animal population is {}".format(Animal.population))
How are the classes initiated? I can't get my head around how this is done, seems the classes dog and cat are already initiated when the script reaches the if animals part.
Thanks in advance

The classes are instantiated inside this for loop:
for k in animals:
if animals[k][1]:
animals_list.append(globals()[animals[k][0]](k, animals[k][1])) # Instantiates a class
else:
animals_list.append(globals()[animals[k][0]](k)) # Instantiates a class
So, if k is 'Felix', this code is saying:
if None: # Because animals['Felix'][1] is None
animals_list.append(globals()['Cat']('Felix', None))
else:
animals_list.append(globals()['Cat']('Felix'))
globals() returns a dictionary containing the name -> object mapping of all variables in the global namespace. So, globals()['Cat'] returns the actual Cat class. That means globals()['Cat']('Felix') is equivalent to Cat('Felix'). And that, obviously, is instantiating the Cat class with the name 'Felix'. This same pattern repeats for every entry in the animals dict, so in the end, the animals_list contains the instances returned by calling Cat('Felix'), Dog('Fido', 'mutt'), and Dog('Charlie', 'spaniel').
And for what its worth, I agree with the commentors who have pointed out this code is quite ugly. It makes much more sense to do this:
animals = {'Felix': (Cat, None), 'Fido': (Dog, 'mutt'), 'Charlie': (Dog, 'spaniel')}
animals_list = []
for k in animals:
if animals[k][1]:
animals_list.append(animals[k][0](k, animals[k][1]))
else:
animals_list.append(animals[k][0](k))
No need to use globals().

Related

Python: pass a class-function as a reference to an external function

I have a class with several functions.
From outside that class, I want to specify with a reference which function to call - but I'm not sure how.
For example, I have an Animal class with two functions sound and food. I want to write a Zoo class which receives one of the Animal's functions as input and applies that function to every animal instance it holds (the function all_animals_features).
class Animal:
def __init__(self, sound, food):
self.my_sound = sound
self.my_food = food
def sound(self):
# Do some complicated stuff....
return self.my_sound
def food(self):
return self.my_food
class Zoo():
def __init__(self, animals):
self.animals = animals
def all_animals_features(self, f):
return [animal.f() for animal in self.animals]
dog = Animal('Woof', 'Bone')
cat = Animal('Meow', 'Cream')
zoo = Zoo([cat, dog])
zoo.all_animals_features(Animal.sound)
But of course, 'Animal' object has no attribute 'f'.
Any idea how can this be implemented?
Clarification: if all that is needed, as demonstrated by this silly example, is just getting an attribute then it may be simpler to use getattr().
In your case you just need to adjust the way the method is called:
class Zoo():
def __init__(self, animals):
self.animals = animals
def all_animals_features(self, f):
return [f(animal) for animal in self.animals]
dog = Animal('Woof', 'Bone')
cat = Animal('Meow', 'Cream')
zoo = Zoo([cat, dog])
print(zoo.all_animals_features(Animal.sound))
Output:
['Meow', 'Woof']
Since you supply Animal.sound, as parameter f, the call in the list comprehension is: f(animal)

How print call work on my newly created Python Class

I am trying to learn how classes work on Python and new to this, I have the following class defined as 'Animal':
class Animal(object):
def __init__(self, size, color, mood):
# init: consists of statements that bind the parameters passed to init to the instance o f the class, when an instance
# is created.
self.size = size
self.color = color
self.mood = mood
self.alive = True
def feeling(self):
# first method, methods are limited to the class, that is why their name does not need to be unique.
return "The", self.color, str(self), " is feeling", self.mood, "."
def colors(self, other):
return "The", str(self), "is", self.color, "and the", str(other), "is", other.color, "."
I then create an instance of the Animal object as follow:
hippo = Animal("large", "purple", 'amused')
Finally I call a method on my object as follow:
print(hippo.feeling())
My expectation is to get an output like below:
"The purple hippo is feeling amused."
But what I get in output if I print the same argument as above is:
('The', 'purple', '<__main__.Animal object at 0x7f43cc978160>', ' is feeling', 'amused', '.')
Can someone explain please why the output is similar to a list? also why str(self) returned the object name rather than the word hippo.
The original code in the tutorial was written in Python 3.5, I thought that may have caused it, but I tried the online IDE on https://www.jdoodle.com/python3-programming-online/ for Python 3.5.1 and the result was the same.
You need to pass the animal name when you initialize it — the class won't know the variable name.
class Animal(object):
def __init__(self, name, size, color, mood):
# init: consists of statements that bind the parameters passed to init to the instance of the class, when an instance
# is created.
# pass the name to the class
self.name = name
self.size = size
self.color = color
self.mood = mood
self.alive = True
def feeling(self):
# first method, methods are limited to the class, that is why their name does not need to be unique.
return str("The " + self.color + " " + self.name + " is feeling " + self.mood + ".")
def colors(self, other):
return str("The ", self.name, " is " + self.color + " and the " + other.name, " is ", other.color, ".")
The output:
hippo = Animal("hippo", "large", "purple", 'amused')
print(hippo.feeling())
# The purple hippo is feeling amused.
Your method uses commas to separate arguments. Use an f-string, and print instead of returning, as such:
print(f”The {self.size} {self.color} Animal is feeling {self.mood}.”)
Also, you expect self to somehow return the name of the variable. Instead, pass the animal type to the function in init.
As answered by # Ch3steR, you can use __repr__ or __str__. Both serve for the purpose. example is as below:
>>> class Test:
... def __repr__(self):
... return "Test()"
... def __str__(self):
... return "member of Test"
...
>>> t = Test()
>>> t
Test()
>>> print(t)
member of Test
There are few things to address here
The value return from the def feeling(self) function is a tuple, whenever the return values are seprated by a comma, the function returns a tuple consiting of all the return values. Hence we you try to print it it comes looking like a list. So either return a string or modify how you are printing the returned values from the tuple.
You want to get the instance name of class object when you are printing it. i.e
hippo = Animal("large", "purple", 'amused') for this you expect str(self) to return the instance name, which is a bit non trivial. First define this funtion def __str__(self): to get desired output whenever you use str(self). Second to get the variable name either supply the name as a class variable in constructor and use it, which would be easier way or you can use python-varname package to use the varname() function to acheive this.
class Klass:
def __init__(self):
self.id = varname()
k = Klass()
# k.id == 'k'
Expandable using list comprehension,
class Animal:
def __init__(self, name, size, color, mood):
self.name = name
self.size = size
self.color = color
self.mood = mood
self.alive = True
def feeling(self):
# first method, methods are limited to the class, that is why their name does not need to be unique.
return str(f"The {self.color} {self. name} is feeling {self.mood}.")
def colors(self, other):
return str(f"The {self.name} is {self.color} and the {other.name} is {other.color}.")
n = int(input("No of Animals:"))
Animals = []
for i in range(n):
print(f"Enter Details for Animal No.{i+1}")
s = Animal(*[input(f'Enter {info}: ')for info in ["Name", "Size", "Color", "Mood"]])
Animals.append(s)
for i in range(len(Animals)):
print(f"\nAnimal {i+1}")
print(Animals[i].feeling())
No of Animals:1
Enter Details for Animal No.1
Enter Name: Hippo
Enter Size: large
Enter Color: purple
Enter Mood: happy
Animal 1
The purple Hippo is feeling happy.
[Program finished]

How to not run init based on __new__ method

I have the following class, which acts as a collection of people:
class Person:
PERSONS = dict() # name ==> instance
def __new__(cls, *args, **kwargs):
name = kwargs.get('name') or '' if not args else args[0]
print ('Name: %s' % name)
if name in cls.PERSONS:
print ('Returning found person!')
return cls.PERSONS[name]
else:
print ('Initializing new person')
return super(Person, cls).__new__(cls)
def __init__(self, name):
print ("Running init")
self.name = name
Person.PERSONS[name] = self
If a person is found, it returns that person, otherwise it creates a new one. And when I run it it works:
>>> p1 = Person('Julia')
Name: Julia
Initializing new person
Running init
>>> p2 = Person('Julia')
Name: Julia
Returning found person!
Running init # <== how to get this not to run?
>>> p1 is p2
True
However, if the person is found, I don't want the __init__ method to run. How would I "skip" the init method based on the return of the __new__ ?
One option is to add a conditional in the __init__, such as:
def __init__(self, name):
if name in Person.PERSONS: return # don't double-initialize
print ("Running init")
self.name = name
Person.PERSONS[name] = self
But I was hoping there might be a cleaner approach.
#MadPhysicist's idea of using a metaclass with a custom __call__ method is correct but the implementation included in the answer is quite off. Instead, the custom __call__ method should use the name of the person, rather than a new Person object, to check if a given name has an existing entry in the PERSONS dict:
class PersonMeta(type):
def __call__(cls, name):
print ('Name: %s' % name)
if name in cls.PERSONS:
print ('Returning found person!')
return cls.PERSONS[name]
print('Initializing new person')
obj = cls.__new__(cls, name)
cls.__init__(obj, name)
cls.PERSONS[name] = obj
return obj
class Person(metaclass=PersonMeta):
PERSONS = dict() # name ==> instance
def __init__(self, name):
print ("Running init")
self.name=name
p1=Person('Julia')
p2=Person('Julia')
print(p1 is p2)
This outputs:
Name: Julia
Initializing new person
Running init
Name: Julia
Returning found person!
True
Instead of trying to skip __init__, put your initialization in __new__. In general, most classes should only implement one of __new__ and __init__, or things get messy.
Also, trying to have a class act as a collection of anything is usually a bad idea. Instead of trying to make your class itself manage its instances, it tends to be a better idea to give that role to a dedicated collection object. This makes it easier to manage object lifetimes, have multiple containers, avoid weird __new__ problems, etc.
The problem I find in your approach is that the __new__ dunder method is triggered just before the __init__. Once said that, it's not that easy to change that behavior.
Instead of handling the new Person's creation inside __new__, create a class method (e.g. create_person) and update the PERSONS dict if needed.
class Person:
def __init__(self, name):
print("Running init\n")
self.name = name
class PersonFactory:
PERSONS = dict()
#classmethod
def create_person(cls, name):
print('Name: %s' % name)
if name in cls.PERSONS:
print ('Returning found person!')
return cls.PERSONS[name]
print('Initializing new person')
cls.PERSONS[name] = Person(name)
return cls.PERSONS[name]
if __name__ == '__main__':
PersonFactory.create_person('Julia')
PersonFactory.create_person('Julia')

Calling list of instances in a function of a class

I am learning Python using "Learn Python the Hard Way". Programming is very new to me. I'm at exercise 42. I'm asked to do the following:
Make some new relationships that are lists and dictionaries so you can also have "has-many" relationships.
I want the program to print the names of all of Frank's pets. However, I get an error saying 'AttributeError: 'list' object has no attribute 'callpet'.
I understand the problem is that I'm using a list. When I write frank.pet = satan there is no problem at all. But I want Frank to have more than one pet, and I want the program to print the names of those pets.
This is the full code I am using:
class Person(object):
def __init__(self, name):
self.name = name
self.pet = None
def callme(self):
return self.name
def petsyay(self):
namepet = self.pet.callpet()
return namepet
class Pets(object):
def __init__(self, petname):
self.petname = petname
def callpet(self):
return self.petname
frank = Person("Frank")
poekie = Pets("Poekie")
satan = Pets("Satan")
frank.pet = [poekie, satan]
print frank.petsyay()
What I understand is that I need to split the list or something. So I've tried the following:
def callpet(self):
for eachpet in petname:
return self.petname
Put that just gets the same error. I'm confused, what am I doing wrong here?
Not true after edits (do note that his return is sometimes a petname and sometime a list of petnames, this is not good): [If you call Mr. E's answer for a single Pets only you will most likely recieve an error TypeError: 'Pets' object is not iterable. His answer will only work as long as self.pets is a list and will fail when they're not.]
If you want to support singular and multiple Pets at the same time, either always make sure your self.pet is a list or make sure you check before you process them. Kepp in mind that it's important that you have a constant type output exiting your function because otherwise it makes other people's lives hard.
I recommend that you make self.pets a mandatory list in __init__
class Person(object):
def __init__(self, name):
self.name = name
self.pet = None
def callme(self):
return self.name
def petsyay(self):
if isinstance(self.pet, list):
namepet = []
for pet in self.pet:
namepet.append(pet.callpet())
elif isinstance(self.pet, Pets):
namepet = []
namepet.append(self.pet.callpet())
else:
raise ValueError("Not a correct type. Send Pets or list of Pets")
return namepet
class Pets(object):
def __init__(self, petname):
self.petname = petname
def callpet(self):
return self.petname
frank = Person("Frank")
poekie = Pets("Poekie")
satan = Pets("Satan")
frank.pet = [poekie, satan]
print frank.petsyay()
frank.pet = poekie
frank.petsyay()
print frank.petsyay()
I debugged your program and what was happening was that you did not have a setPets method in your person object so self.pets was never getting set with the list of pets. It was constantly None. I added a setPets method as well as updating the petsYay method to return a list. You could also use yield and return a generator to loop through outside of the class. Heres the updated code:
class Person(object):
def __init__(self, name):
self.name = name
self.pets = None
def setPets(self, p):
self.pets = p
def callme(self):
return self.name
def petsyay(self):
tempL = []
if self.pets is not None:
for pet in self.pets:
tempL.append(pet.callpet())
return tempL
class Pets(object):
def __init__(self, petname):
self.petname = petname
def callpet(self):
return self.petname
frank = Person("Frank")
poekie = Pets("Poekie")
satan = Pets("Satan")
frank.setPets([poekie, satan])
print frank.petsyay()
The problem is that frank.pet is a list of Pets. To do what you want you must redefine petsyay to work with a list of Pets, and return a list of names
def petsyay(self):
return [p.callpet() for p in self.pet]
If you want to work with both lists of pets or single pet, you could handle the exception
def petsyay(self):
try:
return self.pet.callpet()
except AtributeError: #Just handle the AttributeError to prevent hiding another exception
return [p.callpet() for p in self.pet]

python add object to variable when using deepcopy

I've got a few questions. Keep in mind i need to use deep copy as my classes will be expanding in complexity.
Is there a way to make it not reach the recursion limit when i do a deep copy?
When I preform a deep copy I want the new copy to be appended to the NODES variable just like it does in the init?
import copy
# Global
NODES = []
# Classes
class Node(object):
def __init__(self, name, age):
self.name = name
self.age = age
class Truck(Node):
def __init__(self, name="", age=0):
super(Truck, self).__init__(name=name, age=age)
NODES.append(self)
class Car(Node):
def __init__(self, name="", age=0):
super(Car, self).__init__(name=name, age=age)
NODES.append(self)
def __deepcopy__(self, memo):
print '__deepcopy__(%s)' % str(memo)
return Car(copy.deepcopy(self, memo))
Truck( name="Tonka Truck")
Truck( name="Monster Truck")
Truck( name="Pickup Truck")
car = Car( name="Oldsmobile Car")
car.age = 55
new_car = copy.deepcopy( car )
type_name = "Car"
cars = [x for x in NODES if type(x).__name__ == type_name]
print cars
print "NODES:"
for node in NODES:
print "\t", node.name, node.age
First, you really should use a defaultdict for Toys. It just meet this requirement If the superclass doesn't exists, it adds and appends the object. So let's go with
Toys = collections.defaultdict(list)
If you did not want to use copy.deepcopy, you could simply change Node.__init__ method to:
class Node(object):
def __init__(self, name, superclass):
self.name = name
self.superclass = superclass
Toys[superclass].append(self)
It works fine when you create a new Truck:
t = truck()
Toys['Trucks'][-1] is t
gives True
Unfortunately, deepcopy uses a special construction scheme and bypasses __init__ here.
But when __init__ can't do, just call __new__ to help...
__new__ is a lower level special method called as a class method to create the object before __init__ is called. And even deepcopy created objects are created with __new__. As it is a class method, you just need to declare the superclass names (BTW a superclass is another animal and you really should use a different name...) as a class attribute.
You code becomes:
import copy
import collections
# Globals
Toys = collections.defaultdict(list)
class Node(object):
def __new__(cls):
obj = super(Node, cls).__new__(cls)
superclass = cls.superclass
Toys[superclass].append(obj)
return obj
def __init__(self, name=""):
self.name = name
class Truck(Node):
superclass = "Trucks"
class Car(Node):
superclass = "Boats"
class Boat(Node):
superclass = "Nodes"
class Plane(Node):
superclass = "Planes"
t = Truck()
t.name = "Tonka Truck"
print Toys
t2 = copy.deepcopy( t )
print t, t2, Toys
With this output:
defaultdict(<type 'list'>, {'Trucks': [<__main__.Truck object at 0x0000000002D71A20>]})
<__main__.Truck object at 0x0000000002D71A20> <__main__.Truck object at 0x0000000002D71B70> defaultdict(<type 'list'>, {'Trucks': [<__main__.Truck object at 0x0000000002D71A20>, <__main__.Truck object at 0x0000000002D71B70>]})
That proves that:
Trucks list has automatically been added to Toys
t created as Truck() as been correctly added to Toys['Trucks']
t2 create with deepcopy as been correctly added to Toys['Trucks']
You now just have to change to superclass name for this code to be acceptable...

Categories