I re-wrote the entire class from scratch in a separate file and everything magically worked, conditionals and all. So, I simply imported that class and a couple functions from the new file into the master file. I still have no idea what went wrong the first time around.
Note the issues below were technically solved. You can see a typo towards the bottom of the code. That, however, uncovered an issue where all of my conditionals (if, try, etc.) stopped functioning, which is why I re-wrote the class in a separate module
I would delete this post since it got everyone nowhere, but that's not how things work on Stack Overflow, apparently.
Alright, I've been learning Python 3.4 and decided to do some homework on the side as practice. I started making a script that does a very basic simulation of 2 people fighting, and would expand upon it with any new stuff I learn (such as adding a GUI).
The script started out fine, but the more changes I made the more errors started showing up. Now it's to the point where I can't access any fields of the "fighter" class without it throwing errors such as:
'duelist' object has no attribute '_duelist__health'
Besides "'duelist' object has no attribute '_duelist__XXX'", I've had 0 other errors besides typos.
Google unfortunately couldn't help with this one, so that's why I'm making my first StackOverflow post.
Here's the class down to the first error-happy field, "health":
class duelist:
def __init__(self):
self.name = "Duelist" #must not be ""
self.health = 5 #must be >0
self.damage = [1, 3] #random attack range. Must be >=0 0 and the first must not be higher.
self.skill = 10 #% chance to pass a skill check. Representative of parrying/dodging. Must be >=0
self.shield = True #can block?
self.shieldE = 80 #max block %. Must be >0
self.agility = 0.5 #rate of attack in seconds. Must be >=0.05
self.precision = 10 #critical hit chance. Must be >=0
self.critical = 2.0 #critical multiplier. Must be >= 1.1
#name
#property
def name(self):
return self.__name
#name.setter
def name(self, value):
if value != "":
self.__name = value
else:
print("Invalid Name.\n")
#name
#health
#property
def health(self):
return self.__health
#health.setter
def health(self, value):
try:
value = value(int)
if value>=1:
self.__health = value
else:
print("Health must be above 0.\n")
except:
print("Invalid Health.\n")
#health
Also, for those suggesting to change the field names to not include an ' __ ' (or to include an ' __ ' everywhere), that causes an infinite loop.
Typing exactly this:
class duelist:
def __init__(self):
self.health = 5
#property
def health(self):
return self.__health
#health.setter
def health(self, value):
self.__health = value
D = duelist()
print(D.health)
D.health = 15
print(D.health)
Correctly returns
5
15
Your code does:
Set the value of .name = inside __init__()
That goes through the setter, and sets .__name =
When you read it by .name that reads through the getter, and reads .__name
And your description of the problem "I can't access any fields of the "fighter" class", I suspect is wrong, because accessing some of them work.
Health doesn't, though, and on line 45 you have this:
value = value(int)
instead of
value = int(value)
So that causes the getter to throw an exception and print("Invalid Health.\n") and __health is never set.
With self.shield = True, the setter is trying to do:
if value.lower() == 'y':
and you can't call lower() on a boolean, so it crashes out before it ever gets to try to type(value) == bool, and __shield is never set.
The exception can be caused if your class does not define an attribute __health in its __init__() method. Trying to read its value will trigger the problem. The attribute will be created by calling the setter method (indirectly through attribute assignment), and after that the attribute becomes available.
Here is a simplified version of your class:
class Duelist:
def __init__(self):
# self.health = 5
pass
#property
def health(self):
return self.__health
#health.setter
def health(self, value):
self.__health = value
>>> d = Duelist()
>>> d.health
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "p.py", line 9, in health
return self.__health
AttributeError: 'Duelist' object has no attribute '_Duelist__health'
>>> d.health = 123
>>> d.health
123
So one way to fix it is to initialise the attributes using the same name:
class Duelist:
def __init__(self):
# self.health = 5 # this will also work
self.__health = 5
>>> d.health
5
>>> d.health = 123
>>> d.health
123
So, given that the exception will be raised if you do not initialise the property (self.health) or the underlying attribute (self.__health), have you posted the actual code that causes the problem in your question?
Related
In this example, what should be done so that print(left_hand.number_of_fingers) returns 4 and not 5?
class Hand:
def __init__(self, fingers:list):
self.fingers = fingers
self.number_of_fingers = len(fingers)
left_hand = Hand(["thumb", "index", "middle", "ring", "pinkie"])
left_hand.fingers.pop()
print(left_hand.number_of_fingers) # I want this to actualize and be 4, not 5
I found a solution using #property
class Hand:
def __init__(self, fingers:list):
self.fingers = fingers
#property
def number_of_fingers(self):
return len(self.fingers)
But I'm not satisfied because of a computational power issue, if computing number_of_fingers was expensive we would only want to compute it whenever fingers is modified, not every time the user asks for the attribute number_of_fingers.
Now I found a not elegant solution to solve the issue with computational power:
class Hand:
def __init__(self, fingers:list):
self.fingers = fingers
self.old_fingers = fingers
self.number_of_fingers = len(fingers)
def get_number_of_fingers(self):
if self.fingers != self.old_fingers:
self.old_fingers = self.fingers
self.number_of_fingers = len(self.fingers)
return self.number_of_fingers
The problem is that the underlying list in your Hand class, i.e. self.fingers, is not sufficiently encapsulated so that any user can be modifying it, for example by calling left_hand.fingers.pop() or even by assigning to it a new list. Therefore, you cannot assume that it has not been modified between calls to number_of_fingers and therefore you have no choice but to compute its length in that call.
The solution is to control what clients of your class can and cannot do. The easiest way to do this is by using name mangling. That is, you prefix your attribute names with two leading underscore characters. This makes it difficult (although not impossible) for clients of your class to access these attributes from outside of the class (we assume that your users are not intentionally malicious). And therefore we have to provide now a pop method:
class Hand:
def __init__(self, fingers:list):
self.__fingers = fingers
self.__number_of_fingers = len(fingers)
def pop(self):
assert(self.__fingers)
self.__number_of_fingers -= 1
return self.__fingers.pop()
#property
def number_of_fingers(self):
return self.__number_of_fingers
left_hand = Hand(["thumb", "index", "middle", "ring", "pinkie"])
print(left_hand.pop())
print(left_hand.number_of_fingers)
Prints:
pinkie
4
I am not suggesting that you actually do the following, but if you wanted to you can get more elaborate by creating special class decorators #Private and #Public that will wrap your class in a new class and check access to your attributes ensuring that you are not accessing those attributes defined to be private. You use either the #Private decorator to define those attributes/methods that are private (everything else is considered public) or the #Public decorator to define those attributes/methods that are public (everything else is considered private), but not both. You would typically name your private attributes with a leading single underscore, which is the convention that tells users that the attribute/method is to be considered private.
This is meant more to catch inadvertent access of attributes that are meant to be private. If you execute the code with the -O Python flag, then no runtime checks will be made.
def accessControl(failIf):
def onDecorator(aClass):
if not __debug__:
return aClass
else:
class onInstance:
def __init__(self, *args, **kargs):
self.__wrapped = aClass(*args, **kargs)
def __getattr__(self, attr):
if failIf(attr):
raise TypeError('private attribute fetch: ' + attr)
else:
return getattr(self.__wrapped, attr)
def __setattr__(self, attr, value):
if attr == '_onInstance__wrapped':
self.__dict__[attr] = value
elif failIf(attr):
raise TypeError('private attribute change: ' + attr)
else:
setattr(self.__wrapped, attr, value)
return onInstance
return onDecorator
def Private(*attributes):
return accessControl(failIf=(lambda attr: attr in attributes))
def Public(*attributes):
return accessControl(failIf=(lambda attr: attr not in attributes))
#Private('_fingers', '_number_of_fingers')
class Hand:
def __init__(self, fingers:list):
self._fingers = fingers
self._number_of_fingers = len(fingers)
def pop(self):
assert(self._fingers)
self._number_of_fingers -= 1
return self._fingers.pop()
#property
def number_of_fingers(self):
return self._number_of_fingers
left_hand = Hand(["thumb", "index", "middle", "ring", "pinkie"])
print(left_hand.pop())
print(left_hand.number_of_fingers)
# Thsis will throw an exception:
print(left_hand._fingers)
Prints:
pinkie
4
Traceback (most recent call last):
File "C:\Booboo\test\test.py", line 50, in <module>
print(left_hand._fingers)
File "C:\Booboo\test\test.py", line 9, in __getattr__
raise TypeError('private attribute fetch: ' + attr)
TypeError: private attribute fetch: _fingers
Update
This is the OP's approach using a cache:
class Hand:
def __init__(self, fingers:list):
self._cache = {}
self.fingers = fingers
def get_number_of_fingers(self):
fingers = tuple(self.fingers) # can be key of a dictionary
fingers_length = self._cache.get(fingers)
if fingers_length:
print(self.fingers, 'in cache')
return fingers_length
fingers_length = len(fingers)
self._cache[fingers] = fingers_length
return fingers_length
left_hand_fingers = ["thumb", "index", "middle", "ring", "pinkie"]
right_hand_fingers = ["thumb", "middle", "ring", "pinkie"]
hand = Hand(left_hand_fingers)
print(hand.get_number_of_fingers())
hand.fingers = right_hand_fingers
print(hand.get_number_of_fingers())
hand.fingers = left_hand_fingers
print(hand.get_number_of_fingers())
hand.fingers = right_hand_fingers
print(hand.get_number_of_fingers())
hand.fingers = left_hand_fingers
print(hand.get_number_of_fingers())
Prints:
5
4
['thumb', 'index', 'middle', 'ring', 'pinkie'] in cache
5
['thumb', 'middle', 'ring', 'pinkie'] in cache
4
['thumb', 'index', 'middle', 'ring', 'pinkie'] in cache
5
So here in the first code (without using #property), you will get the output as 5 and not 4, because you are simply assigning the value of len(fingers) to number_of_fingers attribute while initialising a Hand object, and number_of_fingers attribute is not getting linked to fingers.
So even if left_hand.fingers is modified in between the code, it will have no effect on the value of number_of_fingers. One cannot change this behaviour.
Also you don't need that #property, I tested and found that there will be no error if it is not written.
And finally coming to
But I'm not satisfied, because if computing number_of_fingers was expensive we would only want to compute it whenever fingers is modified, not every time the user asks for the attribute number_of_fingers.
Where do you need so much computing power?
I have defined the below code but there seems to be issues regarding methods load and damage.
(edited based on suggestions by ShadowRanger):
class RangedWeapon(Weapon):
def __init__(self, name, min_dmg, max_dmg):
super().__init__(name, min_dmg, max_dmg)
self.shots=0
def shots_left(self):
return self.shots
def load(self, ammo):
if ammo.weapon_type()==self.name:
self.shots+=ammo.get_quantity()
ammo.remove_all()
def damage(self):
if self.shots==0:
return 0
else:
self.shots-=1
return super().damage()
_
bow = RangedWeapon('bow', 10, 40)
crossbow = RangedWeapon('crossbow', 15, 45)
arrows = Ammo('arrow', bow, 5)
bolts = Ammo('bolt', crossbow, 10)
bow.load(arrows)
print(bow.shots_left()) # should return 5
print(arrows.get_quantity()) #should return 0
But for print(bow.shots_left()) I got 0 and print(arrows.get_quantity()) I got 5 instead. They are reversed. I think my problem is that I didn't load the Ammo quantity? I'm not very sure. Any help would be appreciated. Thank you!
class Ammo(Thing):
def __init__(self, name, weapon, quantity):
self.name=name
self.weapon=weapon
self.quantity=quantity
def get_quantity(self):
return self.quantity
def weapon_type(self):
return self.weapon.name
def remove_all(self):
self.quantity=0
Primary problem: Ammo's weapon_type is a method, not an attribute or property, and you didn't call it, so you're comparing the method itself to the name, not the result of calling it. This is the reason why load does nothing; no method is ever equal to a string.
Other issues:
It looks like you're calling methods on the class, not on the instances. You pass ammo (an instance) as an argument, then call methods on Ammo (the class).
Similarly, your damage method should probably be calling super().damage() not Weapon.damage(), since the latter doesn't use your instance state. And you've got typos (shots vs. shot) that should make this code non-functional in other ways.
Short version: This code is broken in a million ways, and you'll run into each of them as you fix the previous issues.
I don't understand the meaning of this problem or how to fix it!
I keep getting the problem AttributeError: 'list' object has no attribute 'assignmentScores'
What does this mean? and how do I fix this issue?
My code is:
class Student:
studentName = ""
studentCourse = ""
averageMark = 0
grade = "none"
assignmentScores = [1, 2, 3, 4]
def __init__(self, n, c, a, g,m):
self.studentName = n
self.studentCourse = c
self.averageMark = a
self.grade = g
self.assignmentScores = m
def getName(self):
return self.studentName
def getCourse(self):
return self.studentCourse
def getAverage(self):
return self.averageMark
def getGrade(self):
return self.grade
def getMarks(self):
return self.assignmentScores
def setAverage(self):
mark = self.averageMark
return mark
def setGrade(self):
grade = self.grade
return grade
def setMarks(self):
marks = self.setMarks()
return marks
def addMark(self):
score = list.append(self, self.assignmentScores)
def calculateAverage(self):
if len(self.assignmentScores) > 0:
average = sum(self) / float(len(self.assignmentScores))
return average
else:
return 0
def determineGrade(self):
return 0
print(calculateAverage(assignmentScores))
First, please use 4 spaces for all indentation, it helps a lot. PEP 8 is your friend and will keep everyone friendly and helpful.
As for your problem, after running the code myself and looking at the traceback, it looks like you assigned the self.assignmentScores list to self itself, so when you type self.assignmentScores you are looking up an attribute of self, which is now a list instead of an instance of the class.
This mistake comes from the way you called the method:
calculateAverage(assignmentScores)
This method only requires one argument, which is supposed to be an instance of the class Student, but not only are you calling the method directly from the class instead of from an instance, you are using the assignmentScores list as an argument for the method. This makes it so that the method calculateAverage() replaces self with self.assignmentScores so when you try to check if the list is empty the code is reading it as self.assignmentScore.assignmentScore instead of the intended way.
The way you have the class defined at the moment strongly encourages you to call the method like this.
billy = Student("","",0,"none",[1,2,3,4])
print(billy.calculateAverage())
There is another error standing in your way after you solve this problem, but a good look at the traceback and a careful reading of the relevant code will lead you to the solution. Right now all you need is a better understanding of classes and calling methods work.
I am trying to simply get the value out of my class using a simple function with a return value, I'm sure its a trivial error, but im pretty new to python
I have a simply class set up like this:
class score():
#initialize the score info
def __init__(self):
self.score = 0
self.num_enemies = 5
self.num_lives = 3
# Score Info
def setScore(num):
self.score = num
# Enemy Info
def getEnemies():
return self.num_enemies
# Lives Info
def getLives():
return self.getLives
etc.....
Than I create an instance of the class as such:
scoreObj = score()
for enemies in range(0, scoreObj.getEnemies):
enemy_sprite.add(enemy())
I get the error saying that an integer is expected, but it got an instancemethod
What is the correct way to get this information?
Thanks!
scoreObj.getEnemies is a reference to the method. If you want to call it you need parentheses: scoreObj.getEnemies().
You should think about why you are using a method for this instead of just reading self.num_enemies directly. There is no need for trivial getter/setter methods like this in Python.
The first parameter for a member function in python is a reference back to the Object.
Traditionally you call it "self", but no matter what you call the first parameter, it refers back to the "self" object:
Anytime I get weird errors about the type of a parameter in python, I check to see if I forgot the self param. Been bit by this bug a few times.
class score():
#initialize the score info
def __init__(self):
self.score = 0
self.num_enemies = 5
self.num_lives = 3
# Score Info
def setScore(self, num):
self.score = num
# Enemy Info
def getEnemies(self):
return self.num_enemies
# Lives Info
def getLives(foo): #foo is still the same object as self!!
return foo.num_lives
#Works but don't do this because it is confusing
This code works:
class score():
def __init__(self):
self.score = 0
self.num_enemies = 5
self.num_lives = 3
def setScore(self, num):
self.score = num
def getEnemies(self):
return self.num_enemies
def getLives(self):
return self.getLives
scoreObj = score()
for enemy_num in range(0, scoreObj.getEnemies()):
print enemy_num
# I don't know what enemy_sprite is, but
# I commented it out and just print the enemy_num result.
# enemy_sprite.add(enemy())
Lesson Learned:
Class functions must always take one parameter, self.
That's because when you call a function within the class, you always call it with the class name as the calling object, such as:
scoreObj = score()
scoreObj.getEnemies()
Where x is the class object, which will be passed to getEnemies() as the root object, meaning the first parameter sent to the class.
Secondly, when calling functions within a class (or at all), always end with () since that's the definition of calling something in Python.
Then, ask yourself, "Why am I not fetching 'scoreObj.num_lives' just like so instead? Am I saving processing power?" Do as you choose, but it would go faster if you get the values directly from the class object, unless you want to calculate stuff at the same time. Then your logic makes perfect sense!
You made a simple mistake:
scoreObj.getEnemies()
getEnemies is a function, so call it like any other function scoreObj.getEnemies()
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.