So I've been creating Person classes, which hold variables like name and age. I would then create multiple Person classes which have different variables unique to each class. What I want to add next is to give each Person a "relationship" variable shared between each of these classes.
For example:
The relationship between George and Alex is -31.
How would I approach this? I've thought about just making a variable for each object, but this doesn't seem convenient when there's just too much Person classes in the program.
First, I'm not sure you want to make each person a different class. The idea behind classes is that a class describes a type of object (like a Person), and then each Person is an instance of that type of object (like George).
So if your Person class is like:
class Person:
def __init__(name: str, age: int):
self.name = name
self.age = age
then you might have:
george = Person("George", 20)
alex = Person("Alex", 22)
Both george and alex are instances of Person, not their own class. This make sense so far?
Now, the way you'd add a relationship might be to have a dictionary that associates other Persons with this relationship score. While we're at it, let's add a __repr__ function to our class so we can pretty-print our people:
class Person:
def __init__(name: str, age: int):
self.name = name
self.age = age
self.relationships: Dict["Person", int] = {}
def __repr__(self) -> str:
return "%s, age %d" % (self.name, self.age)
Now you can do:
george.relationships[alex] = -31
alex.relationships[george] = -31
If we print out George's relationships, we get:
>>> print(george.relationships)
{Alex, age 22: -31}
And you can do this regardless of how many people and how many relationships between them you want to model (a dictionary can hold an arbitrary number of values associated with an arbitrary number of keys, which in this case is a Person instance).
Assuming that a person can have a "relationship" with more than one other person, you will need a dictionary to store all of the relationships that a person has. I suggest mapping the other person's name to the "relationship" number (-31 in your example).
class Person:
def __init__(self, name):
self.name
self.relationships = dict()
def set_relationship(self, other, value):
self.relationships[other.name] = value
other.relationships[self.name] = value
def get_relationship(self, other):
return self.relationships[other.name]
def has_relationship(self, other):
return other.name in self.relationships
The set_relationships method updates both objects' dictionaries, because the way you've described the relationship, it should be symmetric.
Usage:
>>> alice = Person('alice')
>>> bob = Person('bob')
>>> clive = Person('clive')
>>> alice.set_relationship(bob, 12)
>>> alice.relationships
{'bob': 12}
>>> bob.get_relationship(alice)
12
>>> clive.has_relationship(alice)
False
If you genuinely need each person to be represented by a different class (instead of just objects of the same class), you can write your other classes as subclasses of Person. That way you won't have to duplicate the code above across many different classes.
Related
I came across code which makes the class Person allow iteration here and here. It worked great for a single class, but I tried to modify it to allow iteration of multiple classes (separately) by changing _ClassRegistry to a dict, which would store
{class_str_name_1: [instance_obj_1, instance_obj_2, ...], ...}
instead of a list. My code is:
class ClassIter(type):
def __iter__(cls):
# iterate over the list in the dict value
return iter(cls._ClassRegistry[cls.__name__])
def __len__(cls):
return len(cls._ClassRegistry[cls.__name__])
class Person(metaclass=ClassIter):
_ClassRegistry = {}
def __init__(self, name, age):
if type(name).__name__ in self._ClassRegistry.keys():
self._ClassRegistry[type(self).__name__].append(self)
else:
self._ClassRegistry[type(self).__name__] = [self]
self.name, self.age = name, age
class Dog(metaclass=ClassIter):
_ClassRegistry = {}
def __init__(self, name, age, owned_by):
if type(name).__name__ in self._ClassRegistry.keys():
self._ClassRegistry[type(self).__name__].append(self)
else:
self._ClassRegistry[type(self).__name__] = [self]
self.name, self.age = name, age
self.owned_by = type(owned_by).__name__
Alice = Person("Alice", 20)
Bob = Person("Bob", 25)
Spud = Dog("Spud", 6, Alice)
Buster = Dog("Buster", 12, Bob)
for p in Person:
print(p.name, p.age)
for d in Dog:
print(d.name, d.age, d.owned_by)
Unfortunately this did not work, and the output is
Bob 25
Buster 12 Person
while I was hoping it would produce
Alice 20
Bob 25
Spud 6 Alice
Buster 12 Bob
so not only is it using the class name Person instead of the name attribute (which is probably an easy fix but I've lost myself in the code) but also not storing the previous instances.
How can I either:
(1) convert this program to a class decorator (such as #iterator; class Person:) to make it easier to implement this for multiple classes? or
(2) fix the program to work as expected using the current implementation?
Any help much appreciated, I am unexperienced in OOP.
I want a method in my class that creates new objects from user-inputs and adds them to a dictionary (this may not be the best way of doing this, appreciate any input). Further I want to be able to still get the attributes of the objects in the dictionary for other functions in a simple way which to me is the hardest part.
I have looked around for any similar problems but cant seem to find any, while this issue seems pretty general to me I believe there is an easy way to do this or i'm just doing something very wrong. If i just add the objects to a dictionary I just the "position" of the object but I want the specific attributes added to the dictionary based on name and species as given below.
animals = {}
class animal:
def __init__(self,name,species):
self.name = name
self.species = species
def dict(self,name,species):
#i want this function to create and add new objects to animals
For a given user input of name and species I would like for the output of dict() to add the new object to a dictionary and then somehow for example being able to loop through the dictionary for the names of all animals in the dictionary. Thank you so much in advance.
I am not sure why you want to do this, but you can use a class method to effectively make a new constructor that also takes a dictionary and mutate that dictionary inside
class Animal:
def __init__(self, name, species):
self.name = name
self.species = species
#classmethod
def init_and_track(cls, dict_, name, species):
a = cls(name, species)
dict_[name] = a
and use as so
animals = {}
Animal.init_and_track(animals, 'sally', 'horse')
as for getting user inputs, you could instead do something like
#classmethod
def init_and_track(cls, dict_):
name = input('name: ')
species = input('species: ')
a = cls(name, species)
dict_[name] = a
where the name and species are made by the user upon calling this method. I am not sure exactly what you want and why you want it this way but this is what I believe you are asking for.
Your comment suggests your original question was an xy-problem - how about something like this. Define your animal using dataclasses because it is just a holder for some data (assuming python 3.7)
from dataclasses import dataclass
#dataclass
class Animal:
name: str
species: str
def say_hi(self):
print(f'{self.name} the {self.species} says hi')
the : <...> you see are type annotations. Then define a new class to keep track of these animals.
class Zoo:
def __init__(self):
self.animals = []
def add_animal(self):
print('adding a new animal to the zoo')
name = input('what is it\'s name? ')
species = input('what species is it? ')
a = Animal(name, species)
self.animals.append(a)
def find_by_name(self, name):
animals_with_name = [a for a in self.animals if a.name == name]
return animals_with_name
def find_by_species(self, species):
return [a for a in self.animals if a.species = species]
If you only wanted to keep track of one attribute like by species, you are right that a dictionary would be best. If you wanted to keep track of more attributes do something like
def find_by_attribute(self, attribute_name, value):
return [a for a in self.animals if getattr(a, attribute_name) == value]
to avoid writing duplicate code. This should help get you started
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
Isit possible to loop through the objects of class person and extract the attributes?
so for e.g. from below code after looping the output would be for each object as such
Object mike, name-Mike, age-20
class Person(object):
def__init__(self,name,age):
self.name = name
self.age = age
mike = Person('Mike',20)
john = Person('John',20)
jack = Person('Jack',20)
adam = Person('Adam',20)
Thanks
Python classes don't automatically track all their instances. You can use a class attribute to track them yourself if you need to:
class Person(object):
people = []
def __init__(self, name, age):
self.name = name
self.age = age
self.people.append(self)
Even better would be to have a separate object that tracked people. The Person.people attribute here is a single global value for the process, which can limit the usability and testability of the class.
To loop the attributes of a given instance, I think you're looking for the builtin function vars:
>>> mike = Person('Mike', 20)
>>> vars(mike)
{'age': 20, 'name': 'Mike'}
To loop through all instances of a given class is not possible, unless you add some code to maintain the list of instances yourself.
if my understanding is right, you are asking about looping the class person
then it can be done like this.
class person(object):
def __init__(self,name,age):
self.name=name
self.age=age
for name,age in {'mike':20,'john':20,'jack':20}.iteritems():
p = person(name,age)
print ('I am %s my age is %d' %(p.name,p.age))
## answer
I am mike my age is 20
I am john my age is 20
I am jack my age is 20
I'm trying to learn python and I now I am trying to get the hang of classes and how to manipulate them with instances.
I can't seem to understand this practice problem:
Create and return a student object whose name, age, and major are
the same as those given as input
def make_student(name, age, major)
I just don't get what it means by object, do they mean I should create an array inside the function that holds these values? or create a class and let this function be inside it, and assign instances? (before this question i was asked to set up a student class with name, age, and major inside)
class Student:
name = "Unknown name"
age = 0
major = "Unknown major"
class Student(object):
name = ""
age = 0
major = ""
# The class "constructor" - It's actually an initializer
def __init__(self, name, age, major):
self.name = name
self.age = age
self.major = major
def make_student(name, age, major):
student = Student(name, age, major)
return student
Note that even though one of the principles in Python's philosophy is "there should be one—and preferably only one—obvious way to do it", there are still multiple ways to do this. You can also use the two following snippets of code to take advantage of Python's dynamic capabilities:
class Student(object):
name = ""
age = 0
major = ""
def make_student(name, age, major):
student = Student()
student.name = name
student.age = age
student.major = major
# Note: I didn't need to create a variable in the class definition before doing this.
student.gpa = float(4.0)
return student
I prefer the former, but there are instances where the latter can be useful – one being when working with document databases like MongoDB.
Create a class and give it an __init__ method:
class Student:
def __init__(self, name, age, major):
self.name = name
self.age = age
self.major = major
def is_old(self):
return self.age > 100
Now, you can initialize an instance of the Student class:
>>> s = Student('John', 88, None)
>>> s.name
'John'
>>> s.age
88
Although I'm not sure why you need a make_student student function if it does the same thing as Student.__init__.
Objects are instances of classes. Classes are just the blueprints for objects. So given your class definition -
# Note the added (object) - this is the preferred way of creating new classes
class Student(object):
name = "Unknown name"
age = 0
major = "Unknown major"
You can create a make_student function by explicitly assigning the attributes to a new instance of Student -
def make_student(name, age, major):
student = Student()
student.name = name
student.age = age
student.major = major
return student
But it probably makes more sense to do this in a constructor (__init__) -
class Student(object):
def __init__(self, name="Unknown name", age=0, major="Unknown major"):
self.name = name
self.age = age
self.major = major
The constructor is called when you use Student(). It will take the arguments defined in the __init__ method. The constructor signature would now essentially be Student(name, age, major).
If you use that, then a make_student function is trivial (and superfluous) -
def make_student(name, age, major):
return Student(name, age, major)
For fun, here is an example of how to create a make_student function without defining a class. Please do not try this at home.
def make_student(name, age, major):
return type('Student', (object,),
{'name': name, 'age': age, 'major': major})()
when you create an object using predefine class, at first you want to create a variable for storing that object. Then you can create object and store variable that you created.
class Student:
def __init__(self):
# creating an object....
student1=Student()
Actually this init method is the constructor of class.you can initialize that method using some attributes.. In that point , when you creating an object , you will have to pass some values for particular attributes..
class Student:
def __init__(self,name,age):
self.name=value
self.age=value
# creating an object.......
student2=Student("smith",25)