Better understanding of __str__ usage - python

I'm trying to better understand the proper usage of the __str__ function.
Let's say I have a very simple class called Character for use in a game that looks like this:
class Character(object):
""" A game character. """
def __init__(self, name):
self.name = name
self.poisoned = False
self.strength = random.randrange(10, 20)
self.max_strength = self.strength
self.dexterity = random.randrange(10, 20)
self.max_dexterity = self.dexterity
self.hit_points = 100
self.spell_points = 100
self.weapon = []
self.spell = []
self.items = []
self.aura = []
self.xp = 0
Prior to learning about the __str__ function, I would have defined a method of the class called print_stats(self) that would print the character stats via Character.print_stats(). After learning about __str__ though it seemed like this was a function for defining and displaying the statistics of an object, similar to what I would do with print_stats(self)... but in playing with it and learning that the returned value must be a string and not contain integers, it appears my assumption is wrong.
So now my question is what are some examples of good usage of the __str__? Would the example I provide benefit from using that function?

Printing stats is a fine use of __str__(). Simply use string formatting to return a single string value:
def __str__(self):
return ('Name: {name}\n'
'Poisoned: {poisoned}\n'
# etc.
).format(**self.__dict__)

__str__ exists so that you can get a string representation of your object. Note that the builtin print function/statement calls str implicitly:
print 1
is exactly the same as:
print str(1)
which is the same as:
print (1).__str__()
because print calls __str__ implicitly.
Now to your class -- The most natural thing to do is if you would have written:
print self.foo,self.bar,self.baz
You could define __str__ as:
def __str__(self):
return " ".join(str(x) for x in (self.foo,self.bar,self.baz))
Now to print your character's stats, you'd just do:
print character #same as `print str(character)` :)
Usually this is a little limited, so there exists string formatting via .format (or old "sprintf" style formatting using the % operator).
def __str__(self):
return "my {foo} went to the {baz} to buy a {bar}".format(foo=self.foo,
baz=self.baz,
bar=self.bar)

def __str__(self):
return 'Name: %s, Hit points: %d' % (self.name, self.hit_points)

The return value has to be a string, but it can be any string, including one that contains the string representation of integers. A simple example:
def __str__(self):
return "%s (strength: %i)" % (self.name, self.strength)
This might return something like "Arthur (strength: 42)".

In basic use cases, __str__() and a method like print_stats() can pretty safely be interchanged. However, especially for something like a character in a game, you might want to go with print_stats(), as you won't be able to refactor __str__() to do something like print the data to the screen (in the context of a graphical desktop or web application), or to take arguments, which could prove useful for things like this.

Related

__repr__ returned non-string type error

Below is a dummy example of my class method:
class A:
def __init__(self, name):
self.name = name
def __repr__(self):
for i in range(0,5):
if i == 0:
print(self.name)
else:
print("-")
i += 1
m1 = A("x")
m1
It prints out the result for me. However, in the meantime, it gives an error saying that __repr__ returned non-string. I understand that I need to use return instead of print for __repr__, however return would stop my program when the first condition is met. I also tried yield but had no luck.
I know this dummy program looks ridiculous and I could have used some other methods instead of __repr__ to print out the results. But this is just a simplified version of my actual program and I have to use __repr__ or __str__ for some reasons.
You have two basic problems. The first is that you altered the loop index within the loop -- bad form, but not at all fatal. The second is that you fail to return a value.
IMMEDIATE REPAIR
def __repr__(self):
for i in range(0,5):
if i == 0:
val = self.name
else:
val += "-"
return val
However, even this is ineffective.
EVEN BETTER
It appears that you want the name with four dashes appended. Why not just do that?
def __repr__(self):
return self.name + "----"
I am assuming that you want your __repr__ output exactly as you are printing it. For that you will just need to change the logic a bit. Something like this should work:
def __repr__(self):
string = ""
for i in range(0,5):
if i == 0:
string += self.name
else:
string += "-"
i += 1
return string

My code is printing an extra "None" after every call of the method getTotalByKind.. Where could it be from? [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions concerning problems with code you've written must describe the specific problem — and include valid code to reproduce it — in the question itself. See SSCCE.org for guidance.
Closed 9 years ago.
Improve this question
class Item:
def __init__(self, name, price, kind):
self.name = name
self.price = price
self.kind = kind
def getPrice(self):
return self.price
def getName(self):
return self.name
def getKind(self):
return self.kind
class Cart:
def __init__(self):
self.list = []
pass
def addItem(self, item):
self.list.append(item)
def getTotalsByKind(self, kind):
total = 0
for i in self.list:
if i.getKind() == kind:
total += i.getPrice()
t = '{:.2f}'.format(total)
print "The total for %s items is %s" %(kind, t)
You are printing the return value of the method.
Remove the print statement from before the .getTotalsByKind() method call; the method itself does all the printing.
Your method does not have an explicit return statement, which means the default return value None is used:
>>> def foo():
... # Nothing is returned in this function
... print 'Bar!'
...
>>> print foo()
Bar!
None
>>> foo()
Bar!
The better alternative is to have your method return the string to be printed:
def getTotalsByKind(self, kind):
total = 0
for i in self.list:
if i.getKind() == kind:
total += i.getPrice()
t = '{:.2f}'.format(total)
return "The total for %s items is %s" %(kind, t)
Now you can do different things with the returned string, not just print it.
You should make getTotalsByKind return the string, not print it. To do this, make this line:
print "The total for %s items is %s" %(kind, t)
like this:
return "The total for %s items is %s" %(kind, t)
Now, when you print the results of getTotalsByKind, it will work.
Functions, by default, return None if they come to the end of themselves without returning. And, by using print with getTotalsByKind (which you must be doing), you are telling Python to print the return value of getTotalsByKind, which is None.
You don't show this part of your code, but my guess is that you're doing print cart.getTotalsByKind(...), thereby telling Python to print the return value of that function. But it doesn't return anything, therefore it returns None. Instead that method prints the total.
You have fallen prey to a poorly named method: getTotalsByKind implies that the totals will be returned, but there's only one total, and it is printed instead of being returned. I would name this method printTotalByKind instead. Or name it getTotalByKind and have the caller do the printing (and formatting). Then your method could be written much more simply as follows:
def getTotalByKind(self, kind):
return sum(item.price for item in self.list if item.kind == kind)
This isn't related to your question, but your getter methods are entirely superfluous and should probably be removed. You can already get an item's price via item.price, no need for the overhead of calling a function to do the same thing. PINJ.

Python Accessing Dict That Has Defualt Values That Have Been Updated

So I have this class:
class hero():
def __init__(self, name="Jimmy", prof="Warrior", weapon="Sword"):
"""Constructor for hero"""
self.name = name
self.prof = prof
self.weapon = weapon
self.herodict = {
"Name": self.name,
"Class": self.prof,
"Weapon": self.weapon
}
self.herotext = {
"Welcome": "Greetings, hero. What is thine name? ",
"AskClass": "A fine name, {Name}. What is your class? ",
"AskWeapon": "A {Class}, hmm? What shalt thy weapon be? ",
}
def setHeroDicts(self, textkey, herokey):
n = raw_input(self.herotext[textkey].format(**self.herodict))
if n == "":
n = self.herodict[herokey]
self.herodict[herokey] = n
#print self.herodict[herokey]
def heroMake(self):
h = hero()
h.setHeroDicts("Welcome", "Name")
h.setHeroDicts("AskClass", "Class")
h.setHeroDicts("AskWeapon", "Weapon")
And in another class I have this executing
def Someclass(self):
h = hero()
print h.herodict["Class"]
h.heroMake()
print h.getClass()
if "Mage" in h.herodict["Class"]:
print "OMG MAGE"
elif "Warrior" in h.herodict["Class"]:
print "Warrior!"
else:
print "NONE"
So if I input nothing each time, it will result in a blank user input, and give the default values. But if I put an input, then it will change the herodict values to what I customize. My problem is, if I try and access those updated values in Someclass it only gives me the default values instead of the new ones. How do I go about accessing the updated values?
The main issue with your class is that you are creating a new object within heromake instead of using the existing one. You can fix this by replacing h with self (so that each time you are calling setHeroDicts on the object):
def heromake(self):
self.setHeroDicts("Welcome", "Name")
self.setHeroDicts("AskClass", "Class")
self.setHeroDicts("AskWeapon", "Weapon")
The first argument to a method is always set to the instance itself, so if you want to interact with the instance or mutate it, you need to use it directly. When you do h = hero() in your original code, you create a whole new hero object, manipulate it and then it disappears when control passes back to your function.
A few other notes: you should name your classes with CamelCase, so it's easier to tell they are classes (e.g., you should really have class Hero) and in python 2, you need to make your classes descend from object (so class Hero(object)). Finally, you are duplicating nearly the entire point of having classes with your herodict, you should consider accessing the attributes of the object directly, instead of having the intermediary herodict (e.g., instead of doing h.herodict["Class"] you could do h.prof directly.

Printing individual list objects within __str__ using .format(**self.__dict__)

I'm printing a stat block for a game character object. In a previous question I was demonstrated a way to display the object data using in the __str__ function like so:
def __str__(self):
if self.poisoned is True:
status = "[POISONED]"
else:
status = ""
self.status = status
return ('NAME: {name} {status}\n' \
'XP: {xp}\n' \
'HP: {hit_points}\n' \
'SP: {spell_points}\n' \
'STR: {strength}\n' \
'DEX: {dexterity}\n' \
'WEAPON: {weapon}\n' \
'SPELL: {spell}\n' \
'ITEM: {item}\n' \
'AURA: {aura}\n' \
).format(**self.__dict__)
The problem I want to solve has to do with the WEAPON, SPELL, ITEM and AURA variables. These items are defined in the Character object as single item lists: weapon=[] and so on. Using the above method returns the list object instead of the object it contains without the []. I'd rater see a blank " " string or the list's contained object if one exists and not [].
NAME: Bones
XP: 0
HP: 100
SP: 100
STR: 14
DEX: 19
WEAPON: []
SPELL: []
ITEM: []
AURA: []
I've tried a number of experiments including replacing the {weapon} reference with {current_weapon} after defining current_weapon = weapon[0] which won't work if the list object is empty. That just errors with IndexError: list index out of range. I could generate the items at object instantiation, but that won't work as self.item will at times be an empty list container.
I could propagate the lists with " " objects but would then have to juggle them out with replacement items and keep track of this which seems very inelegant and potentially cumbersome.
I just can't seem to wrap my head around an elegant way to print the list object in the above __str__ return as currently designed. I'm still learning Python and want to believe there is a simple addition I could append to this return string to do this.
Another option is to use the power of string formatting to check attributes of what it's passed in, and the fact that self is a local variable within the method:
def __str__(self):
status = '[POISONED]' if self.poisoned else ''
weapon = self.weapon[0] if self.weapon else ''
spell = self.spell[0] if self.spell else ''
item = self.item[0] if self.item else ''
aura = self.aura[0] if self.aura else ''
return ('NAME: {self.name} {status}\n'
'XP: {self.xp}\n'
'HP: {self.hit_points}\n'
'SP: {self.spell_points}\n'
'STR: {self.strength}\n'
'DEX: {self.dexterity}\n'
'WEAPON: {weapon}\n'
'SPELL: {spell}\n'
'ITEM: {item}\n'
'AURA: {aura}\n'
).format(**locals())
You could just create a local copy of your dict, and modify the values you want, before passing that on to the format:
def __str__(self):
local_data = self.__dict__.copy()
local_data['status'] = "[POISONED]" if self.poisoned else ""
local_data['weapon'] = " " if not self.weapon else ','.join(self.weapon)
return ('NAME: {name} {status}\n' \
'XP: {xp}\n' \
'HP: {hit_points}\n' \
'SP: {spell_points}\n' \
'STR: {strength}\n' \
'DEX: {dexterity}\n' \
'WEAPON: {weapon}\n' \
'SPELL: {spell}\n' \
'ITEM: {item}\n' \
'AURA: {aura}\n' \
).format(**local_data)
It is probably better to do that, than to modify your attributes simple for the formatting, like you were doing with your self.status. Now you are just modifying temp copies.
You can do it in a simple way, even if not so trivial. You can modify the string format to take the whole object and harness the power of the properties.This has the advantage of not creating a copy of your dictionary, that can be expensive for big object.
I'll give you an example that should be close to what you need:
class A(object):
def __init__(self):
# one full list and one empty one
self.c = [1,2,3]
self.d = []
#these two preperties create a string versione when requeste
c_str = property(lambda self: ", ".join(str(i) for i in self.c))
d_str = property(lambda self: ", ".join(str(i) for i in self.d))
def __str__(self):
#you have to use the dotted version because properties are not visibles
# from the dict attribute
string = "c = {0.c_str} \nd = {0.d_str}"
return string.format(self)
a = A()
print str(a)
# c = 1, 2, 3
# d =
If you are programming some kind of game properties can be a huge lifesavers, as you can use them to obtain complicated values as attribute instead of functions, creating a lot more cleaner code. They allow you to implement even check for the insertion of value, for examples that a value is positive.
EDIT:
Why I am using the 0.c_str instead of c_str? it is because the properties are special objects that will be called only if you access them with the dot notation (self.c_str). They do not exist in the objects __dict__ so you can't use it. If you try to print the __dict__ you will see only the values c and d.
That's why I passed to the format function the whole object and accessed its attributes instead of passing it the object dictionary.
If you don't like the 0.c_str notation you can escape it differently, for example keeping it close to the usual notation:
"{self.c_str}".format(self=self)
or
"{foo.c_str}".format(foo=self)

Instantiating a unique object every time when using object composition?

As an example, just a couple of dummy objects that will be used together. FWIW this is using Python 2.7.2.
class Student(object):
def __init__(self, tool):
self.tool = tool
def draw(self):
if self.tool.broken != True:
print "I used my tool. Sweet."
else:
print "My tool is broken. Wah."
class Tool(object):
def __init__(self, name):
self.name = name
self.broken = False
def break(self):
print "The %s busted." % self.name
self.broken = True
Hammer = Tool(hammer)
Billy = Student(Hammer)
Tommy = Student(Hammer)
That's probably enough code, you see where I'm going with this. If I call Hammer.break(), I'm calling it on the same instance of the object; if Billy's hammer is broken, so is Tommy's (it's really the same Hammer after all).
Now obviously if the program were limited to just Billy and Tommy as instances of Students, the fix would be obvious - instantiate more Hammers. But clearly I'm asking because it isn't that simple, heh. I would like to know if it's possible to create objects which show up as unique instances of themselves for every time they're called into being.
EDIT: The kind of answers I'm getting lead me to believe that I have a gaping hole in my understanding of instantiation. If I have something like this:
class Foo(object):
pass
class Moo(Foo):
pass
class Guy(object):
def __init__(self, thing):
self.thing = thing
Bill = Guy(Moo())
Steve = Guy(Moo())
Each time I use Moo(), is that a separate instance, or do they both reference the same object? If they're separate, then my whole question can be withdrawn, because it'll ahve to make way for my mind getting blown.
You have to create new instances of the Tool for each Student.
class Student(object):
def __init__(self, tool):
self.tool = tool
def draw(self):
if self.tool.broken != True:
print "I used my tool. Sweet."
else:
print "My tool is broken. Wah."
class Tool(object):
def __init__(self, name):
self.name = name
self.broken = False
def break(self):
print "The %s busted." % self.name
self.broken = True
# Instead of instance, make it a callable that returns a new one
def Hammer():
return Tool('hammer')
# Pass a new object, instead of the type
Billy = Student(Hammer())
Tommy = Student(Hammer())
I'll try to be brief. Well.. I always try to be brief, but my level of success is pretty much random.randint(0, never). So yeah.
Lol. You even failed to be brief about announcing that you will try to be brief.
First, we need to be clear about what "called into being" means. Presumably you want a new hammer every time self.tool = object happens. You don't want a new instance every time, for example, you access the tool attribute, or you'd always a get a new, presumably unbroken, hammer every time you check self.tool.broken.
A couple approaches.
One, give Tool a copy method that produces a new object that should equal the original object, but be a different instance. For example:
class Tool:
def __init__(self, kind):
self.kind = kind
self.broken = False
def copy(self):
result = Tool(self.kind)
result.broken = self.broken
return result
Then in Student's init you say
self.tool = tool.copy()
Option two, use a factory function.
def makehammer():
return Tool(hammer)
class Student:
def __init__(self, factory):
self.tool = factory()
Billy = Student(makehammer)
I can't think any way in Python that you can write the line self.tool = object and have object automagically make a copy, and I don't think you want to. One thing I like about Python is WYSIWYG. If you want magic use C++. I think it makes code hard to understand when you not only can't tell what a line of code is doing, you can't even tell it's doing anything special.
Note you can get even fancier with a factory object. For example:
class RealisticFactory:
def __init__(self, kind, failurerate):
self.kind = kind
self.failurerate = failurerate
def make(self):
result = Tool(self.kind)
if random.random() < self.failurerate:
result.broken = True
if (self.failurerate < 0.01):
self.failurerate += 0.0001
return result
factory = RealisticFactory(hammer, 0.0007)
Billy = Student(factory.make)
Tommy = Student(factory.make) # Tommy's tool is slightly more likely to be broken
You could change your lines like this:
Billy = Student(Tool('hammer'))
Tommy = Student(Tool('hammer'))
That'll produce a distinct instance of your Tool class for each instance of the Student class. the trouble with your posted example code is that you haven't "called the Tool into being" (to use your words) more than once.
Just call Tool('hammer') every time you want to create a new tool.
h1 = Tool('hammer')
h2 = Tool('hammer')
Billy = Student(h1)
Tommy = Student(h2)
Oh wait, I forgot, Python does have magic.
class Student:
def __setattr__(self, attr, value):
if attr == 'tool':
self.__dict__[attr] = value.copy()
else:
self.__dict__[attr] = value
But I still say you should use magic sparingly.
After seeing the tenor of the answers here and remembering the Zen of Python, I'm going to answer my own dang question by saying, "I probably should have just thought harder about it."
I will restate my own question as the answer. Suppose I have this tiny program:
class Item(object):
def __init__(self):
self.broken = False
def smash(self):
print "This object broke."
self.broken = True
class Person(object):
def __init__(self, holding):
self.holding = holding
def using(self):
if self.holding.broken != True:
print "Pass."
else:
print "Fail."
Foo = Person(Item())
Bar = Person(Item())
Foo.holding.smash()
Foo.using()
Bar.using()
The program will return "Fail" for Foo.using() and "Pass" for Bar.using(). Upon actually thinking about what I'm doing, "Foo.holding = Item()" and "Bar.holding = Item()" are clearly different instances. I even ran this dumpy program to prove it worked as I surmised it did, and no surprises to you pros, it does. So I withdraw my question on the basis that I wasn't actually using my brain when I asked it. The funny thing is, with the program I've been working on, I was already doing it this way but assuming it was the wrong way to do it. So thanks for humoring me.

Categories

Resources