Using class instance as a function argument - python

Suppose I have something like:
class Dog:
name = ""
weight = 0
def __init__(self,name,wg):
self.name = name
self.wg = wg
def feed(pet):
pet.weight = pet.weight + 5
if pet.weight > 20:
print("your dog would get too fat")
else:
print("it is safe to feed your dog")
my_dog = Dog("sparky",10)
feed(my_dog)
Is there anyway to have it so that the changes to pet.weight are not transferred to my_dog.weight?
I want the function to be able to take information from a class instance, perform calculations, and then leave the original instance unchanged.

You are reassigning the value of the pet.weight attribute on this line:
pet.weight = pet.weight + 5
Doing this affects the pet instance even after the code has returned from the feed function.
To fix the problem, simply create a local name that is equal to pet.weight + 5 and work with that instead:
def feed(pet):
weight = pet.weight + 5
if weight > 20:
print("your dog would get too fat")
else:
print("it is safe to feed your dog")
Now, we use the value of pet.weight but avoid changing it.
Also, you have defined your class incorrectly. By doing this:
class Dog:
name = ""
weight = 0
you have made name and weight to be class attributes of Dog. This means that their values will be shared among all instances of Dog and modifying them will affect all instances. In other words, all of your Dogs will have the same name and weight.
But then you do:
def __init__(self,name,wg):
self.name = name
which overshadows the class attribute name with an instance attribute of the same name. Unlike class attributes, instance attributes are unique to each instance of a class. So, weight is still a class attribute (shared by all Dogs) while name is now an instance attribute (unique to each Dog).
I think it would be best if you removed those lines and wrote the Dog class like so:
class Dog:
def __init__(self, name, weight):
self.name = name
self.weight = weight
Now, name and weight are both instance attributes. Each Dog has a unique name and weight and changing either of those values on one Dog will no longer affect the other Dogs.
For more information on this, you can check out these sources:
https://docs.python.org/3/tutorial/classes.html
http://www.tutorialspoint.com/python/python_classes_objects.htm

Related

How can i add atrributes to the class method from outside class?

This is gonna be my first question on this website. If I do mistake about English, sorry.
Okey, my question is how can I add or set attribute to the class method from outside class?
If i am not wrong,we use settatr() to do this. Can you guys help me pls?
class sss():
def __init__(self,name,surname):
self.name = name
self.surname = surname
def method1(self):
a = "Python"
When we do this:
object = sss("Adam","Roger") #This 2 lines are on outside the class
setattr(object,"Age",19)
What exactly happens? Now this "Age" attribute belong to our object? What is the value of the "Age" attribute now? I mean is that class attribute or what?
My second question is how can I add attribute to the class method?
Thank you
If you instanciate your class with e.g. person = Sss("Adam", "Roger") you can access and add attributes with a dot like this:
person.age = 19
Full example:
class Sss:
# You should name your class with capital letter and without brackets
def __init__(self, name, surname):
self.name = name
self.surname = surname
person = Sss("Adam", "Roger")
print(person.name) # access attribute
person.age = 19 # add attribute
print(person.age)
As #Julian Fock mentioned, you can add attributes to an object by simply writing statements, such as person.age = 19. Your instance/object of the sss-Person class would now have this attribute, but not the class itself.
To modify the class sss-Person itself, you would need to implement some ugly methods, which would be labelled as "hacky" or "bad practices" by most programmers. Companies could refuse to accept the code as valid. You should instead consider using other methods such as class inheritance, where you can derive a modified class adding more advanced functionality.
Still, changing a class definition can be useful for debugging or exceptional emergencies. In that case, you can define external functions, and link them to override your class methods as follows:
# A) Initial Analysis
class Person:
def __init__(self,name,surname):
self.name = name
self.surname = surname
def method1(self):
a = "Python"
person1 = Person("Adam","Roger")
person2 = Person("Adam","Roger")
person1.age = 19
print(person1.age)
# Output: 19
# print(person2.age)
# AttributeError: 'Person' object has no attribute 'age'
# B) Change class attributes
def __init2__(self, name, surname, age):
self.name = name
self.surname = surname
self.age = age
Person.__init__ = __init2__
person3 = Person("Adam","Roger", 20)
print(person3.age)
# Output: 20
# person4 = Person("Adam","Roger")
# TypeError: __init2__() missing 1 required positional argument: 'age'
# C) Overwrite method1
def method2(self):
self.a = "Python"
Person.method1 = method2
person5 = Person("Adam","Roger",44)
person5.method1()
print(person5.a)
# Output: "Python"
What is "Age" attribute now? I mean is that class attribute or what?
Age is now an instance variable on your instance of sss named object.
How can i add attribute to the class method?
I'm not sure what you're asking for here.
PS.: I will not get into the merit of wether it is recommended to do things that I will exemplify here.
Adding to Bill Lynch's answer
What is "Age" attribute now? I mean is that class attribute or what?
Age is now an instance variable on your instance of sss named object.
PS.: Don't use object for it is a keyword in python.
What is the value of the "Age" attribute now?
The value is 19.
I mean is that class attribute or what?
It is a attribute of the instance you created from the class, and only for that instance.
how can I add attribute to the class method?
If you want to add the attribute to the class so that every instance has a new attribute of your choosing, you can do this:
setattr(sss, 'b', 'Python')
obj1 = sss("Adam", "Roger")
print(obj1.b)
obj2 = sss("Adam", "Roger")
print(obj2.b)
obj2.b = "New"
print(obj1.b) # will still be Python
print(obj2.b) # now will be New
If you want to overwrite method1 with a new method:
sss_obj = sss("Adam", "Roger")
def method1(self):
self.b = 'New'
setattr(sss, 'method1', method1)
print('Should be False because the instance has no b attribute yet.', hasattr(sss_obj, 'b'))
sss_obj.method1()
print('Now it has and b value is', sss_obj.b)
If you want to change the method on a specific instance of class sss:
sss_obj = sss("Adam", "Roger")
sss_obj2 = sss("Adam", "Roger")
def method1():
sss_obj2.b = 'New'
setattr(sss_obj2, 'method1', method1)
sss_obj.method1()
print('Should be False because method1 from instance sss_obj does not set b.', hasattr(sss_obj, 'b'))
sss_obj2.method1()
print('Now on instance 2 the b value is', sss_obj2.b)
If you want to change the source of the method as a string programatically:
sss_obj = sss("Adam", "Roger")
new_method_source = 'self.b = "NotRecommended"\n' \
'print("Got into changed method")'
def method1(self):
return exec(
compile(new_method_source, '', mode='exec'),
None,
{
'self': self
}
)
setattr(sss, 'method1', method1)
If you want to change the code from method1, first grab the source with the following line
method1_src = inspect.getsource(sss_obj.method1)
Then change the string as you will and do a similar thing as previously mentioned (compile and stuff).
Cheers

In Python, do objects have their own copies of class variables?

This is a question which confuses every Python Intermediate learner, so please give a brief (and idiot-friendly) answer.
I wanted to create a variable which increments the variable population by 1 when a new object is created.
class Human:
population = 0
# initialization.
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
def increment_pop(self):
self.population += 1
# class Human ends.
person = Human('Tom', 22, 'M')
person.increment_pop()
person.increment_pop()
print('Population : ', person.population)
person2 = Human('Anna', 24, 'F')
person2.increment_pop()
print('Population : ', person2.population)
print(Human.population)
Output :
Population : 2
Population : 1
0
So both the object and the class has the variable population? What is the difference between the variable population and the variables inside the init() method?
I know that only instance variables are inside the init() method.
It's a bit more complex than that. There are both class variables and instance variables, but what's happening in your example is that the class variable is being overridden by the instance variable.
Consider the following:
>>> class Foobar:
... my_list = []
...
>>> foo = Foobar()
>>> Foobar.my_list.append('hello')
>>> foo.my_list.append('world')
>>> foo.my_list
['hello', 'world']
As you can see the Foobar class and foo instance share a variable here. So no, instantiating a class does not "copy" all the class variables. Consider this, however:
>>> foo.my_list = ['spam', 'eggs']
>>> foo.my_list
['spam', 'eggs']
>>> Foobar.my_list
['hello', 'world']
Now we have two variables, one that's a class variable, the other that's an instance variable. They are no longer the same.
If you wanted to always use class variables, the most elegant thing would be to use class methods. Eg,
class Human:
population = 0
# initialization.
def __init__(self, name, age, gender):
self.name = name
self.age = age
self.gender = gender
#classmethod
def increment_pop(cls):
cls.population += 1
Now because increment_pop is a class method, it will always operate on the class, not the instance. However, the population attribute will be available to instances and as long as they don't overwrite it, it will be the same as the class variable.
A few important things to keep in mind:
Primitives, like integers and strings, are immutable. When you do foo += 1, you're creating a new instance of an integer and replacing the old one. You're not modifying it in place.
Instances can access class variables and class methods with self.whatnot. But if you want to be unambiguous, you can also reference them as ClassName.whatnot.
Class methods can be called from instances, similarly, like self.class_method().
Other notes on Python variables
Let's back up here and consider how Python resolves a request for something like my_human.population on the instance:
First, Python looks for an instance attribute named population. If it exists, that's the value you get.
Second, Python looks for a class attribute named population. If that exists, that's the value you get.
So, when you have no assigned population on your instance, and you access self.population, since no instance attribute exists with that name, you get the class attribute.
However, once you assign an instance attribute to your object, that second step above never happens.
You can inspect this at runtime, too:
>>> class Human:
... population = 0
...
>>> my_human = Human()
>>> 'population' in Human.__dict__
True
>>> 'population' in my_human.__dict__
False
So population only exists on the Human class, not on the instance. When we access Human.population, it finds the class attribute:
>>> my_human.population
0
But what happens if I create an instance attribute?
>>> Human.population = 100
>>> my_human.population = 50
>>> Human.population
100
>>> my_human.population
50
Now, because the instance has an attribute matching the name population, when you access my_human.population, it never looks up what's on the class.
self references a specific instance of a class. When you call increment_pop, you are using self.population, which is causing the increment only of that instance. Because in python you do not need to call initializers, increment_pop is created a new variable called self.population, where the class variable population should be global.
Change increment_pop to have cls.population rather than self.population and it should work as intended. Note that in the function definition you'll need the decorator #classmethod and the parameter cls.

Search for object in array by parameter in Python

I used created an array of objects belonging to a class. Each object has values that are unique to themselves. I need to find class a parameter's value belongs to.
For example
class Dog:
def __init__(self, name):
self.name = name
dogNames = ["Rex", "Otis", "Max"]
dogs = []
for dog in dogNames:
dogs.append(Dog(dog))
>>> dogs
[<__main__.Dog instance at 0x1181c5cf8>, <__main__.Dog instance at 0x117c7a050>, <__main__.Dog instance at 0x117d169e0>]
I would need to find which object has the name "Rex" assigned to it.
What is the most Pythonic way of doing this ?
if the names are unique, don't create a list, create a dictionary:
dog_names = ["Rex", "Otis", "Max"]
dogs = {name:Dog(name) for name in dog_names}
now you can perform fast dict lookup with default value to None if name not found:
rex_object = dogs.get("Rex")
if you want to keep your list, the same thing can be achieved (but with O(n) complexity with a generator expression and next defaulting to None if no dog is called "Rex":
next((d for d in dogs if d.name=="Rex"),None)
the most pythonicway is to use repr dunder method in your Dog class
class Dog:
def __init__(self,name):
self.name = name
def __repr__(self):
return 'dog name is {}'.format(self.name)
A simple way to do it is through the existing for loop that you have and just throw in an if statement to check if the name equals the name you're looking for and assign it to a variable.
find = ""
for dog in dogNames:
if dog.name == "Rex":
find = dog

Change attribute in multiple objects

I'm learning OOP in Python and I get stucked with one thing.
I have an example class:
class Animal:
def __init__(self, name="", hunger=0):
self.name = name
self.hunger = hunger
def eat(self):
self.hunger += 1
And some objects:
dog = Animal("dog")
cat = Animal("cat")
giraffe = Animal("giraffe")
I would like to use method eat() to change value of hunger in every single one of them at one blow. I have already tried to do something like this:
Animal.eat()
But it doesn't work (there's TypeError, because of missing argument 'self').
Also:
Animal.hunger += 1
Doesn't work (returns AttributeError).
If anyone has any ideas, I would be very grateful!
You can maintain a class variable that collects the instances and adjust all of their hungers in eat:
class Animal:
instances = []
def __init__(self, name="", hunger=0):
self.name = name
self.hunger = hunger
Animal.instances.append(self)
def eat(self):
for i in Animal.instances:
i.hunger += 1
Semantically, you might want to make it a classmethod, though
#classmethod
def eat(cls):
for i in cls.instances:
i.hunger += 1
You can still call it on instances if you so wish.
#schwobaseggi has the most straightforward answer for what you want to do, but what you want to do seems like it's asking for trouble. You have one class that does two very different things. Animal is an animal that has a name and eats, and it also keeps track of every animal instance and makes all of them eat. Animal is trying to do what individual animals do and also control a group of animals.
It might be better to split this into two different kinds of objects: An animal, and some sort of AnimalGroup like Zoo or Farm or Herd. The AnimalGroup class should be responsible for keeping track of a bunch of instances and make them all do stuff.
class AnimalGroup(object):
def __init__(self, animal_list):
self.animals = animal_list[:]
def add_animal(self, animal):
self.animals.append(animal)
def all_eat(self):
for animal in self.animals:
animal.eat()
then
dog = Animal("dog")
cat = Animal("cat")
giraffe = Animal("giraffe")
group = AnimalGroup([dog, cat, giraffe])
group.all_eat()
group.add_animal(Animal("pig"))
group.all_eat()
This separates out the responsibilities of each class and makes things much easier to change later on. You can now have different group behaviors without ever needing to change the animal class. You can have new animal classes that inherit from Animal and you don't need to worry about side effects. for example: class Mammal(Animal) . When I call Mammal.eat, will it update all animals? It might. class variables can be a bit tricky like that. Should it update all animals? No idea. With an AnimalGroup object, you don't need to worry.
You actually have to call it on the object itself like this:
cat.eat()
dog.eat()
giraffe.eat()
otherwise it doesn't know which object to actually change. You could store all your Objects in an array and loop over that array to call the function on all of them one after another:
dog = Animal("dog")
cat = Animal("cat")
giraffe = Animal("giraffe")
animals=[dog, cat, giraffe]
for animalType in animals:
animalType.eat()
now you can do them all at once or one at a time if you want. You will however need to addnew animals to the array after you create them to keep the list up to date:
fish=new Animal("fish")
animals.append(fish)
class Animal(object):
hunger = 0
def __init__(self, name=""):
self.name = name
def eat(self):
Animal.hunger = Animal.hunger + 1
dog = Animal("dog")
cat = Animal("cat")
giraffe = Animal("giraffe")
dog.eat()
print("Dog's hunger variable is", dog.hunger)
1
dog.eat()
print("Dog's hunger variable is :",dog.hunger)
2
print("Cat's hunger variable is :",cat.hunger)
2
print("Giraffe's hunger variable is :", giraffe.hunger)
2
When eat() is called on a single instance, the hunger variable is updated for all instances!
If you're wanting to do something on the class you have to declare it as a class variable:
class Animal:
hunger = 0
def __init__(self, name=""):
self.name = name
#classmethod
def eat(klass):
klass.hunger += 1
This way anytime you call Animal.eat() you'll be referencing the class method that modifies your class variable. You can still access the variable from within an Animal class with self.hunger but I would advise against that as it can get confusing coming back and trying to determine what's a class variable and what's a member variable.
To the best of my knowledge (and I really like OOP in python), the only way to do this is to create a new class with that specific attribute a.k.a.
class Animals:
def __init__(self, animals):
self.animals = animals
def all_eat(self):
for animal in animals:
animal.eat()
Then what you would have to do is:
dog = Animal("dog")
cat = Animal("cat")
giraffe = Animal("giraffe")
animals = Animals((dog, cat, giraffe))
animals.all_eat()
The reason for this is that python classes themselves do not have callable attributes so you have to call each instance of the class separately.

Prepopulating instances in code for classes (Python)

I am trying to make it so that when I can run a function which would populate a class with a couple of instances which are contained within the code.
class Pets(object):
def __init__(self, name, scientific_name, feet_number, type)
super(Pets,self).__init__()
self.name = name
self.scientific_name = scientific_name
self.feet_number = feet_number
self.type = type
This is the point where I get stuck.
I want to make a function which has a list of instances (Ex. a dog, a cat, a horse...) so that when the function is run those instances can be accessed immediately.
I know from places like Creating dynamically named variables from user input (Second Paragraph, First Sentence), that what I'm asking for is possible, I just don't know the syntax for it.
Is this what you are trying to do?
class Pet(object):
def __init__(self, details):
self.name = details[0]
self.scientific_name = details[1]
self.feet_number = details[2]
self.type = details[3]
if __name__ == '__main__':
pet_list = [('Cat', 'Kitty Cat', 4, 'Cuddly'), ('Dog', 'Puppy Wuppy', 3, 'Licky')]
pets = [Pet(item) for item in pet_list]
Which gives you:
pets
> [<__main__.Pet at 0x8134d30>, <__main__.Pet at 0x8134d68>]
pets[0]
> <__main__.Pet at 0x8134d30>
pets[0].name
> 'Cat'
pets[0].scientific_name
> 'Kitty Cat'
pets[1].name
> 'Dog'
There are a lot of ways this could be put together depending on what you want to do. For example, you could make a master class called Pet() with some basic attributes and methods that are true for all pets, then create specific classes for each pet that inherit the base class, e.g. class Cat(Pet):
Or you could give the Pet class the ability to know all the other details depending on what name is passed into it, then populate the instance variables accordingly.

Categories