Instantiating a unique object every time when using object composition? - python

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.

Related

(Classname) has no attribute (attribute)

I'm trying to create a DnD style dungeon crawler game. I'm using the 5E SRD and other publicly available information as the base for my characters and gameplay.
Currently I'm working on the character generator, and it seems to be going well, but I've hit a roadblock when trying to assign the racial bonuses. I've got the races set up as their own subclasses, each with it's unique bonuses. When I try to assign the appropriate bonuses based on the character's race I get a (Classname)has no attribute (attribute) error.
python
class Race:
def __init__(self, race):
self.name = race
self.racial_str_bonus = 0
self.racial_char_bonus = 0
class Dragonborn(Race):
def __init__(self):
super()
self.name = "Dragonborn"
self.racial_str_bonus = +2
self.racial_char_bonus = +1
def get_racial_bonus(race):
race = race
racial_str_bonus = 0
racial_char_bonus = 0
if race == "Dragonborn":
racial_str_bonus = Dragonborn.racial_str_bonus
racial_char_bonus = Dragonborn.racial_char_bonus
return racial_str_bonus, racial_char_bonus
class BaseCharacter:
def __init__(self, racial_str_bonus, racial_char_bonus):
self.racial_str_bonus = racial_str_bonus
self.racial_char_bonus = racial_char_bonus
#classmethod
def generate_player_character(cls):
cls.race = input("Race: ")
get_racial_bonus(cls.race)
BaseCharacter.generate_player_character()
What I'm looking for is something along the line of:
'''
Race: Dragonborn
print(my_player_char.racial_str_bonus)
2
'''
Where am I goofing up?
Thanks, everyone for the feedback. In cleaning up the code to get it minimally reproducible, I figured out the issue. Per Jonrsharpe's note, I corrected the inhertance invocation to 'super().init(self)'. Once that was correct, I realized that the way they had been defined, I had to include parentheses in the property call: "Dragonborn().racial_str_bonus".
Thanks again, and I will remember to improve my submissions in the future.

python object oriented programming methods

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.

Python: Preventing duplication of data when using dictionaries and lists

Hello Stack Overflow!
I am executing a simple command in a program that compiles a report of all the books contained in a library. The library contains a list of shelves, each shelves contains a dictionary of books. However, despite my best efforts, I am always duplicating all my books and placing them on every shelf, instead of the shelf I've instructed the program to place the book on.
I expect I have missed out on some kind of fundamental rule with object creation and organization.
I believe the culprits are the enshelf and unshelf methods in the book class.
Thank you so much for your time,
Jake
Code below:
class book():
shelf_number = None
def __init__(self, title, author):
super(book, self).__init__()
self.title = title
self.author = author
def enshelf(self, shelf_number):
self.shelf_number = shelf_number
SPL.shelves[self.shelf_number].books[hash(self)] = self
def unshelf(self):
del SPL.shelves[self.shelf_number].books[hash(self)]
return self
def get_title(self):
return self.title
def get_author(self):
return self.author
class shelf():
books = {}
def __init__(self):
super(shelf, self).__init__()
def get_books(self):
temp_list = []
for k in self.books.keys():
temp_list.append(self.books[k].get_title())
return temp_list
class library():
shelves = []
def __init__(self, name):
super(library, self).__init__()
self.name = name
def make_shelf(self):
temp = shelf()
self.shelves.append(temp)
def remove_shelf(shelf_number):
del shelves[shelf_number]
def report_all_books(self):
temp_list = []
for x in range(0,len(self.shelves)):
temp_list.append(self.shelves[x].get_books())
print(temp_list)
#---------------------------------------------------------------------------------------
#----------------------SEATTLE PUBLIC LIBARARY -----------------------------------------
#---------------------------------------------------------------------------------------
SPL = library("Seattle Public Library")
for x in range(0,3):
SPL.make_shelf()
b1 = book("matterhorn","karl marlantes")
b2 = book("my life","bill clinton")
b3 = book("decision points","george bush")
b1.enshelf(0)
b2.enshelf(1)
b3.enshelf(2)
print(SPL.report_all_books())
b1.unshelf()
b2.unshelf()
b3.unshelf()
OUTPUT:
[['decision points', 'my life', 'matterhorn'], ['decision points', 'my life', 'matterhorn'], ['decision points', 'my life', 'matterhorn']]
None
[Finished in 0.1s]
..instead of [["decision points"],["my life"],["matterhorn"]]
Use dict.pop() instead of del.
Add self.books = {} to shelf's __init__. Don't declare books outside of the __init__, because if you do so, all of the instances of that class are going to refer to the same thing. Instead, this makes each instance have its own dictionary, which is of course what you want since a book can't be in two shelves at once.
Do the same for library and its shelves and book and its shelf_number.
Pass a library instance as an argument to enshelf and unshelf. When you refer to SPL from within your objects' methods, Python finds that there is no local SPL defined, so it searches for one outside of the local scope; but if you were to try to assign something to SPL or do some other sort of mutative business, you would get an UnboundLocalError.
Bonuses:
class book(object), class shelf(object), and class library(object). (Won't fix your problem, but you should do that anyway.)
You don't need to hash the keys before using them, they will be hashed (if they are hashable, but if you're hashing them, then they are).
There is no need to call super() unless you are inheriting from something, in which case you can delegate a method call to a parent or sibling using it - but you aren't doing that.
get_books() can be implemented as nothing more than return [self.books[k].get_title() for k in self.books.iterkeys()]
Likewise for report_all_books(): return [shlf.get_books() for shlf in self.shelves]. Note that I am not iterating over the indices, but rather over the elements themselves. Try for c in "foobar": print(c) in the interactive shell if you want to see for yourself.

problems writing address book program in python

I am writing a program to add to and update an address book. Here is my code:
EDITED
import sys
import os
list = []
class bookEntry(dict):
total = 0
def __init__(self):
bookEntry.total += 1
self.d = {}
def __del__(self):
bookEntry.total -= 1
list.remove(self)
class Person(bookEntry):
def __init__(self, n):
self.n = n
print '%s has been created' % (self.n)
def __del__(self):
print '%s has been deleted' % (self.n)
def addnewperson(self, n, e = '', ph = '', note = ''):
self.d['name'] = n
self.d['email'] = e
self.d['phone'] = ph
self.d['note'] = note
list.append()
def updateperson(self):
key = raw_input('What else would you like to add to this person?')
val = raw_input('Please add a value for %s' % (key))
self.d[key] = val
def startup():
aor = raw_input('Hello! Would you like to add an entry or retrieve one?')
if aor == 'add':
info = raw_input('Would you like to add a person or a company?')
if info == 'person':
n = raw_input('Please enter this persons name:')
e = raw_input('Please enter this persons email address:')
ph = raw_input('Please enter this persons phone number:')
note = raw_input('Please add any notes if applicable:')
X = Person(n)
X.addnewperson(n, e, ph, note)
startup()
When I run this code I get the following error:
in addnewperson
self.d['name'] = n
AttributeError: 'Person' object has no attribute 'd'
I have two questions:
UPDATED QUESTIONS
1. why isnt the d object being inherited from bookentry()?
I know this question/code is lengthy but I do not know where to go from here. Any help would be greatly appreciated.
The addnewperson shoud have 'self' as first argument; actually, the name doesn't matter ('self' is just a convention), but the first argument represent the object itself. In your case, it's interpreting n as the "self" and the other 3 as regular arguments.
____del____ must not take arguments besides 'self'.
Edit: BTW I spotted a few other problems in your example, that maybe you're not aware of:
1) d in bookentry is a class member, not an instance member. It's shared by all bookentry's instances. To create an instance member, use:
class bookentry(dict):
def __init__(self,n):
self.d = {}
# rest of your constructor
2) you're trying to access d directly (as you would do in Java, C++ etc), but Python doesn't support that. You must have a 'self' parameter in your methods, and access instance variables through it:
class person(bookentry):
def foo(self,bar):
self.d[bar] = ...
person().foo(bar)
Update: for the last problem, the solution is to call the super constructor (which must be done explicitly in Python):
class Person(bookEntry):
def __init__(self, n):
super(Person, self).__init__()
self.n = n
print '%s has been created' % (self.n)
A brief explanation: for people with background in OO languages without multiple inheritance, it feels natural to expect the super type constructor to be called implicitly, automatically choosing the most suitable one if no one is mentioned explicitly. However, things get messy when a class can inherit from two or more at the same time, for this reason Python requires the programmer to make the choices himself: which superclass constructor to call first? Or at all?
The behavior of constructors (and destructors) can vary wildly from language to language. If you have further questions about the life cycle of Python objects, a good place to start would be here, here and here.
why isnt the d object being inherited from bookentry()?
That's because __init__ of the bookEntry is not called in the __init__ of the Person:
super(Person, self).__init__()
BTW, why inherit from dict if its functionality is not used? It's better to remove it and inherit from object instead (also class names are usually CamelCased):
class BookEntry(object):

Managing Items in an Object Oriented game

Every once in a while I like to take a break from my other projects to try to make a classic adventure text-based-game (in Python, this time) as a fun project, but I always have design issues implementing the item system.
I'd like for the items in the game to descend from one base Item class, containing some attributes that every item has, such as damage and weight. My problems begin when I try to add some functionality to these items. When an item's damage gets past a threshold, it should be destroyed. And there lies my problem: I don't really know how to accomplish that.
Since del self won't work for a million different reasons, (Edit: I am intentionally providing the use of 'del' as something that I know is wrong. I know what garbage collection is, and how it is not what I want.) how should I do this (And other similar tasks)? Should each item contain some kind of reference to it's container (The player, I guess) and 'ask' for itself to be deleted?
The first thing that comes to mind is a big dictionary containing every item in the game, and each object would have a reference to this list, and both have and know it's own unique ID. I don't like this solution at all and I don't think that it's the right way to go at all. Does anybody have any suggestions?
EDIT: I'm seeing a lot of people thinking that I'm worried about garbage collection. What I'm talking about is not garbage collection, but actually removing the object from gameplay. I'm not sure about what objects should initiate the removal, etc.
I would have your object keep a reference to all of its parents. Then, when it should be destroyed, it would notify its parents. If you're already using an event system, this should integrate nicely with the rest of the game.
A nice way to avoid forcing your parent to explicitly notify the object whenever the reference is dropped or added is to use some sort of proxy. Python supports properties that will allow for code like self.weapon = Weapon() to actually hand off the duty of setting the weapon attribute to the new weapon to a user defined function.
Here's some example code using properties:
class Weapon(object):
def __init__(self, name):
self.name = name
self.parent = None
def destroy(self):
if self.parent:
self.parent.weaponDestroyed()
def WeaponRef():
def getWeapon(self):
return self._weapon
def setWeapon(self, newWeapon):
if newWeapon == None: #ensure that this is a valid weapon
delWeapon(self)
return
if hasattr(self, "weapon"): #remove old weapon's reference to us
self._weapon.parent = None
self._weapon = newWeapon
newWeapon.parent = self
def delWeapon(self):
if hasattr(self, "weapon"):
self._weapon.parent = None
del self._weapon
return property(getWeapon, setWeapon, delWeapon)
class Parent(object):
weapon = WeaponRef()
def __init__(self, name, weapon=None):
self.name = name
self.weapon = weapon
def weaponDestroyed(self):
print "%s deleting reference to %s" %(self.name, self.weapon.name)
del self.weapon
w1 = Weapon("weapon 1")
w2 = Weapon("weapon 2")
w3 = Weapon("weapon 3")
p1 = Parent("parent 1", w1)
p2 = Parent("parent 2")
w1.destroy()
p2.weapon = w2
w2.destroy()
p2.weapon = w3
w3.destroy()
Now if you're doing some sort of inventory system, where a player can have more than 1 weapon and any one of them can be destroyed at any time, then you're going to have to write your own collection class.
For something like that, just keep in mind that x[2] calls x.__getitem__(2), x[2] = 5 calls x.__setitem__(2, 5) and del x[2] calls x.__delitem__(2)
You're conflating two meanings of the "destroying" idea. The Item should get destroyed in a "gameplay" sense. Let the garbage collector worry about when to destroy it as an object.
Who has a reference to the Item? Perhaps the player has it in his inventory, or it is in a room in the game. In either case your Inventory or Room objects know about the Item. Tell them the Item has been destroyed (in a gameplay sense) and let them handle that. Perhaps they'll now keep a reference to a "broken" Item. Perhaps they'll keep track of it, but not display it to the user. Perhaps they'll delete all references to it, in which case the object in memory will soon be deleted.
The beauty of object-oriented programming is that you can abstract these processes away from the Item itself: pass the messages to whoever needs to know, and let them implement in their own way what it means for the Item to be destroyed.
One option would be to use a signal system
Firstly, we have a reusable class that lets you define a signal
class Signal(object):
def __init__(self):
self._handlers = []
def connect(self, handler):
self._handlers.append(handler)
def fire(self, *args):
for handler in self._handlers:
handler(*args)
Your item class uses this signal to create a destroyed signal that other classes can listen for.
class Item(object):
def __init__(self):
self.destroyed = Signal()
def destroy(self):
self.destroyed.fire(self)
And inventory listens to the signals from the items and updates its internal state accordingly
class Inventory(object):
def __init__(self):
self._items = []
def add(self, item):
item.destroyed.connect(self.on_destroyed)
self._items.add(item)
def on_destroyed(self, item):
self._items.remove(item)
Assuming you call a method when the item is used, you could always return a boolean value indicating whether it's broken.
How about:
from collections import defaultdict
_items = defaultdict(set)
_owner = {}
class CanHaveItems(object):
#property
def items(self):
return iter(_items[self])
def take(self, item):
item.change_owner(self)
def lose(self, item):
""" local cleanup """
class _nobody(CanHaveItems):
def __repr__(self):
return '_nobody'
_nobody = _nobody()
class Destroyed(object):
def __repr__(self):
return 'This is an ex-item!'
class Item(object):
def __new__(cls, *a, **k):
self = object.__new__(cls)
_owner[self] = _nobody
_items[_nobody].add(self)
self._damage = .0
return self
def destroy(self):
self.change_owner(_nobody)
self.__class__ = Destroyed
#property
def damage(self):
return self._damage
#damage.setter
def damage(self, value):
self._damage = value
if self._damage >= 1.:
self.destroy()
def change_owner(self, new_owner):
old_owner = _owner[self]
old_owner.lose(self)
_items[old_owner].discard(self)
_owner[self] = new_owner
_items[new_owner].add(self)
class Ball(Item):
def __init__(self, color):
self.color = color
def __repr__(self):
return 'Ball(%s)' % self.color
class Player(CanHaveItems):
def __init__(self, name):
self.name = name
def __repr__(self):
return 'Player(%s)' % self.name
ball = Ball('red')
ball = Ball('blue')
joe = Player('joe')
jim = Player('jim')
print list(joe.items), ':', list(jim.items)
joe.take(ball)
print list(joe.items), ':', list(jim.items)
jim.take(ball)
print list(joe.items), ':', list(jim.items)
print ball, ':', _owner[ball], ':', list(jim.items)
ball.damage += 2
print ball, ':', _owner[ball], ':', list(jim.items)
print _items, ':', _owner
at first: i don't have any python experience, so think about this in a more general way
your item should neither know or care ... your Item should have an interface that says it is something destroyable. containers and other objects that care about things that can be destroyed, can make use of that interface
that destroyable interface could have some option for consuming objects to register a callback or event, triggered when the item gets destroyed

Categories

Resources