Python prints object address instead of values - python

I want to print all of the tracks that have been added to the tracks[] list. When I attempt to do so, I get the address where that object sits in memory, rather than its actual value. I obviously don't understand how object creation/passing of objects from one class to another class works.
class Song:
def __init__(self, title, artist, album, track_number):
self.title = title
self.artist = artist
self.album = album
self.track_number = track_number
artist.add_song(self)
class Album:
def __init__(self, title, artist, year):
self.title = title
self.artist = artist
self.year = year
self.tracks = []
artist.add_album(self)
def add_track(self, title, artist=None):
if artist is None:
artist = self.artist
track_number = len(self.tracks)
song = Song(title, artist, self, track_number)
self.tracks.append(song)
print(self.tracks)
class Artist:
def __init__(self, name):
self.name = name
self.albums = []
self.songs = []
def add_album(self, album):
self.albums.append(album)
def add_song(self, song):
self.songs.append(song)
class Playlist:
def __init__(self, name):
self.name = name
self.songs = []
def add_song(self, song):
self.songs.append(song)
band = Artist("Bob's Awesome Band")
album = Album("Bob's First Single", band, 2013)
album.add_track("A Ballad about Cheese")
album.add_track("A Ballad about Cheese (dance remix)")
album.add_track("A Third Song to Use Up the Rest of the Space")
playlist = Playlist("My Favourite Songs")
for song in album.tracks:
playlist.add_song(song)

It looks like you are attempting to print the array, and not a value in the array. print(self.tracks) is printing the self.tracks object, which is an array. Try print(self.tracks[x]), x being the index of the string you want to print.
If you want to print all of the objects in that array, iterate through it and print each object.
Use this to iterate through the array:
for x in range(len(self.tracks)):
print self.tracks[x].title
or
for track in self.tracks
print track.title
To get the value of the title of each song object, address it in the loop with track.title. To get the artist or year, change it to track.artist or track.year.
You can build larger strings using the same logic, for example:
print("Title " + track.title + ", Artist " + track.artist)

Yes, the "value" of an object includes its type and memory location. You need to extract the attribute values you want. Note that you need to do this for the other objects included in the Song attributes.
One way to do this is to implement the "representation" method within your class, __repr__. For your application, it might look something like this:
def __repr__(self):
return "\n".join([self.title, self.artist.name, self.album.title,
"Track " + str(self.track_number)])
Now, any time you use a Song object where its string representation is syntactically required, Python will use this method to make the conversion. Without any other coding, your program now produces:
[A Ballad about Cheese
Bob's Awesome Band
Bob's First Single
Track 0]
[A Ballad about Cheese
Bob's Awesome Band
Bob's First Single
Track 0, A Ballad about Cheese (dance remix)
Bob's Awesome Band
Bob's First Single
Track 1]
[A Ballad about Cheese
Bob's Awesome Band
Bob's First Single
Track 0, A Ballad about Cheese (dance remix)
Bob's Awesome Band
Bob's First Single
Track 1, A Third Song to Use Up the Rest of the Space
Bob's Awesome Band
Bob's First Single
Track 2]
Of course, you'll want to customize this to your own listing desires.

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 split received data into classes based on content

I am trying to write a chunk of code that will organize different types of data into classes. I can split them as of now, but I'm not sure how to get Python to look at the string and automatically sort them into either class based on the content of the string. For example, I have the following and would like to pass the string to either class depending on which type of data is being given to me:
#The data comes in by two different types continuously and is displayed as such below:
animal=dog, age=13, colour=brown, name=Jeff
animal=cat, age=9, colour=white, declawed=yes, friendly=yes, name=Jimmy
class Dogclass():
def __init__(self,age,colour,name):
self.age = age
self.colour = colour
self.name = name
class Catclass():
def __init__(self,age,colour,declawed,friendly,name):
self.age = age
self.colour = colour
self.declawed = declawed
self.friendly = friendly
self.name = name
def splitter():
m = re.split('[, =]', data),
if "dog" in m:
I would like my splitter function to not only have the ability to split the strings, but also go on to sort the split data into classes. This is what I had before (did not work) but would like to figure out a way to utilize OOP more and understand the use of classes.
dog = []
cat = []
def splitter(data):
m = re.split('[, =]', data)
if 'dog' in m:
dog['age'] = (m[7])
dog['colour'] = (m[11])
dog['name'] = (m[13])
elif 'cat' in m:
cat['age'] = (m[7])
cat['colour'] = (m[9])
cat['declawed'] = (m[11])
cat['friendly'] = (m[13])
cat['name'] = (m[15])
else:
return()
I have also tried to create dictionaries to store the data I want to call to, but everything I have tried does not successfully take the splitted data and assign it to a value within my dictionary. Any help would be appreciated.
Lets say you got a string that represent data like this :
"animal=dog, age=13, colour=brown, name=Jeff"
The fisrt thing you would have to do is to parse it to a dictionary like object with a simple function like this one :
def parser(stringToParse):
remove_space = stringToParse.replace(" ", "")
addQuotes = {i.split('=')[0]: i.split('=')[1]
for i in remove_space.split(',')}
return addQuotes
Then you would get an object and you could get its corresponding class by any of the corresponding attribute (lets say your class is based on the "animal" attribute, you could define a simple function to to that :
def getConstructor(classname):
return globals()[classname]
All in one :
import json
class dog():
def __init__(self, age, colour, name):
self.age = age
self.colour = colour
self.name = name
class cat():
def __init__(self, age, colour, declawed, friendly, name):
self.age = age
self.colour = colour
self.declawed = declawed
self.friendly = friendly
self.name = name
def parser(stringToParse):
remove_space = stringToParse.replace(" ", "")
addQuotes = {i.split('=')[0]: i.split('=')[1]
for i in remove_space.split(',')}
return addQuotes
def getConstructor(classname):
return globals()[classname]
def buildIt(any_animal):
my_animal = parser(any_animal)
my_animal_constructor = getConstructor(my_animal["animal"])
if my_animal_constructor.__name__ == "dog":
return dog(my_animal["age"], my_animal["colour"], my_animal["name"])
my_new_animal = buildIt("animal=dog, age=13, colour=brown, name=Jeff")
print(my_new_animal.colour)
In this example i build a dog from the input. If you try to print its coulour you get : "brown"
Of course you will have to implement the if statement for the other class in order to get the cat (and other) class work too...
EDIT
Also, if you want to improve your code you should implement it as an Object oriented one as suggested in Yaron Grushka's answer (create an Animal parent class and makes cat and dog inherit from it)
First of all, I would suggest that in general for cases like these that you use inheritance. You can have a parent class called Animal which has all the common attributes such as age, name and color. Then you can create the Cat and Dog classes that inherit from the parent class, each having unique attributes (such as declawed for cats). Like so:
class Animal():
def __init__(self, name, age, colour):
self.name = name
self.age = age
self.colour = colour
class Cat(Animal):
def __init__(self, name, age, colour, declawed, friendly):
super().__init__(name, age, colour)
self.declawed = declawed # Can make this a boolean with an if/else
self.friendly = friendly # Same
For splitting, you can actually use the split function that Python offers, and use commas as the separator. Then make it into a dictionary. e.g:
def create_animal(data):
details = data.split(",");
attributes = {}
for detail in details:
pair = detail.split("=")
attributes[pair[0]] = pair[1]
print(attributes)
if attributes["animal"] == "cat":
animal = Cat(attributes[" name"], attributes[" age"], attributes[" colour"], attributes[" declawed"], attributes[" friendly"])
else: # Dog creation, same idea...
return animal
a = create_animal("animal=cat, age=9, colour=white, declawed=yes, friendly=yes, name=Jimmy")
print(a.name)
# =>"Jimmy"

Spawn multiple classes and retrieve them

I'm wondering if there is a "standard" way to spawn multiple instances of a class dynamically and retrieve them?
The below code spawns 10 bikes, with random prices and colours, I'm wondering how I would call them back? I played around with appending to a list but the returned items are strings.
bikes = []
class Bike:
style = ""
price = 0
colour = ""
def __init__(self, style, price, colour):
self.style = style
self.price = price
self.colour = colour
self.description = "a {0} {2} worth £{1}".format(self.colour, self.price, self.style)
def print_details(self):
print("description: {0}".format(self.description))
for b in range(10):
price = random.choice(random_prices)
colour = random.choice(random_colours)
bike = "bike" + str(b)
bike = Bike('bike', price, colour)
bikes.append(bike)
for i in bikes:
print_details(i)
Traceback (most recent call last):
print("description: {0}".format(self.description))
AttributeError: 'str' object has no attribute 'description'
Any time you create a class (or any kind of data) it would need to be referenced somewhere. If it isn't, you can consider it lost.
If you need to create a collection of classes, then a collection is probably a good way to store them:
bikes = []
for b in range(10):
price = random.choice(random_prices)
colour = random.choice(random_colours)
bike = "bike" + str(b)
bike = Bike('bike', price, colour)
bikes.append(bike)
Based on your updated question, I believe the issue is in how you're calling print_details. Because it's a class member, it should be called like this:
for i in bikes:
i.print_details()

Refactoring For-Loops in Python

I am currently working on an iTunes data program that's cycling constantly through a user's library to get statistics about one's library.
returns
I have a few code snippets like these:
def numArtist(self):
num = 0
for song in self.allSongs:
tempList = []
if song.artist not in tempList:
tempList.append(song.artist)
num += 1
return num
def getAlbumNames(self):
albums = []
for song in self.allSongs:
if song.album not in albums:
albums.append(song.album)
return albums
Where the main for loop body is repeated:
for song in self.allSongs: # same for-loop condition
# different for-loop body
Is there a way to refactor methods like these, where I have the same for loop conditions but with different body definitions?
I have a quite a few methods with the same for-loop, so I'd like to find a way to decrease the complexity and redundancy of my code.
Just for reference, all Song objects have attributes - artist, album (name),genre, etc - that I'm using to get my data.
Use set comprehensions and len to simplify each of them:
def numArtist(self):
return len({song.artist for song in self.allSongs})
def getAlbumNames(self):
return {song.album for song in self.allSongs}
To make it more generic, you could write a method that takes a lambda and use that to filter the property out of each song:
def uniqueProps(self, fxn):
return {fxn(song) for song in self.allSongs}
def getAlbumNames(self):
return self.uniqueProps(lambda song: song.album)
You can use set comprehensions for both snippets, if that counts as a valid "For-Loop refactoring":
artist_count = len({song.artist for song in self.allSongs})
album_names = set({song.album for song in self.allSongs})
Generic version using getattr
get_values = lambda objs, attr: {getattr(obj, attr) for obj in objs
attributes = 'artist', 'album'
values = [get_values(self.allSongs, name) for name in attributes]
artists, albums = values
artist_count = len(artists)
Generic version using lambda
get_artist = lambda song: song.artist
get_album = lambda song: song.album
getters = get_artist, get_album
values = [
{func(song) for song in self.allSongs}
for getter in getters
]
artists, albums = values
artist_count = len(artists)
Generic version using property
# If `song` is an instance of the `Song` class and both `artist` and
# `album` are properties defined on the class, it's also possible to
# directly use the property getter (`property.fget`) to avoid defining
# the lambdas manually:
get_artist = Song.artist.fget
get_album = Song.album.fget
... # <same as above>
If the contents of your allSongs list are immutable - which I suspect they are - you can convert your lists to sets and back to lists again - or use set comprehension - to get rid of duplicates. Then your functions can be greatly simplified like so:
def numArtist(self):
return len({song.artist for sing in self.allSongs})
def getAlbumNames(self):
return list({song.album for song in self.allSongs})
If you're not sure if the song objects are mutable or not, try this out anyway. If they're mutable objects you'll get an exception like:
TypeError: unhashable type: ...
You could try to create the generators, that produces the value of song attributes. Let me give you an example:
def gen_attr(songs, attr_name):
for song in songs:
yield getattr(song, attr_name)
class Song(object):
def __init__(self, name, artist):
self.name = name
self.artist = artist
class Album(object):
def __init__(self, songs_list):
self.songs_list = songs_list
def allSongs(self):
return self.songs_list
s = Song('Ahoy', 'Pirate')
s1 = Song('Bye', 'My Son')
s2 = Song('Ahoy', 'Captain')
a = Album([s, s1])
Now if you want to get all of the song names, u can use:
song_names = list(gen_attr(a.allSongs(), 'name'))
print(song_names) # ['Ahoy', 'Bye', 'Ahoy']
For non-repeated song names you would use:
song_names = list(set(gen_attr(a.allSongs(), 'name')))
print(song_names) # ['Ahoy', 'Bye']
To count the non-repeated artists names, you would use:
artists = len(set(gen_attr(a.allSongs(), 'artist')))
And to create the list of artists, just go for:
artists = list(gen_attr(a.allSongs(), 'artist'))
print(artists) # ['Pirate', 'My Son', 'Captain']

How to access an object in a list by property?

Consider a list of objects and a method that works on one object:
beers = []
beers.append(Beer('Carlsberg', 'tasty'))
beers.append(Beer('Guinness', 'bitter'))
beers.append(Beer('Leffe', 'perfect'))
def howIsBeer (name):
taste = ???
print taste
Class Beer:
def __init__ (self, name, taste):
self.name = name
self.taste = taste
How would the howIsBeer() method go about getting the taste of a beer if it is provided only with the beer name? My first inclination is to iterate the beers list, but being Python I suspect that there is a more direct method. Is there?
Can't really think of any better way, simply use a for-loop.
def howIsBeer(name):
for beer in beers:
if beer.name == name:
return beer.taste
In [1]: howIsBeer("Carlsburg")
Out[1]: tasty
Also notice, that keyword class is written with small c. You will also have to define your class before you use it for creating instances.
Edit:
One way, as suggested in the commments, would be to define dictionaries. If you find it useful, you can use the code below. Notice, however, this is only recommended if you have HUGE amount of Beer objects and performance speed is really important for you. Else use the first code provided
class Beer:
names = {}
def __init__(self, name, taste):
Beer.names[name] = self
self.name = name
self.taste = taste
In [3]: Beer.names["Carlsburg"].taste
Out[3]: tasty
Just loop through the list and check for each one.
You need to move the code around a little though, as the class def needs to be above the use of the class, and also it uses a small c. You should also watch out for the case where it's not recognised.
You also spelt Carlsberg wrong :/
class Beer:
def __init__ (self, name, taste):
self.name = name
self.taste = taste
beers = []
beers.append(Beer('Carlsberg', 'tasty'))
beers.append(Beer('Guinness', 'bitter'))
beers.append(Beer('Lef', 'perfect'))
def howIsBeer (name):
taste = "I have no idea"
for beer in beers:
if beer.name == name:
taste = beer.taste
print taste
howIsBeer("Carlsberg") # tasty
I'd do it like this though (using the dictionaries here allows for the flexibility of having more than one property):
beers = {}
beers["Lef"] = {"taste": "tasty"}
beers["Staropramen"] = {"taste": "tasty"}
beers["Peroni"] = {"taste": "tasty"}
beers["Coors Light"] = {"taste": "What is this?!"}
def howIsBeer (name):
taste = "I have no idea"
if name in beers:
taste = beers[name]["taste"]
print taste
howIsBeer("Lef")
If you just want to store the tastes, then you could do this:
beers = {}
beers["Lef"] = "tasty"
beers["Staropramen"] = "tasty"
beers["Peroni"] = "tasty"
beers["Coors Light"] = "What is this?!"
def howIsBeer (name):
taste = "I have no idea"
if name in beers:
taste = beers[name]
print taste
howIsBeer("Lef")
If you are looking to store a series of objects - as you mention in the question - then you want a dictionary of objects - not a dictionary that is a variable of the class.
i.e.
beers = {}
def add_beer(beer):
beers[beer.name] = beer
then to get data on the beer you're looking at;
if beer in beers:
beers[beer].taste
This can be extended to any object type, and i believe is exactly what you're looking for;
e.g.
cheeses = {}
add_cheese(cheese):
cheeses[cheese.name] = cheese
where
class cheese:
def __init__(self, name, smelliness, hardness, colour, country):
self.name = name
self.smelliness = smelliness
self.hardness = hardness
self.colour = colour
self.country = country
For sake of completeness, I have found a way to use a Linq-like single-line statement which also has the advantage of being a bit easier to read:
taste = [b.taste for b in beers if b.name==name][0]
The [0] at the end is to return the first item, since the query returns a list of matching items. So to get the whole list:
[b.taste for b in beers if b.name==name]
This is why I love Python!

Categories