Having trouble understanding order of evaluation in Python method call - python

Just wondering if anyone can help me understand this code a little better. Here is the program:
class Card(object):
""" A playing card. """
RANKS = ["A", "2", "3", "4", "5", "6", "7",
"8", "9", "10", "J", "Q", "K"]
SUITS = ["c", "d", "h", "s"]
def __init__(self, rank, suit):
self.rank = rank
self.suit = suit
def __str__(self):
rep = self.rank + self.suit
return rep
class Hand(object):
""" A hand of playing cards. """
def __init__(self):
self.cards = []
def __str__(self):
if self.cards:
rep = ""
for card in self.cards:
rep += str(card) + "\t"
else:
rep = "<empty>"
return rep
def clear(self):
self.cards = []
def add(self, card):
self.cards.append(card)
def give(self, card, other_hand):
self.cards.remove(card)
other_hand.add(card)
class Deck(Hand):
""" A deck of playing cards. """
def populate(self):
for suit in Card.SUITS:
for rank in Card.RANKS:
self.add(Card(rank, suit)) # <- HERE
def shuffle(self):
import random
random.shuffle(self.cards)
def deal(self, hands, per_hand = 1):
for rounds in range(per_hand):
for hand in hands:
if self.cards:
top_card = self.cards[0]
self.give(top_card, hand)
else:
print("Can't continue deal. Out of cards!")
# main
deck1 = Deck()
print("Created a new deck.")
print("Deck:")
print(deck1)
deck1.populate()
print("\nPopulated the deck.")
print("Deck:")
print(deck1)
The thing I'm wondering about is self.add(Card(rank, suit)) it's confusing me. Does Card(rank, suit) get sent to the add method straight away or does it get sent to the Card class first and then go to the add method? The add method has a cardparameter, what gets put into that parameter?
If I'm not making much sense it's because I've been staring at this for 2 hours.
Thanks

It's pretty simple.
Any time you call a class in Python, you create a new instance of the class. So if you call the Card class, you create a new instance of class Card... in other words, you create a new card.
Let's create the ace of spades:
ace_of_spades = Card('A', 's')
But I'm cheating. I looked and saw what strings to pass to Card when I should be using the pre-defined constants. Let's do it more properly:
ace_of_spades = Card(Card.RANKS[0], Card.SUITS[3])
Hmm, I'm not sure that Card.RANKS[0] is that easy to understand. Personally I would probably do something like
class Card(object):
RANK_ACE = 'A'
RANK_2 = '2'
# and so on
and then:
ace_of_spades = Card(Card.RANK_ACE, Card.SUIT_SPADES)
Now that we have used the variable name ace_of_spades to hold a reference to the card, we can use the card:
hand = Hand()
hand.add(ace_of_spades)
But if we just want to get the card into the hand, we don't need that variable name ace_of_spades. We could remove the name:
del(ace_of_spades)
But there is no need to use the name in the first place. We can simply do:
hand.add(Card(Card.RANK_ACE, Card.SUIT_SPADES))
This creates the card, then immediately passes the newly created card to Hand.add() to add it to a hand.
You don't, strictly speaking, need to store the ranks and suits inside the Card class. In Python, it might be more common to put these things outside of the classes, in "module" scope. I would most likely do:
RANK_ACE = 'A'
RANK_2 = '2'
# and so on
SUIT_CLUBS = 'c'
# and so on
class Card(object):
# and so on
But if you are taking a class and your teacher wants you to put constants inside your programs' classes, then do that.
EDIT: You are trying to figure out what self.add() does in the program.
Here's the source code for it:
# inside class Hand
def add(self, card):
self.cards.append(card)
self refers to the object. To see how this works, let's imagine you have an instance of class Hand, saved in the variable h. If you call h.add(ace_of_spades) then the self in this function will be set to the same instance of Hand that h refers to.
In fact, the call h.add(ace_of_spades) is exactly the same as doing this call:
Hand.add(h, ace_of_spades)
When you use the form h.add(), Python automatically does the same thing as the above call. Python looks at h and figures out that it is an instance of class Hand. Then Python looks in class Hand for a function called add(). Then Python builds a call similar to the one shown above.
self.cards refers to a list object stored inside the Hand instance. Python lists have a method function, .append(), that appends an item to the list.
You can read more about classes and method functions here: http://docs.python.org/2/tutorial/classes.html

Good question. You are definitely making sense :)
Here's what's happening in self.add(Card(rank, suit)): note that self.add(Card(rank, suit)) is being called from within a nested for loop. The rank and suit parameters are defined by those loops. You are right, Card(rank, suit) is evaluated first, then the returned card object is fed as a parameter to self.add method on the Deck. So, the combination of loops and method calls populates the deck with 52 card objects -- one of each suit and face. Does that make sense?

Q1: Does (Card(rank, suit)) get sent to the add method straight away or
does it get sent to the Card class first and then go to the add
method?
Here's what's happening in self.add(Card(rank, suit)):
Card(rank, suit) calls Card's __new__ and __init__ methods. __new__ creates a new Card object and the __init__ method initializes it to have the given rank an suit.
That new card is passed to Deck.add() method where it is appended to self.cards list (Note that Deck inherits the cards list and add method from Hand)
Here I've stripped the code down to only the necessary methods for reference:
class Card(object):
def __init__(self, rank, suit):
self.rank = rank
self.suit = suit
class Hand(object):
def __init__(self):
self.cards = []
def add(self, card):
self.cards.append(card)
class Deck(Hand):
def populate(self):
for suit in Card.SUITS:
for rank in Card.RANKS:
self.add(Card(rank, suit))
Q2: The add method has a 'card' parameter, what gets put into that
parameter?
The newly created Card as described above

If you add two print statements like these:
In Card class:
def __init__(self, rank, suit):
print "card.init: {} {}".format(rank, suit)
self.rank = rank
self.suit = suit
In Hand:
def add(self, card):
print "add: {}".format(card)
self.cards.append(card)
You will see this output:
Created a new deck.
Deck:
<empty>
card.init: A c
add: Ac
card.init: 2 c
add: 2c
card.init: 3 c
add: 3c

Related

Print Object / Instance of Class in Python

Deck is class with attribute "deck" which is list. This list contains cards. cards is also an object of class Card with attribute suit and rank..
How can I print instance / object deck of class Deck?
class Deck:
def __init__(self):
self.deck = []
for suit in suits:
for rank in ranks:
self.deck.append(Card(suit,rank))
You should override repr() or str() function. It depends python version.
You can write that you want see data.
Then you can use print(Deck()) or print(instance)
Add this function into you class definition, you can change it for what style you want.
def __repr__(self):
return str(self.deck)
on the line after that, use a print function on deck
class Deck:
def __init__(self):
self.deck = []
for suit in suits:
for rank in ranks:
self.deck.append(Card(suit,rank))
print(self.deck)
Then whenever that code is run, which it will be whenever you make a new Deck, it will print deck after

Using self_value in another class

I'm trying to learn more about classes so I've built a deck of cards using multiple classes. The goal is to be able to play multiple card games with the deck.
I have a basic class, called PlayingCard:
class PlayingCard:
def __init__(self, suit, value):
self.suit = suit
self.value = value
And different sub-classes for the numbered cards, the jack cards, the queen cards etc. defined as:
class NumberedCards(PlayingCard):
def __init__(self, suit, value):
self.value = value
super().__init__(suit, self.value)
class JackCard(PlayingCard):
def __init__(self, suit):
self.value = 11
super().__init__(suit, self.value)
...and so on
I then build my deck of cards in a new class, 'Deck':
class Deck:
def __init__(self):
self.deck = []
def create_deck(self):
for suit in Suit:
for i in range(2, 11):
self.deck.append(NumberedCards(suit.value, i))
self.deck.append(JackCard(suit.value))
...
Lastly, I have a class called Hand:
class Hand:
def __init__(self):
self.hand = []
where I have functions to draw cards from the deck and place them in self.hand
I now want to create yet another class with different functions, where I can use a Hand()-instance to check if there's for example any pairs in the hand or if there's a straight etc.
I have something like this in mind:
class BestHand:
def __init__(self):
...
def check_pairs(self, cards):
"""Here, for example, I want to extract the value of
each card on hand and put them in a new list for easier comparison"""
where I then can check the cards in the hand by calling something in the style of
hand = Hand()
test = BestHand()
test.check_pairs(hand)
The problem is that I can't extract the value from the hand-cards. I realize they're only defined as self.value in the class PlayingCard, but is there a way to extract them to use in the BestHand class?
You have an object called hand, it is of type Hand meaning it has an attribute called hand which is list of Card objects. hand.hand is the list of Card objects in that object. [card.value for card in hand.hand] will get the value for each card in that list of cards

Python object keeping data from previous?

I've seen multiple instances of this question like this one, but it fails to identify what exactly I am doing wrong since I don't have default arguments.
What am I doing wrong? Python object instantiation keeping data from previous instantiation?
#Table.py
class Table:
def __init__(self, players):
self.deck = Deck()
And this is Main
t = Table(2)
print len(t.deck.cards)
t = Table(2)
print len(t.deck.cards)
I would expect this to print 48 each time, but instead it prints
48 and then 96
Why is this? Shouldn't this member variable be overridden every time?
#Deck.py
from Card import *
import random
class Deck:
suits = ['H','C','D','S']
numbers = [2,3,4,5,6,7,8,9,10,11,12,13,14]
cards = []
def __init__(self):
for num in self.numbers:
for suit in self.suits:
c = Card(num,suit)
self.cards.append(c);
random.shuffle(self.cards)
Card.py
class Card:
def __init__(self, num, suit):
self.num = num
self.suit = suit
def __repr__(self):
return str(self.num) + str(self.suit)
def __str__(self):
return str(self.num) + str(self.suit)
Initialize cards in the constructor, like this:
def __init__(self):
self.cards = []
for num in self.numbers:
for suit in self.suits:
c = Card(num,suit)
self.cards.append(c);
random.shuffle(self.cards)
That way, every time a new instance of the class is created, cards will be freshly initialized.
Your approach didn't work as you wished, since cards is a class data member, shared among all instances of class Deck.
suits, numbers and cards are class variables. So when doing self.cards.append(c) you add to a class variable, which is shared by all instances of all Deck instances.
Put them into __init__ instead:
def __init__(self):
self.cards = []
for num in self.numbers:
for suit in self.suits:
c = Card(num,suit)
self.cards.append(c);
random.shuffle(self.cards)
You are using class variables instead of instance variables. See, for example, python class variables
So even though you instantiate a new instance, you don't get a new instance of the static class variables.
Suits, numbers, cards. If you want instance variables, use "self.", and do it in the init function.
You are appending cards each time you instantiate, but you are appending them to the class variable. Thus you end up with twice as many.

python "in" application for classes

How can you extend "in" keyword to a class I made? I am making a card game with a Card class. There is another class which is a Hand of a player. Basically I want to see if a certain card is in a hand. An analogy is below:
>>> 5 in range(0, 5)
True
This is my code. I have a Hand class and I want to see if a Card() is in a Hand()
Also, I'm new to this concept of classes. I'm just starting to understand how this whole thing works. Did I implement len method correctly?
class Card:
def __init__(self, suit, rank):
# self.suit > str
# self.rank > str
if (suit in SUITS) and (rank in RANKS):
self.suit = suit
self.rank = rank
else:
self.suit = None
self.rank = None
print "Invalid card:", suit, rank
def __str__(self):
return self.suit + self.rank
def get_suit(self):
return self.suit
def get_rank(self):
return self.rank
# define hand class
class Hand:
# A list of Card objects
# adding cards to the hand should remove cards from the deck.
def __init__(self):
self.hand = []
def __str__(self):
cards = []
for card in self.hand:
cards += [card.get_suit() + card.get_rank()]
return str(cards)
def add_card(self, card):
return self.hand.append(card)
def __len__(self):
counter = 0
for card in self.hand:
counter +=1
return counter
OK, so I added this code in the hand class:
def __contains__(self, card):
return card in self.hand
but I tried testing my code and it doesn't work:
c = Card('H','A')
h = Hand()
h.add_card(Card('S','K'))
h.add_card(Card('D','A'))
h.add_card(Card('H','A'))
print 'hand=', h
print 'c=', c
print 'c in h', c in h
It says False in terminal... Why??
You're looking for the __contains__ magic method.
As for len, your implementation gives the right result, but is needlessly complicated. You can just do:
def __len__(self):
return len(self.hand)
#BrenBarn gave you a pointer in the right direction to look at __contains__. However, as I commented on his answer, implementing that method will probably require that your Card objects be comparable. Right now, two cards will only appear equal if they are both the same object.
For an example of what I mean, try this:
c1 = Card("H", "A")
c2 = Card("H", "A")
print c1 == c2 # False!
To fix this, you need to add the __eq__ method to your Card class (and probably the __ne__ method too, so you'll be able to use != tests). Here's a possible implementation:
def __eq__(self, other):
return self.suit == other.suit and self.rank == other rank
def __ne__(self, other):
return not self == other
There's one other thing I'd like to point out (unrelated to your question). Your Card class has "getter" methods for the suit and rank. Those are usually unnecessary in Python, where you can generally program everything using public variables at first. That is, anything that currently calls card.get_suit should just access card.suit instead.
In less common situation where you need to do complicated things in response to variable access (like calculating certain values when they're requested, or preventing certain values from being assigned), you can put a Property instance in the class (usually as a decorator to a function), and external code can still access it just as if it was still a public variable. Code with lots of getters is common in other programming languages which can't switch between regular variables and Properties like Python can.

Managing Instances in Python

I am new to Python and this is my first time asking a stackOverflow question, but a long time reader. I am working on a simple card based game but am having trouble managing instances of my Hand class. If you look below you can see that the hand class is a simple container for cards(which are just int values) and each Player class contains a hand class. However, whenever I create multiple instances of my Player class they all seem to manipulate a single instance of the Hand class. From my experience in C and Java it seems that I am somehow making my Hand class static. If anyone could help with this problem I would appreciate it greatly.
Thank you,
Thad
To clarify: An example of this situation would be
p = player.Player()
p1 = player.Player()
p.recieveCard(15)
p1.recieveCard(21)
p.viewHand()
which would result in:
[15,21]
even though only one card was added to p
Hand class:
class Hand:
index = 0
cards = [] #Collections of cards
#Constructor
def __init__(self):
self.index
self.cards
def addCard(self, card):
"""Adds a card to current hand"""
self.cards.append(card)
return card
def discardCard(self, card):
"""Discards a card from current hand"""
self.cards.remove(card)
return card
def viewCards(self):
"""Returns a collection of cards"""
return self.cards
def fold(self):
"""Folds the current hand"""
temp = self.cards
self.cards = []
return temp
Player Class
import hand
class Player:
name = ""
position = 0
chips = 0
dealer = 0
pHand = []
def __init__ (self, nm, pos, buyIn, deal):
self.name = nm
self.position = pos
self.chips = buyIn
self.dealer = deal
self.pHand = hand.Hand()
return
def recieveCard(self, card):
"""Recieve card from the dealer"""
self.pHand.addCard(card)
return card
def discardCard(self, card):
"""Throw away a card"""
self.pHand.discardCard(card)
return card
def viewHand(self):
"""View the players hand"""
return self.pHand.viewCards()
def getChips(self):
"""Get the number of chips the player currently holds"""
return self.chips
def setChips(self, chip):
"""Sets the number of chips the player holds"""
self.chips = chip
return
def makeDealer(self):
"""Makes this player the dealer"""
self.dealer = 1
return
def notDealer(self):
"""Makes this player not the dealer"""
self.dealer = 0
return
def isDealer(self):
"""Returns flag wether this player is the dealer"""
return self.dealer
def getPosition(self):
"""Returns position of the player"""
return self.position
def getName(self):
"""Returns name of the player"""
return self.name
From my experience in C and Java it seems that I am somehow making my Hand class static.
Actually, that is basically what you're doing. Well, not really making the class static, but making the variable static.
When you write declarations like this:
class Hand:
cards = []
that variable (cards) is associated with the class, not with the instance. To make an analogy to Java, every statement in a Python class that isn't part of a method of that class basically runs in a static initializer. You could almost think of it like this:
class Hand {
static {
cards = new object[];
}
}
(merely a rough analogy, of course)
To create an instance variable in Python, you have to set it as an attribute of the instance, which requires you to wait until you have a reference to the instance. In practice, this means you initialize it in the constructor, like so:
class Hand:
def __init__(self):
self.cards = []
Your problem is quite simple
if you assign lists to the body of python classes, when you append items to it, they will be store at Class level, not at instance level.
you can solve this problem by adding the line:
def __init__(self):
self.cards = []
this is a very known case of python pitfall, and I recommend you the reading:
http://zephyrfalcon.org/labs/python_pitfalls.html
As other answers noted, you were confused about class variables vs. instance variables. I suggest you review the basics of how Python classes work. Here is an answer I wrote for another question; reading this might help you.
How to define a class in Python

Categories

Resources