I have a python array of objects
class ball(self, size, color, name):
self.size = size
self.color = color
self.name = name
then a user will inputs a name and an attribute via the command line. For example a user could input "name1" and then "color" or "weirdName" then "size"... I then want to find the object based on the name and print get either the color object or the size object. Can I do it like this or will I need to use a switch case?
Thanks
If you know there is exactly one match, you can do this:
the_ball = next(b for b in list_of_balls if b.name == ???)
If there are multiple then you can get a list:
the_balls = [b for b in list_of_balls if b.name == ???]
If you are primarily looking up balls by their name, you should keep them in a dictionary instead of a list
To retrieve an attribute by name use getattr
getattr(the_ball, "size")
Doing this can be a bad idea
getattr(the_ball, user_input)
what if user_input is "__class__" or something else you didn't expect?
If you only have a few possibilities it's better to be explicit
if user_input == "size":
val = the_ball.size
elif user_input in ("colour", "color"):
val = the_ball.color
else:
#error
I think you're trying to do two different things here.
First, you want to get a particular ball by name. For that, gnibbler already gave you the answer.
Then, you want to get one of the ball's attributes by name. For that, use getattr:
the_ball = next(b for b in list_of_balls if b.name == sys.argv[1])
the_value = getattr(the_ball, sys.argv[2])
print('ball {}.{} == {}'.format(sys.argv[1], sys.argv[2], the_value)
Also, your class definition is wrong:
class ball(self, size, color, name):
self.size = size
self.color = color
self.name = name
You probably meant for this to be the __init__ method inside the ball class, not the class definition itself:
class ball(object):
def __init__(self, size, color, name):
self.size = size
self.color = color
self.name = name
However, you may want to reconsider your design. If you're accessing attributes dynamically by name more often than you're accessing them directly, it's usually better just to store a dict. For example:
class Ball(object):
def __init__(self, size, color, name):
self.name = name
self.ball_props = {'size': size, 'color': color}
list_of_balls = [Ball(10, 'red', 'Fred'), Ball(20, 'blue', 'Frank')]
the_ball = next(b for b in list_of_balls if b.name == sys.argv[1])
the_value = the_ball.ball_props[sys.argv[2]]
Or you may even want to inherit from dict or collections.MutableMapping or whatever, so you can just do:
the_value = the_ball[sys.argv[2]]
Also, you may want to consider using a dict of balls keyed by name, instead of a list:
dict_of_balls = {'Fred': Ball(10, 'red', 'Fred'), …}
# ...
the_ball = dict_of_balls[sys.argv[1]]
If you've already built the list, you can build the dict from it pretty easily:
dict_of_balls = {ball.name: ball for ball in list_of_balls}
If I understood properly, you need to get a particular ball from a list of balls, based on the value of an attribute. A solution would be:
attribute_value = sys.argv[1]
attribute_name = sys.argv[2]
matching_balls = [ball_item for ball_item in list_balls if \
getattr(ball_item, attribute_name) == attribute_value]
Related
I'd like certain instances to initialize with certain default attribute values depending on the initialization parameters of the object. I'm considering using a nested dictionary as a class attribute, but it feels convoluted for some reason. Is there a best practice for this type of situation?
class Shape:
metadata = {
3: {"names": ["Triangle", "Triforce"], "color": "sage"},
4: {"names": ["Square", "Box", "Cube"], "color": "dusty rose"},
12: {"names": ["Dodecagon", "Crude circle"], "color": "gold"}
}
colors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"]
def __init__(self, sides, *names):
# All instances will certainly have the same attributes
self.sides = sides
self.names = list(names)
# Most attributes will have automatically generated values based on
# the init parameters
self.color = self.colors[sides % 7]
self.names += [str(sides) + "-gon"]
# But a few will have commonly recognized values which I'd like to set
# manually.
# This is the part I'm not sure how to handle
data = __class__.metadata.get(sides)
if data is not None:
self.names += data["names"]
self.color = data["color"]
I could add the custom values after creating the objects, but if I ever create another object with the same initialization parameters, it won't retain those custom values (i.e. I want all my Shape(3) objects to posses the name "Triangle").
I think the reason it feels complicated is because your Shape class is trying to do too many things at once. Ideally, a class should be responsible for a single part of your programs behavior (this is the Single Responsibility Principle).
I'd recommend two main changes to your code.
Don't make the Shape class responsible for creating itself
A shape doesn't really need to know about all other possible kinds of shapes, or the rules required for deciding which kind of shape it is. This code can be abstracted out into another class, so the shape can focus on containing sides, shapes and colours. I'd recommend using something like the Factory Pattern for this (https://en.wikipedia.org/wiki/Factory_method_pattern).
Consider using polymorphism
If you plan on only ever having shapes be containers for sides names and colours, your current class will work fine. However, if you ever want to add functionality that changes depending on the kind of shape (if you wanted to calculate it's area, say), you'll wind up with some complicated logic in your shapes class which will mean it's back doing too many things again.
Example:
class Shape:
def __init__(self, sides, color, *names):
self.sides = sides
self.color = color
self.names = names
def __str__(self):
return "Sides: {}\nNames:{}\nColor: {}\n".format(self.sides, self.names, self.color)
class Triangle(Shape):
def __init__(self, color, *names):
super().__init__(3, color, *names)
class Square(Shape):
def __init__(self, color, *names):
super().__init__(4, color, *names)
class BlueShapeFactory:
def createShapes(sides):
if sides == 3:
return Triangle("Blue", "PointyBoy", "Triangle")
elif sides == 4:
return Square("Blue", "Uncool", "Square")
else:
return Shape(sides, "Blue", str(sides) + "-o-gon")
class DefaultShapeFactory:
def createShapes(sides):
if sides == 3:
return Triangle("green", "Triforce", "Triangle")
elif sides == 4:
return Square("blue", "Box", "Cube", "Square")
else:
return Shape(sides, "purple", str(sides) + "-o-gon")
print("Blueshapes:\n")
print(BlueShapeFactory.createShapes(3))
print(BlueShapeFactory.createShapes(4))
print(BlueShapeFactory.createShapes(42))
print("Your shapes:\n")
print(DefaultShapeFactory.createShapes(3))
print(DefaultShapeFactory.createShapes(4))
print(BlueShapeFactory.createShapes(42))
I found a solution: Use The Flyweight design pattern.
Using this design pattern, for each initialization parameter, an instance is only instantiated once, and referenced if construction is attempted again with the same init parameter(s). This is similar to object caching of certain immutable Python built-ins.
Rather than saving default attribute values inside the class definition, simply set those values after instantiation. Any future object with the same initialization parameters will retain those custom values because it will reference the first object.
class Shape:
_instances = {}
colors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"]
def __new__(cls, sides, *names):
# Retrieve the Shape instance if previously instantiated
self = cls._instances.get(sides)
# If it has not yet been instantiated...
if self is None:
# Save this Shape instance
self = cls._instances[sides] = object.__new__(Shape)
# Initialize here for efficiency (because __init__ gets called
# automatically, even when returning a previously instantiated
# object)
self.sides = sides
self.color = cls.colors[sides % 7]
self.names = list(names)
self.names += [str(sides) + "-gon"]
return self
triangle = Shape(3)
triangle.names += ["triangle", "triforce"]
triangle.color = "sage"
square = Shape(4)
square.names += ["Square", "Box", "Cube"]
square.color = "dust rose"
dodecagon = Shape(12)
dodecagon.names += ["Dodecagon", "Crude circle"]
dodecagon.color = "gold"
Usually, this pattern should be used for immutable objects, because modifying attributes of one object causes that change to be made everywhere that object is referenced, which could cause unexpected results. However, in this case, that is desirable behavior.
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"
Hey the question title might be a bit confusing. So basically I want to print out a dictionary with the keys and it's values, so I know to call dictionary.items() but I want it to print out using the object's str function instead of printing the memory address.
My Player object
class Player():
def __init__(self, id, name, position):
self.player_id = id
self.name = name
def __str__(self):
return self.name
So I have a dictionary -
depth_chart = {}
That I insert the player objects into as a list.
def addPlayer(player, position):
# Function to add player to depth chart.
if position not in depth_chart:
depth_chart[position] = []
depth_chart[position].append(player)
else:
depth_chart[position].append(player)
So I eventually might have something like this -
depth_chart = {
'shooting_guard': [PlayerObject1, PlayerObject2],
'center': [PlayerObject3],
'point_guard': [PlayerObject4]
}
Now here's my problem if I call depth_chart.items() I want it to return me something like
[('shooting_guard', ['John', 'Joseph'] ), ('center', ['Alex']), ('point_guard': ['Sean'] ]
But right now it just returns me the memory address of the Player objects. Is there a way to call the str function or another method to print out the player object names? I do have a function I can do to get it to look like how I want but I'm not sure if there's an easier way? How that function looks -
def getFullDepthChart():
# Function to print out all the positions in the depth chart
all_players = []
for items in depth_chart.items():
temp = []
position = items[0]
player_array = items[1]
for players in player_array:
temp.append(players.name)
all_players.append((position, temp))
return all_players
You can implement __repr__ to return the player's name:
class Player:
def __repr__(self):
return self.name
def getFullDepthChart():
# Function to return all positions in the depth chart
all_players = [(position,
[player.name for player in players])
for pos, players in depth_chart.items()]
return all_players
When i try to put my objects into a list, i can not get an output with object names, it gives a weird output like "_ main _.object at 0x029E7210". I want to select my objects randomly to blit ONE of them onto the screen. But i could not figure this out.
car_main = pygame.image.load("car_main.png")
car_red_ = pygame.image.load("car_red.png")
car_blue = pygame.image.load("car_blue.png")
class cars:
def __init__(self,x,y,car_type,w=50,h=100,s=5):
self.x = x
self.y = y
self.w = w
self.h = h
self.s = s
self.car_type = car_type
def draw(self):
dp.blit(self.car_type,(self.x,self.y))
car1 = cars(x,y,car_main)
car2 = cars(x,y,car_red)
car3 = cars(x,y,car_blue)
car_list = [car1,car2,car3]
rc = random.choice(car_list)
print(rc)
# output> __main__.object at 0x02A97230
When I change
car_list = [car1,car2,car3] with;
car_list = [car1.car_type,car2.car_type,car3.car_type]
# output > Surface(50x100x32 SW)
But I want to see an output as my object names. Not as a string type ("car_main"). I want to get an output as the object name (car_main) directly. Because in the main loop, i will choose one of them to blit onto the screen everytime when the loop renews itself.
You need to define __str__ for your class Car to let it properly handle object to string:
class Car:
def __str__(self):
for k, var in globals().items():
if var == self:
return k
# default
return "Car"
Note1: Usually use uppercased Car for a class and car for an instance.
Note2: Look up variable strings in globals is not reliable. You may not want to make all variables global, and manually search them in scope is tedious. Actually why don't you give your Car a name attribute? Then you nicely have:
class Car:
def __init__(self, name):
self.name=name
def __str__(self):
return self.name
car = Car(name='first car')
print(car) # 'first car'
More read about "magic methods": https://rszalski.github.io/magicmethods/#representations
Add a __str()__ magic method to your car class like so:
def __str__(self):
return f'car with x of {self.x}, y of {self.y}, and type of {self.car_type}'
I'm working my way through the libtcod python tutorial, I've decided to mess around with some of the code to make it more unique today, and decided to start off with a feature to allow the player to hover the mouse over an object and press 'd' for a description of that object.
I'm currently running into an attribute error: 'str' object has no attribute 'describe' line 657. I've tried many different things but notihng seems to work, unfortunately my level of understanding is pretty limited right now so I can't figure out what's going wrong.
Here are the relevant classes and functions:
class Object:
#this is a generic object: the player, a monster, an item, the stairs...
#it's always represented by a character on screen.
def __init__(self, x, y, char, name, color, blocks=False, fighter=None, ai=None, item=None, description=None):
self.x = x
self.y = y
self.char = char
self.name = name
self.color = color
self.blocks = blocks
self.fighter = fighter
if self.fighter: #let the fighter component know who owns it
self.fighter.owner = self
self.ai = ai
if self.ai: #let the ai component know who owns it
self.ai.owner = self
self.item = item
if self.item: #let the item component know who owns it, like a bitch
self.item.owner = self
self.description = self
if self.description: #let the description component know who owns it
self.description.owner = self
def describe(self):
#describe this object
message(self.description, libtcod.white)
def handle_keys():
global keys;
if key_char == 'd':
#show the description menu, if an item is selected, describe it.
chosen_object = description_menu('Press the key next to an object to see its description.\n')
if chosen_object is not None:
chosen_object.describe()
return 'didnt-take-turn'
def description_menu(header):
global mouse
#return a string with the names of all objects under the mouse
(x, y) = (mouse.cx, mouse.cy)
#create a list with the names of all objects at the mouse's coordinates and in FOV
names = [obj.name for obj in objects if obj.x == x and obj.y == y and libtcod.map_is_in_fov(fov_map, obj.x, obj.y)]
names = ', '.join(names) #join the names, seperated by commas
return names.capitalize()
#show a menu with each object under the mouse as an option
if len(names) == 0:
options = ['There is nothing here.']
else:
options = [item.name for item in names]
index = menu(header, options, INVENTORY_WIDTH)
#if an item was chosen, return it
if index is None or len(names) == 0: return None
return names[index].description
Any help would be much appreciated!
The function description_menu() has the following return
names[index].description
This is a string member that belongs to Object.
When you say
chosen_object.describe()
You are calling the describe() method, but that belongs to the Object class, not a string (hence the attribute error: 'str' object has no attribute 'describe'). You would have to have description_menu() return the Object instead of just the name of it.