Generate list of instances in Python [duplicate] - python

This question already has answers here:
How to avoid having class data shared among instances?
(7 answers)
Closed 8 years ago.
I'd like to take a list from football player (which have few attributes like name, goals, club...) and add them to their club (which is another class) but it seems that I'm missing something because the list of players of the club is changing in the loop even if it's not called (I think I'm not correctly managing the instances of the players).
So, here is the code :
clubsWithGoal = []
class Player:
nickname = ""
imageURL = ""
numberOfGoal = 0
clubId = ""
def __init__(self, nickname, imageURL, clubId, numberOfGoal = 0):
self.nickname = nickname
self.imageURL = imageURL
self.clubId = clubId
self.numberOfGoal = numberOfGoal
def __str__(self):
return self.nickname
class Club:
Name = ""
ImageURL = u""
id = u""
numberOfGoal = 0
listOfPlayer = []
def __init__(self, id):
del self.listOfPlayer [:]
self.id = id
self.getData()
def __str__(self):
return self.Name
def getData(self):
try:
results = json.load(urllib.urlopen(
"http://worldcup.kimonolabs.com/api/clubs/" + self.id + "?apikey={youwon'tseeit}"))
self.ImageURL = results["logo"]
self.Name = results["name"]
except:
print(self.id)
def addGoal(self, numberOfGoalsToAdd):
self.numberOfGoal += numberOfGoalsToAdd
def addPlayer(self, player):
self.listOfPlayer.append(player)
print("added "+player.nickname+" to "+self.Name)
self.addGoal(player.numberOfGoal)
print("added the "+str(player.numberOfGoal)+" of "+player.nickname+" to "+self.Name)
So here are for the model class and here is the function which must sort the players and is not working:
def createAndOrderInClub(playerlist):
foundHisClub = False
for player in playerlist:
for club in clubsWithGoal:
# Case 1: The club already exists and the player is part of the club
if player.clubId == club.id:
club.addPlayer(player)
foundHisClub = True
break
# Case 2: The club doesn't already exist
if (foundHisClub == False):
newclub = Club(player.clubId)
newclub.addPlayer(player)
clubsWithGoal.append(newclub)
And an example that it's changing inside the loop (I'm java developer and new to Python):

I think the problem is that the listOfPlayer variable in the Club class is declared as a static class member, and is not initialized inside the __init__ function. This demo http://dbgr.cc/R demonstrates the point.
Besides the error above, it also looks like you're not resetting the foundHisClub variable inside the loop back to False. I would instead declare the foundHisClub variable inside the first for loop:
def createAndOrderInClub(playerlist):
for player in playerlist:
foundHisClub = False
for club in clubsWithGoal:
# Case 1: The club already exists and the player is part of the club
if player.clubId == club.id:
club.addPlayer(player)
foundHisClub = True
break
# Case 2: The club doesn't already exist
if (foundHisClub == False):
newclub = Club(player.clubId)
newclub.addPlayer(player)
clubsWithGoal.append(newclub)

listOfPlayer = []
This is a class attribute, meaning it's shared for all instances of your Club class. If you're coming from Java you can think of this as a static class variable. To make this list unique for each Club class, make sure you initialize it in the constructor with the self prefix.
def __init__(self, id):
del self.listOfPlayer [:]
self.id = id
self.listOfPlayer = []
self.getData()
Make sure you do the same for all the other variables you've defined at the class level:
Name = ""
ImageURL = u""
id = u""
numberOfGoal = 0
Remove these, and initialize them in the constructor using self.

The listOfPlayer container, as you declared it, is a "class variable" (a bit like a static class member in java), and since lists are mutable, whenever you modify this list in any instance of Club, it will change for all instances as well.
To fix it simply remove it from the class definition, and initialize the list in the __init__ function (the strings aren't problematic since they are immutable):
class Club:
Name = ""
ImageURL = u""
id = u""
numberOfGoal = 0
def __init__(self, id):
self.listOfPlayer = []
self.id = id
self.getData()

Related

Create a method that lists instances that were set to True in a previous class

I have the following code that records job candidates' personal details in a class Employee:
class Employee:
def __init__(self, name, role, id):
self.name = name
self.role = role
self.id = id
self.interviewed = False
def __str__(self):
text = f'Candidate {self.name}; {self.id}. '
if self.interviewed == False:
return text + 'Not interviewed yet.'
else:
return text + 'Interviewed.'
def interview(self):
self.interviewed = True
I also have another class Database that lists all the candidates in a database for a particular employer:
class Database:
def __init__(self, company, employer):
self.company = company
self.employer = employer
self.candidates = []
def __str__(self):
text = f'{The hiring company is {self.company} and the employers name is {self.employer}'
return text
def add_candidate(self, candidate):
self.candidates.append(candidate)
Now, if we record personal details of two candidates in the class Employee and add them to the class Database using the method add_candidate, how do I create a new method called list_interviewed_candidates(self) in the class Database that will print all candidates that have self.interviewed set to True?
This is what I tried:
class Database:
def __init__(self, company, employer):
self.company = company
self.employer = employer
self.candidates = []
def __str__(self):
text = f'{The hiring company is {self.company} and the employers name is {self.employer}'
return text
def add_candidate(self, candidate):
self.candidates.append(candidate)
def list_interviewed_candidates(self):
for employee in self.candidates:
if employee.interviewed == True:
return employee
But that doesn't work. I have also tried list comprehension but it seems I just cannot access the boolean value that was set in the first class. Ideally, the output should look something like this:
database1 = Database('Google', 'Jack H')
print(database1)
'The hiring company is Google and the employers name is Jack H'
candidate1 = Employee('Anna S', 'web-designer', 12)
database1.add_candidate(candidate1)
print(database1.list_interviewed_candidates())
[]
candidate1.interview()
print(database1.list_interviewed_candidates())
['Candidate Ana S; 12 - Interviewed']
In your Database.list_interviewed_candidates method you are returning the first employee that was interviewed. Keep in mind that return exits from the current function (method in this case) as soon as is hit.
So it starts looking at your candidates and as soon as one interviewed is found, it returns that one.
You probably want to gather them all in a list and return that:
def list_interviewed_candidates(self):
retval = []
for employee in self.candidates:
if employee.interviewed == True:
retval.append(employee)
return retval
Something pretty interesting you could also use is yield:
def list_interviewed_candidates(self):
for employee in self.candidates:
if employee.interviewed == True:
yield employee
Which... you can try by doing:
print(list(database1.list_interviewed_candidates()))
Pretty cool. This opens the really fun world of iterators!
A list comprehension works, but realize __str__ is used by print but __repr__ is used for items displayed in a list. __repr__ is also used if __str__ isn't defined.
Try the following, but change __str__ to __repr__ in Employee:
def list_interviewed_candidates(self):
return [employee for employee in self.candidates if employee.interviewed]
Then:
database1 = Database('Google', 'Jack H')
print(database1)
candidate1 = Employee('Anna S', 'web-designer', 12)
candidate2 = Employee('Mark T', 'pythonista', 13)
database1.add_candidate(candidate1)
database1.add_candidate(candidate2)
print(database1.list_interviewed_candidates())
candidate1.interview()
candidate2.interview()
print(database1.list_interviewed_candidates())
Outputs:
The hiring company is Google and the employers name is Jack H
[]
[Candidate Anna S; 12. Interviewed., Candidate Mark T; 13. Interviewed.]
Customize __str__ and __repr__ individually if you want differing output between direct print of Employee and how it is displayed in a list.

How to call object of class by input?

I am generating a class of persons and want to get information about a certain person by input. I would like to use the str funtction because I am trying to understand it better. My Idea goes as follows:
class Person:
__init__(self, f_name, l_name):
self.f_name = f_name
self.l_name = l_name
__str__(self):
return "The persons full name is:" + f_name + l_name
person1 = Person(Peter, Punk)
person2 = Person(Mia, Munch)
person = input("What persons full name would you like to know?")
print(person) #I am aware that this just fills in the string saved in person, but how do I connect it to the variable?
another idea was to do it as follows:
#class stays the same except:
__init__(self, f_name, l_name):
self.f_name = f_name
self.l_name = l_name
list.append(self)
#and then for the main:
list = []
person1 = Person(Peter, Punk)
person2 = Person(Mia, Munch)
person = input("What persons full name would you like to know?")
index = list(person)
print(list[index])
Thankful for any edvice since I am obviously new to Python :D
I think OP has some concept problems here which this answer may go some way to help with.
Start by building a robust class definition. Simple in this case as there are just 2 attributes. Note the use of setters, getters and str, repr and eq dunder overrides.
A small function that checks if a given Person can be found in a list of Persons and reports accordingly.
Create a list with 2 different Person instances
Create another Person that is known not to match anything already in the list.
Run check()
Modify the 'standalone' Person to make it equivalent to something previously constructed.
Run check()
class Person:
def __init__(self, forename, surname):
self._forename = forename
self._surname = surname
#property
def forename(self):
return self._forename
#forename.setter
def forename(self, forename):
self._forename = forename
#property
def surname(self):
return self._surname
#surname.setter
def surname(self, surname):
self._surname = surname
def __str__(self):
return f'{self.forename} {self.surname}'
def __repr__(self):
return f'{self.forename=} {self.surname=}'
def __eq__(self, other):
if isinstance(other, type(self)):
return self.forename == other.forename and self.surname == other.surname
return False
def check(list_, p):
if p in list_:
print(f'Found {p}')
else:
print(f'Could not find {p}')
plist = [Person('Pete', 'Piper'), Person('Joe', 'Jones')]
person = Person('Pete', 'Jones')
check(plist, person)
person.surname = 'Piper'
check(plist, person)
Output:
Could not find Pete Jones
Found Pete Piper
You probably want a mapping between a name and an object. This is what Python's dict dictionary structure is for:
people = {} # an empty dictionary
people[f'{person1.f_name} {person1.l_name}'] = person1
people[f'{person2.f_name} {person2.l_name}'] = person2
This is creating a string of the first and last name.
You can then lookup the Person object using the full name:
print(people['Peter Punk'])
You could do this with list comprehension like so (also allowing multiple people to have the same first name)
class Person:
__init__(self, f_name, l_name):
self.f_name = f_name
self.l_name = l_name
__str__(self):
return "The persons full name is:" + f_name + l_name
personList= []
personList.append(Person(Peter, Punk))
personList.append(Person(Mia, Munch))
personName = input("What persons full name would you like to know?")
print([str(person) for person in personList if person.f_name == personName])

How to make each instance of a child class not require parent attributes (kinda?)

class User:
def __init__(self, username):
self.username = username
#classmethod
def get_user(cls):
return cls(input("Username: "))
class Budget(User):
def __init__(self, monthtly_income):
super().__init__(User.get_user())
self.monthly_income = monthtly_income
self.perc_lis = []
self.category_obj_lis = []
self.category_name_lis = []
self.value_lis = []
...
# creates objects and adds them to a list
for i in range(len(self.category_name_lis)):
self.category_obj_lis.append("")
self.category_obj_lis[i] = Category(self.category_name_lis[i], self.monthly_income)
...
class Category(Budget):
def __init__(self, name, monthly_income):
super().__init__(monthly_income)
self.name = name
self.perc = 0
self.amt = 0
self.left = 0
self.max = 0
# defines the percentage for each category
def get_perc(self):
self.perc = float(input(f"{self.name}: "))
When I run this it asks for username 3 times after I have asked for the categories because I create each Category object in a loop. Username is asked for at the start of the code. I need the category class to be a child class because I need the monthly_income variable to use in code not posted. I am not very good at inheritance so i could easily be missing something obvious. Is there a way for each instance of Category to not ask for username and just use the one already asked in the beginning? I apologize if this is somewhat confusing but idk where to go from here.

Return class instance instead of creating a new one if already existing

I defined a class named Experiment for the results of some lab experiments I am conducting. The idea was to create a sort of database: if I add an experiment, this will be pickled to a db before at exit and reloaded (and added to the class registry) at startup.
My class definition is:
class IterRegistry(type):
def __iter__(cls):
return iter(cls._registry)
class Experiment(metaclass=IterRegistry):
_registry = []
counter = 0
def __init__(self, name, pathprotocol, protocol_struct, pathresult, wallA, wallB, wallC):
hashdat = fn.hashfile(pathresult)
hashpro = fn.hashfile(pathprotocol)
chk = fn.checkhash(hashdat)
if chk:
raise RuntimeError("The same experiment has already been added")
self._registry.append(self)
self.name = name
[...]
While fn.checkhash is a function that checks the hashes of the files containing the results:
def checkhash(hashdat):
for exp in cl.Experiment:
if exp.hashdat == hashdat:
return exp
return False
So that if I add a previously added experiment, this won't be overwritten.
Is it possible to somehow return the existing instance if already existant instead of raising an error? (I know in __init__ block it is not possible)
You can use __new__ if you want to customize the creation instead of just initializing in newly created object:
class Experiment(metaclass=IterRegistry):
_registry = []
counter = 0
def __new__(cls, name, pathprotocol, protocol_struct, pathresult, wallA, wallB, wallC):
hashdat = fn.hashfile(pathresult)
hashpro = fn.hashfile(pathprotocol)
chk = fn.checkhash(hashdat)
if chk: # already added, just return previous instance
return chk
self = object.__new__(cls) # create a new uninitialized instance
self._registry.append(self) # register and initialize it
self.name = name
[...]
return self # return the new registered instance
Try to do it this way (very simplified example):
class A:
registry = {}
def __init__(self, x):
self.x = x
#classmethod
def create_item(cls, x):
try:
return cls.registry[x]
except KeyError:
new_item = cls(x)
cls.registry[x] = new_item
return new_item
A.create_item(1)
A.create_item(2)
A.create_item(2) # doesn't add new item, but returns already existing one
After four years of the question, I got here and Serge Ballesta's answer helped me. I created this example with an easier syntax.
If base is None, it will always return the first object created.
class MyClass:
instances = []
def __new__(cls, base=None):
if len(MyClass.instances) == 0:
self = object.__new__(cls)
MyClass.instances.append(self)
if base is None:
return MyClass.instances[0]
else:
self = object.__new__(cls)
MyClass.instances.append(self)
# self.__init__(base)
return self
def __init__(self, base=None):
print("Received base = %s " % str(base))
print("Number of instances = %d" % len(self.instances))
self.base = base
R1 = MyClass("apple")
R2 = MyClass()
R3 = MyClass("banana")
R4 = MyClass()
R5 = MyClass("apple")
print(id(R1), R1.base)
print(id(R2), R2.base)
print(id(R3), R3.base)
print(id(R4), R4.base)
print(id(R5), R5.base)
print("R2 == R4 ? %s" % (R2 == R4))
print("R1 == R5 ? %s" % (R1 == R5))
It gives us the result
Received base = apple
Number of instances = 2
Received base = None
Number of instances = 2
Received base = banana
Number of instances = 3
Received base = None
Number of instances = 3
Received base = apple
Number of instances = 4
2167043940208 apple
2167043940256 None
2167043939968 banana
2167043940256 None
2167043939872 apple
R2 == R4 ? True
R1 == R5 ? False
Is nice to know that __init__ will be always called before the return of the __new__, even if you don't call it (in commented part) or you return an object that already exists.

Applying the #staticmethod, python3

class UserInput():
users=[]
def __init__(self, name,lista,listb,listc,listd):
self.name=""
self.lista=lista
self.listb=listb
self.listc=listc
self.listd=listd
#staticmethod
def create_new_user(x):
x=userinput("x","","","","")
users.append(x)
Im intending on making a function where new users are generated, only returning a name to the user and no lists yet, hence x in the name slot.
My Question: is this the correct usage of #staticmethod or did I miss the entire point of it?
To my understanding, it allows the user to use,in this case, userinput.create_new_user('tim') without having the class already pre-defined, tim=userinput("foo","","","","");it creates it on the spot.
What I was trying to turn the function create_new_users into:
#staticmethod
def create_new_user():
print("how many users do you want to create")
x=int(input())
y=0
while y < x:
print("assign the users names")
name = input("")
if name == "" or "None,none":
raise SyntaxError("name cannot be None or empty")
break
name=userinput("","","","","")
userinput.users.append(name)
y+=1
in a static method you could not use the class variable, your code should get
NameError: global name 'users' is not defined
edit:
use userinput.users.append
Using a #classmethod will be the easiest alternative for that.
class UserInput: # capitals! Look at PEP 8.
users = [] # rearranged to the top for better readability
def __init__(self, name, lista, listb, listc, listd):
self.name = ""
self.lista = lista
self.listb = listb
self.listc = listc
self.listd = listd
#classmethod
def create_new_user(cls): # no need for x if you overwrite it immediately
x = cls("x", "", "", "", "")
cls.users.append(x) # easier access to this static attribute
return x # for the caller having access to it as well.
It works as well if we subclass UserInput as it uses the new class then.
But note that x = cls("x", "", "", "", "") won't be very useful, though; better do
#classmethod
def create_new_user(cls, *a, **k): # no need for x if you overwrite it immediately
x = cls(*a, **k) # pass the arguments given by the caller to __init__.
cls.users.append(x) # easier access to this static attribute
return x # for the caller having access to it as well.
I can use that now this way:
a = UserInput("foo", "whatever", "is", "needed", "here")
or, if I choose to,
a = UserInput.create_new_user("foo", "whatever", "is", "needed", "here")
which additionally appends the new user to the list.
If you want to be able to shorten the arguments list, you can do so as well:
def __init__(self, name, lista=None, listb=None, listc=None, listd=None):
self.name = name
self.lista = lista if lista is not None else []
self.listb = listb if listb is not None else []
self.listc = listc if listc is not None else []
self.listd = listd if listd is not None else []
if they are really lists. If they are strings, another name would be appropriate and, as strings are immutable, you can simply do
def __init__(self, name, lista='', listb='', listc='', listd=''):
self.name = name
self.lista = lista
self.listb = listb
self.listc = listc
self.listd = listd
and call the stuff with
a = UserInput.create_new_user("foo", listc=...) # all others are left empty
b = UserInput("bar") # all are left empty
c = UserInput.create_new_user("ham", lista=..., listd=...) # all others are left empty
Now that you come up with a different task, I'll try to cope with that as well:
#classmethod
def create_new_users(cls): # several users!
print("how many users do you want to create")
num = int(input())
for _ in range(num): # simpler iteration
print("enter the user's name")
name = input("") # in 3.x, this is always a string, so it cannot be None...
# if name == "" or "None,none": # That won't work as you think.
if name == '' or name.lower() == 'none': # but why disallow the string 'None'?
# raise SyntaxError("name cannot be None or empty")
raise RuntimeError("name cannot be None or empty") # or ValueError or alike
# break not needed. raise jumps out without it as well.
user = cls(name, "", "", "", "") # name is an input, not an output.
cls.users.append(name)
But I wonder if the class is really the right place to store new users, and only those created with this function. Maybe it would be better to feed the users list directly in __init__ and let this function be at a higher level.
The advantage of using a #classmethod here is that you always work on the corret basis.
Imagine you have a UserInput with a __init__() method as above. Then you can subclass it and do
UserInput.create_new_users()Using a #classmethod will be the easiest alternative for that.
class UserInputStoring(UserInput):
users = [] # this is only here, not at the parent.
def __init__(self, *a, **k):
super(UserInputStoring, self).__init__(*a, **k) # pass everything up as it was used
self.users.append(self)
Now you can have your create_new_users() in the base class and be a #classmethod and it will pick the right __init__ to call depending on how you call it.

Categories