So I am trying to make a turn-based card game, and one of the cards, when played , can swap the hand of the "caster" and the "castee" (not sure if that's even a word). I created a Player class, and instances Jim, Pam, Dwight and Michael. Whenever that card is played I have to swap the hand attribute between two players.
class Player:
def __init__(self, name, hand=[]):
self.name = name
self.hand = [self.draw_card() for n in range(0, 2)]
I've tried creating a method which takes the "castee", which is the self in this case, and swaps hand with the caster, but it doesn't work. Here caster is the name of the other instance, e.g. it could be Michael.hand instead of caster.hand:
def hand_swap(self):
self.hand, caster.hand = caster.hand, self.hand
I've also tried the following with no luck:
def hand_swap(self):
myhand = self.hand.copy()
casterhand = caster.hand.copy()
setattr(caster, "hand", myhand)
setattr(self, "hand", casterhand)
After every loop, players and their hands are printed in a dictionary. The code doesn't throw up any errors, but no hand has been swapped and everything remains the same.
Is there something wrong with my code or is it not so straightforward when it comes to having one instance change another instance's attributes?
edit1: what "caster" is
edit2: how I check that it fails
If you add caster to hand_swap() you will achieve your goal.
def hand_swap(self, caster):
self.hand, caster.hand = caster.hand, self.hand
Then to swap hands
pam.hand_swap(michael)
Related
This question already has answers here:
What is the difference between class and instance attributes?
(5 answers)
Closed 4 months ago.
I'm learning how classes works and I got stuck on a thing which I can't explain, I also didn't find a solution on the Internet so here am I with my first question on StackOverflow.
class Swords:
damage = 5
Here is a class with only one line, with attribute damage.
Let's make an instance of the class:
sharp_sword = Swords()
And here is the moment of truth.
My class doesn't have an init function, so right now my sharp_sword.__dict__ is empty - my object of the class has an empty namespace, it doesn't have any attributes or methods. Well, we can create an attribute since we already defined it in the class itself:
sharp_sword.damage = 10
Now if we print(sharp_sword.damage) the return will be 10, if we print(Swords.damage), the return will be 5. That means that our instance of the class has its own attribute - and it is without any init inside my class.
I can create another object, set its damage to 15, and I will have two objects with different damage. Since we didn't change damage in class, if we print(Swords.damage), the return will be 5.
The question coming by itself - why then should I use __init__ inside my class to set properties for objects if I can do it without it with even fewer lines of code in my class?
class SwordsWithoutInit:
damage = 5
class SwordsWithInit:
def __init__(self):
self.damage = 5
big_sword = SwordsWithoutInit()
big_sword.damage = 10
sharp_sword = SwordsWithInit()
sharp_sword.damage = 20
print(f'big sword (class {type(big_sword)}) damage equals {big_sword.damage}')
print(f'sharp sword (class {type(sharp_sword)}) damage equals {sharp_sword.damage}')
# it both works the same way
# note that even if you create another object of class without init, it still will work the same as with init, there won't be any errors
another_sword = SwordsWithoutInit()
another_sword.damage = 33
print(f'big sword (class {type(big_sword)}) damage equals {big_sword.damage}')
print(f'another sword (class {type(another_sword)}) damage equals {another_sword.damage}')
print(f'Class without init still will have damage attribute equals to {SwordsWithoutInit.damage} in itself - we didnt change it')
'''
output:
big sword (class <class '__main__.SwordsWithoutInit'>) damage equals 10
#sharp sword (class <class '__main__.SwordsWithInit'>) damage equals 20
big sword (class <class '__main__.SwordsWithoutInit'>) damage equals 10
another sword (class <class '__main__.SwordsWithoutInit'>) damage equals 33
Class without init still will have damage attribute equals to 5 in itself - we didnt change it
'''
The way you've set this up, via some odd sequence you'll sort of get the result you expect either way, but it's mostly due to int being immutable.
So, let's look at a mutable type instead:
class Sword:
pass
class Spear:
pass
# All players get a sword by default
class Items:
weapons = [Sword()]
player1_items = Items()
player2_items = Items()
# give player2 a spear
player2_items.weapons.append(Spear())
# player1 should still just have a sword right?
# whoops! they have a spear too!
print(player1_items.weapons)
live example
You might now ask "well then I'll just use __init__ if I have mutable types." and I suppose that would work for the cases you're describing. It's just a strange way of structuring the code and will make it harder to read over time. To answer the question why you should favor assigning to self even "if I can do it without it with even less lines of code"..
Minimizing the number of lines isn't a good motivation to do something.
Keeping your code readable, maintainable, and fairly consistent is a good motivation.
Listen, init func is very important. With its help you can set the attributes while you create the object. like
sword = Sword(5)
This function is very useful, when you have multiple attributes to set coz you won't want to set each attribute separetly, it is also very useful when you have to make many object of the same the class, you won't want to write another extra lines for each just setting the attributes.
Hope that helps!!
Why do we use __init__ in Python?
Using __init__ is simpler
At first glance, your approach does seem shorter. By not implementing an __init__ method, your class definition becomes one line shorter and looks much cleaner:
class NoInitSword:
damage = 5
However, it actually requires a lot more code. You now have to write two lines each time you want a sword with a different value:
butter_knife = NoInitSword()
butter_knife.damage = 1
This may not seem like a big deal, but it can quickly get messy. For example, if you wanted to create a list of swords:
big_sword = NoInitSword()
big_sword.damage = 10
sharp_sword = NoInitSword()
sharp_sword.damage = 20
normal_sword = NoInitSword()
big_sharp_sword = NoInitSword()
big_sharp_sword.damage = 30
sword_collection = [big_sword, sharp_sword, normal_sword, big_sharp_sword]
Or:
sword_collection = [NoInitSword(), NoInitSword(), NoInitSword(), NoInitSword()]
sword_collection[0].damage = 10
sword_collection[1].damage = 20
sword_collection[3].damage = 30
A better approach
Fortunately, Python provides us a way of creating an object and setting its attributes to whatever values we want—all in just one line. The only thing we have to do is define an __init__ method.
This method takes arguments that we can use when initializing the object. For instance:
class Sword:
def __init__(self, damage=5):
self.damage = damage
This allows you to easily create new objects in one line:
big_sword = Sword(10) # big_sword.damage == 10
sharp_sword = Sword(20) # sharp_sword.damage == 20
normal_sword = Sword() # normal_sword.damage == 5
And that list example from earlier? Here's what it looks like with an __init__ method:
sword_collection = [Sword(10), Sword(20), Sword(), Sword(30)]
I'm trying to make a Die() class, with a DiceShaker() subclass. The DiceShaker() subclass is supposed to take multiple copies of the Die() class, roll them, and then return the individual rolls. Here is my code for Die().
class Die(object):
'a class representing a Die'
def __init__(self, sides=6):
'initialize a die, default 6 sides'
self.sides = sides
def roll(self):
'roll the dice'
self.dieRoll = random.randint(1, self.sides)
def get(self):
'get the current value of the die'
return self.dieRoll
Here is my code for DiceShaker().
class DiceShaker(Die):
'DiceShaker class'
def __init__(self,dieCount=1, dieSides=6, dieList = [], dieList2 = []):
'initialize a dice shaker'
Die.__init__(self,sides = 6)
self.dieCount = dieCount
self.dieSides = dieSides
self.dieList = dieList
self.dieList2 = dieList2
def shake(self):
'shake all the dice in the shaker'
counter = 0
dieList = []
while counter != self.dieCount:
self.dieList.append(Die.roll(self))
counter = counter + 1
return self.dieList
def getIndividualRolls(self):
'get a lsit of integers of all the individual die rolls'
for items in self.dieList:
self.dieList2.append(Die.get(self))
return self.dieList2
getIndividualRolls() is supposed to return a list with all of the dice that were passed through shake(), but the list printed is always just one number.
Example:
d=DiceShaker(3)
d.shake()
[None, None, None]
d.getIndividualRolls()
[3, 3, 3]
I have shake() returning [none] just so I know that the correct number of dice are going through, but I can't figure out how why getIndividualRolls() keeps printing out duplicates. Can anyone help me figure out what I'm doing wrong?
This will be too long for a comment:
As an aside, you should really be learning on Python 3. Python 2 is on the way out. Everything below works in Python 3.
Why is your DiceShaker a subclass of Die? This makes no sense. Subclassing is for is a relationships. DiceShaker isn't a type of Die (just like a ShoppingCart isn't a type of CartItem). Your relationship is compositional. DiceShaker has Dies (which you reflect with your dieList). In this case dieList is all you need here. You don't need to inherit from Die.
The underlying reason why you get the same number rolled is because you inherited from die. By subclassing Die, you made DiceShaker behave like a Die. That means you could've called ds.roll() and ds.get() (where ds = DiceShaker()) and it would behave exactly the same as if you called it on a Die. This is effectively what you've done when you've wrote Dice.get(self). That's equivalent to self.get() (since DiceShaker extends Dice). But DiceShaker is only one Die, not multiple. So calling get() will always return the same thing. What you've effectively done is the following:
die = Die()
die.roll()
for other_die in self.dieList:
self.dieList2.append(die.get()) # note you're getting the same die each time
So to fix this, you don't need to inherit from Die. In fact you shouldn't. Instead compose DieShaker and Die. This means DieShaker should delegate to the Dies it contains (by calling get on them, not itself).
from random import randint
class Die:
def __init__(self, sides=6):
self.sides = sides
def roll(self):
self._rolled_value = randint(1, self.sides)
def get_value_rolled(self):
return self._rolled_value
class DieShaker:
def __init__(self, num_die=1, num_die_sides=6):
self.dice = [Die(num_die_sides) for _ in range(num_die)]
def shake(self):
for die in self.dice:
die.roll()
def get_values_rolled(self):
return [die.get_value_rolled() for die in self.dice]
Note how in get_values_rolled (equivalent to your DieShaker.getIndividualRolls), we call get_value_rolled (equivalent to your Die.get) on each Die that our shaker contains (in the list self.dice).
Also note that I cleaned up your code in the following ways:
It is convention in Python to use snake_case for variables/properties and functions
I renamed a few of the functions to make their relationship more clear (get_value_rolled and get_values_rolled)
I used list comprehensions (ex. [Die(num_die_sides) for _ in range(num_die)] is the same thing as your while loop that appends to your self.dieList, but is more pythonic)
I replaced while with a for. Typically in python you want to use a for when you're working with iterables (list list).
I removed some of the optional arguments to your DieShaker constructor. For your purposes, it doesn't make much sense to allow them to be passed in (If you want to wade into the more technical, there is a reason why you'd do this, but for your purposes now, don't). It looks like you may think you need everything to be a parameter to __init__, but you don't. Instead of this (which actually has a serious issue unrelated to the style/semantics):
Bad:
def __init__(self, num_die=1, die_sides=6, dice = []):
self.dice = dice
You probably should do this:
def __init__(self, num_die=1, die_sides=6):
self.dice = []
I removed your second list. You only need to maintain a list of die (since after calling roll() on each die, it will return the same thing for get_value() each time)
Your DieShaker class should just be a wrapper around a list of Die objects.
class Die(object):
'a class representing a Die'
def __init__(self, sides=6):
'initialize a die, default 6 sides'
self.sides = sides
# You should make sure that by the time someone has created a Die object,
# all the attributes they might want to access have been defined
self.roll()
def roll(self):
'roll the dice'
self.dieRoll = random.randint(1, self.sides)
# Note I got rid of Die.get
# Anyone who can call Die.get can see Die.dieRoll
class DiceShaker(object):
def __init__(self, dice=()): # Note we avoid mutable default arguments
self.dice = list(dice) # Presumably it would be nice to add/remove dice
def shake(self):
for die in self.dice:
die.roll()
# return self.get_dice()
# It doesn't really make sense for this to return anything
def get_dice(self):
return [die.dieRoll for die in self.dice]
I have a class in python, and I'd like to be able to do an operation to one object and have that operation in turn identify another object to change and in turn modify it. To make this concrete:
Say we have a bunch of locations (the objects), and each location has some things (call them elements). I would like to tell location 1 to move one of its elements to the right by location.move_right(element). It should then add that element to location 2. (in my actual problem, the "location" would need to calculate which location it will move to, and so I don't know a priori where it's going.)
Here is the working code I have. Within the class I've placed a dictionary that will hold all objects that have been created. I can imagine this could cause trouble for garbage collection etc. I suspect there is a better way. Is there?
class location(object):
locations = {}
def __init__(self, coordinate):
self.locations[coordinate]=self
self.coord = coordinate
self.elements = set()
def add_element(self, element):
self.elements.add(element)
def move_right(self, element):
self.elements.remove(element)
new_location = self.locations[self.coord+1]
new_location.add_element(element)
x = location(1)
y=location(2)
x.add_element('r')
x.move_right('r')
What would be the fastest and/or easiest (speed preferred) way to find an object, that has certain value on certain attribute, from a list of objects. I got a list of classes, which I am looping through, and I also got a list of objects, and I need to see if the object is an instance of the currently being-looped class. Here's what I've been using so far, but it's a pain in the ass for speed purposes.
for cSoldier in soldierlist:
exists = False
for soldier in user.soldiers:
if cSoldier.id == soldier.id:
exists = True
break
if not exists:
user.soldiers.append(cSoldier())
soldierlist holds in all the different soldier classes, for example class Sniper, class Knight, etc.
user.soldiers is used for storing one instance of each soldier type for every user of this game
id each soldier class has it's unique classid, and it's also being given to each instance of that class, to recognize the instance's type
If you define __hash__ in your soldier class to return the soldier id and define __eq__ to test for id equality, you could try something like this:
user.soldiers = list(set(user.soldiers + soldierlist))
So, somewhere in your soldier class:
def __hash__(self):
return self.id
def __eq__(self, other):
return self.id == other.id
Here is an approach which avoids constantly rescanning the list
current_ids = set(soldier.id for soldier in user.soldiers)
for soldier_class in soldierlist:
if soldier_class.id not in current_ids:
user.soldiers.append( soldier_class() )
By storing all the ids in set, looking them up can be done much faster then just rescanning through the list.
I know it is too late, and now maybe useless, but i've had the same issue. So, for the record.
My fix was to make a object list and a object index:
class Soldier:
soldiers = []
soldier_list = []
s_data = {"name":"jim", "id":"foo","type":"bar"}
name = soldier.get("name")
if name not in soldier_list:
soldiers.append(Soldier(**s_data)) #creates a object with s_data attribs
soldier_list.append(name)
I asked a similar, yet lousy, question very late last night (Access to instance variable, but not instance method in Python) that caused a fair bit of confusion. I'd delete it if I could, but I can't.
I now can ask my question more clearly.
Background: I'm trying to build a black-jack game to learn python syntax. Each hand is an instance of the Hand class and I'm now at the point where I'm trying to allow for hands to be split. So, when it comes time for a hand to be split, I need to create two new hand instances. Given that further splits are possible, and I want to reuse the same methods for re-splitting hands. I therefore (I think) need to dynamically instantiate the Hand class.
Following is a code snippet I'm using to block out the mechanics:
import os
os.system("clear")
class Hand():
instances=[]
def __init__(self, hand_a, name):
Hand.instances.append(self)
self.name = name
self.hand_a = hand_a
def show_hand(self):
ln = len(self.hand_a)
for x in range(ln):
print self.hand_a[x]
class Creation():
def __init__(self):
pass
def create_name(self):
hil = len(Hand.instances)
new_name = 'hand_' + str(hil + 1)
return(new_name)
def new_instance(self):
new_dict = {0: 'Ace of Clubs', 1: '10 of Diamonds'}
new_hand_name = {}
new_hand_name.setdefault(self.create_name(), None)
print new_hand_name
new_hand_name[0] = Hand(new_dict, self.create_name())
print new_hand_name[0]
hand = Hand("blah", 'hand')
hand_z = Hand("blah_z", 'hand_z')
creation = Creation()
creation.new_instance()
here is the output:
{'hand_3': None}
<__main__.Hand instance at 0x10e0f06c8>
With regard to the instance created by the following statement:
new_hand_name[0] = Hand(new_dict, self.create_name)
Is new_hand_name[0] new the variable that refers to the instance?
Or, is hand_3 the variable?
i.e. when calling an instance method, can I use hand_3.show_hand()?
First, to answer your questions: new_hand_name[0] is the variable that refers to the instance- more specifically, it is the value in the new_hand_name dictionary accessed by the key 0. The new_hand_name dictionary, if you printed it, would look like:
{'hand_3': None, 0: <__main__.Hand instance at 0x10e0f06c8>}
Adding the value of "hand_3" to the dictionary is unnecessary, but for that matter, so is the dictionary.
What you really want to do has nothing to do with dynamic instantiation of new classes, which has nothing to do with your problem. The problem is that a Hand might represent a single list of cards, but might also represent a list of lists of cards, each of which have to be played separately. One great way to solve this is to make a separation between a player and a hand, and allow a player to have multiple hands. Imagine this design (I'm also leaving out a lot of the blackjack functionality, but leaving a little in to give you an idea of how to work this in with the rest of the program).
def draw_random_card():
"""
whatever function returns a new card. Might be in a Deck object, depends on
your design
"""
# some code here
class Player:
def __init__(self):
self.hands = []
def deal(self):
"""add a random hand"""
self.hands.append(Hand([draw_random_card(), draw_random_card()]))
def split(self, hand):
"""split the given hand"""
self.hands.remove(hand)
self.hands += hand.split()
class Hand:
def __init__(self, cards):
self.cards = cards
def hit(self):
"""add a random card"""
self.cards.append(draw_random_card())
def split(self):
"""split and return a pair of Hand objects"""
return [Hand(self.cards[0], draw_random_card()),
Hand(self.cards[1], draw_random_card())]
Isn't that simpler?
In response to your comment:
You can refer to any specific hand as self.hands[0] or self.hands[1] within the Players class.
If you want to keep track of a particular hand, you can just pass the hand itself around instead of passing a character string referring to that hand. Like this:
def process_hand(hand):
"""do something to a hand of cards"""
h.hit()
print h.cards()
h.hit()
h = Hand(cards)
process_hand(h)
This is important: modifications you make to the hand in the function work on the actual hand itself. Why put the extra step of passing a string that you then have to look up?
Also note that information specific to each hand, such as the bet, should probably be stored in the Hand class itself.
If you are sure you want to refer to each hand with a specific name (and again, it's not necessary in this case), you just use a dictionary with those names as keys:
self.hands = {}
self.hands["hand1"] = Hand([card1, card2])
self.hands["hand2"] = Hand([card1, card2])
print self.hands["hand1"]
But again, there is probably no good reason to do this. (And note that this is very different than instantiating a new variable "dynamically". It would be a good idea to look into how dictionaries work).