Question about deck of cards using OOP python - python-3.x

This is the current code:
import random
class Cards():
def __init__(self, value, suit):
self.value = value
self.suit = suit
def show(self):
print("{} of {}".format(self.value, self.suit.upper()))
class Deck():
def __init__(self):
self.cards = []
self.build()
def build(self):
suits = ['Spades', 'Hearts', 'Clubs', 'Diamonds']
for suit in suits:
for value in range(1,14):
self.cards.append(Cards(value,suit))
def show(self):
for card in self.cards:
card.show()
def shuffle(self):
for i in range(len(self.cards)-1, 0, -1):
rand = random.randint(0, i)
self.cards[i], self.cards[rand] = self.cards[rand], self.cards[i]
def draw(self):
return self.cards.pop()
deck = Deck()
deck.shuffle()
deck.draw().show()
The OOP part still confuses me, so excuse my elementary question but how come I can do deck.draw().show() but not deck.shuffle().draw().show()?
What I mean is that when I keep deck.shuffle() AND THEN do deck.draw().show() it returns to me random cards. So what if I just wanted to do deck.shuffle().draw().show()? Why does it give me an error of
Traceback (most recent call last):
File "c:\Users\Desktop\deckofcards.py", line 40, in <module>
deck.shuffle().draw().show()
AttributeError: 'NoneType' object has no attribute 'draw'
PS C:\Users\learning_log>

You can get that behaviour with one small change:
def shuffle(self):
for i in range(len(self.cards)-1, 0, -1):
rand = random.randint(0, i)
self.cards[i], self.cards[rand] = self.cards[rand], self.cards[i]
return self # <- return a reference to the deck itself
Demonstration:
In [95]: deck.shuffle().draw().show()
8 of HEARTS
The reason you were getting that error is that you weren't returning anything from shuffle().

You can't do deck.shuffle().draw().show() because as the error says, deck.shuffle() is a nonetype, that means it has no value, because shuffle() does some stuff, it doesn't return anything so you are esentially doing 'nothing'.draw().show(). To solve it you could return something, just like other answer said you could return a reference to the deck.

Related

Function not Defined in Class

I am having trouble with a coding project in which I am trying to use classes in python to make a card game (cheat). However, when creating the player class, one of the functions that was previously defined within the class is shown as undefined. I cannot figure out the reason, and any suggestion is appreciated. Below is the definition of the classes
class card(object):
def __init__(self,rank,suit):
self.rank = rank
self.suit = suit
class player(object):
def __init__ (self):
self.number = number
self.hand = list()
#Here, hand is a list of the card class that was distributed with a suit and a rank
def check_card(self,player_rank,player_suit):
for card in self.hand:
if card.rank == player_rank and card.suit == player_suit:
return True
break
return False
def play_card(self):
suit = input('what is the suit?')
rank = input('what is the rank?')
if check_card(self,rank,suit):
print(True)
else:
print(False)
Here is the actual code that will run it
player = player()
player.play_card()
The following error was received:
NameError: name 'check_card' is not defined
I have been troubleshooting and looking at different solutions, including moving the functions outside the class, but it continues to display the same error. Can anyone point out the mistake? Thanks!
You have the following two issues in your code
The way you passed self to the check_card function is wrong. You must call it in this way
self.check_card(rank,suit)
The second issue is that the number is not defined. Thus I passed it as an argument while initializing the player. Feel free to make changes for that.
This is the corrected code :
class card(object):
def __init__(self,rank,suit):
self.rank = rank
self.suit = suit
class player(object):
def __init__ (self, number):
self.number = number
self.hand = list()
#Here, hand is a list of the card class that was distributed with a suit and a rank
def check_card(self,player_rank,player_suit):
for card in self.hand:
if card.rank == player_rank and card.suit == player_suit:
return True
break
return False
def play_card(self):
suit = input('what is the suit?')
rank = input('what is the rank?')
if self.check_card(rank,suit):
print(True)
else:
print(False)
player = player(3)
player.play_card()
Output :
what is the suit?spade
what is the rank?3
False
Based on this document the function call in python class is self.xxxx(args) (xxxx is denoted function name)
therefore the correct version of play_card function is shown as following.
enter code here
def play_card(self):
suit = input('what is the suit?')
rank = input('what is the rank?')
if self.check_card(rank,suit):
print(True)
else:
print(False)

Python Class, Operator Overloading

Recently while getting my hands on with Python Class concepts, I came upon this observation and was not able to understand.
When I try and create instance out of the below class interactively(Python console), I also get the Finding __len__ line in output.
class MyClass(object):
counter = 0
data = 'Class Variable'
def __init__(self):
self.counter += 1
self.value = -1
def __str__(self):
return "Instance {} is the {} instance".format(self.__class__.__name__, self.counter)
def __getattr__(self, item):
print(f'Finding {item}')
return self.__dict__.get(item, f'Attr {item} not available, {self.__dict__}')
def __setattr__(self, key, value):
if key not in self.__dict__:
self.__dict__[key] = value
def __delattr__(self, item):
print(f'Deleting attr: {item}')
if item in self.__dict__:
del self.__dict__[item]
else:
print(f'Cannot find {item} in {self.__dict__}')
if __name__ == '__main__':
inst = MyClass()
print(inst.id)
But running it as a top level module, doesn't add this additional line in output.
Can someone help me understand, why Finding __len__ output would be displayed interactively.
Below is an interactive output,
import WS1
x = WS1.MyClass()
Finding __len__
x.name = 'Yathin'
Finding __len__

In OOP in python, are different instances of an object when initialised with a default value the same?

I am trying to understand object oriented programming. I am doing this by creating a small poker like program. I have come across a problem whose minimal working example is this:
For this code:
import random
class superthing(object):
def __init__(self,name,listthing=[]):
self.name = name
self.listthing = listthing
def randomlyadd(self):
self.listthing.append(random.randint(1,50))
def __str__(self):
return '\nName: '+str(self.name)+'\nList: '+str(self.listthing)
Aboy = superthing('Aboy')
Aboy.randomlyadd()
print(Aboy)
Anotherboy = superthing('Anotherboy')
Anotherboy.randomlyadd()
print(Anotherboy)
I expect this output :
Name: Aboy
List: [44]
(some number between 1 and 50)
Name: Anotherboy
List: [11]
(again a random number between 1 and 50)
But what I get is:
Name: Aboy
List: [44]
(Meets my expectation)
Name: Anotherboy
List: [44,11]
(it appends this number to the list in the previous instance)
Why is this happening? The context is that two players are dealt a card from a deck. I am sorry if a similar question exists, if it does, I will read up on it if you can just point it out. New to stack overflow. Thanks in advance.
For the non minimal example, I am trying this:
import random
class Card(object):
def __init__(self, suit, value):
self.suit = suit
self.value = value
def getsuit(self):
return self.suit
def getval(self):
return self.value
def __str__(self):
if(self.suit == 'Clubs'):
suitstr = u'\u2663'
elif(self.suit == 'Diamonds'):
suitstr = u'\u2666'
elif(self.suit == 'Hearts'):
suitstr = u'\u2665'
elif(self.suit == 'Spades'):
suitstr = u'\u2660'
if((self.value<11)&(self.value>1)):
valuestr = str(self.value)
elif(self.value == 11):
valuestr = 'J'
elif(self.value == 12):
valuestr = 'Q'
elif(self.value == 13):
valuestr = 'K'
elif((self.value == 1)|(self.value == 14)):
valuestr = 'A'
return(valuestr+suitstr)
class Deck(object):
def __init__(self,DeckCards=[]):
self.DeckCards = DeckCards
def builddeck(self):
suits = ['Hearts','Diamonds','Clubs','Spades']
for suit in suits:
for i in range(13):
self.DeckCards.append(Card(suit,i+1))
def shuffle(self):
for i in range(len(self)):
r = random.randint(0,len(self)-1)
self.DeckCards[i],self.DeckCards[r] = self.DeckCards[r],self.DeckCards[i]
def draw(self):
return self.DeckCards.pop()
def __str__(self):
return str([card.__str__() for card in self.DeckCards])
def __len__(self):
return len(self.DeckCards)
class Player(object):
def __init__(self,Name,PlayerHandcards = [],Balance = 1000):
self.Name = Name
self.Hand = PlayerHandcards
self.Balance = Balance
def deal(self,deck):
self.Hand.append(deck.draw())
def __str__(self):
return 'Name :'+str(self.Name)+'\n'+'Hand: '+str([card.__str__() for card in self.Hand])+'\n'+'Balance: '+str(self.Balance)
deck1 = Deck()
deck1.builddeck()
deck1.shuffle()
Alice = Player('Alice')
Alice.deal(deck1)
print(Alice)
Bob = Player('Bob')
Bob.deal(deck1)
print(Bob)
And after dealing to Bob they both have the same hands. If you have some other suggestions regarding the code, you are welcome to share that as well.
This is a duplicate of “Least Astonishment” and the Mutable Default Argument as indicated by #Mad Physicist. Closing this question for the same.

python3 subclass is being instantiated as superclass, subclass method not found throwing AttributeError

I am attempting to define and instantiate a subclass in python3, but when calling a method on the subclass it throws an AttributeError, much as if I instantiated the object as an instance of the superclass; the method cannot be found. As far as I can tell, I have written the subclass constructor appropriately, calling the super() method without arguments as shown. Any help would be greatly appreciated.
Code used to instantiate subclass and call method:
class Player:
hands = []
...
def __init__(self):
...
def __deal(self, twocards):
self.hands = [Hand(showfirst=True)]
self.hands[0] += twocards
self.hands[0].showcards()
...
Definition of Hand class:
class Hand(pydealer.Stack):
def __init__(self, showfirst, **kwargs):
super().__init__(**kwargs)
stay = False
self.showfirstcard = showfirst
...
def showcards(self):
for i, card in enumerate(self):
if i == 0:
if self.showfirstcard == False:
print('***FACEDOWN CARD***')
else:
print(card)
Stacktrace and error message:
Traceback (most recent call last):
File "/root/python/django_webapp/mysite/webapp/blockjack/src/test.py", line 8, in <module>
table.start()
File "/root/python/django_webapp/mysite/webapp/blockjack/src/table.py", line 23, in start
self.dealer.deal(self.shoe.deal(2))
File "/root/python/django_webapp/mysite/webapp/blockjack/src/dealer.py", line 15, in deal
self.seecards(0)
File "/root/python/django_webapp/mysite/webapp/blockjack/src/player.py", line 39, in seecards
self.hands[whichhand].showcards()
AttributeError: 'Stack' object has no attribute 'showcards'
TL;DR: Why does it think I am trying to call a method on a parent class object (Stack) when I explicitly defined the object in question using Hand()?
EDIT: Added all relevant code undigested below:
test.py
import table
from pydealer import Deck
table = table.Table()
table.start()
table.py
from pydealer import Deck
from player import Player
from dealer import Dealer
class Table:
shoe = None
dealer = None
player = None
def __init__(self):
self.shoe = Deck()
self.shoe.rebuild = True
self.shoe.shuffle()
self.player = Player()
self.dealer = Dealer()
def start(self):
self.player.bet()
self.dealer.deal(self.shoe.deal(2))
self.player.deal(self.shoe.deal(2))
dealer.py
from player import Player
from hand import Hand
class Dealer(Player):
def __init__(self):
super().__init__()
def deal(self, twocards):
self.hands = [Hand(showfirst=False)]
self.hands[0] += twocards
self.seecards(0)
player.py
from hand.py import Hand
class Player:
hands = []
__chips = None
__betamount = None
def __init__(self):
self.__chips = 5000
def bet(self):
type(self.__betamount)
def __deal(self, twocards):
self.hands = [Hand(showfirst=True)]
self.hands[0] += twocards
self.hands[0].showcards()
def hit(self, card, whichhand):
self.hands[whichhand] += card
def stay(self, whichhand):
self.hands[whichhand].setstay()
def printscore(self, whichhand):
print(self.hands[whichhand].stackscore())
hand.py
import pydealer
class Hand(pydealer.Stack):
stay = None
showfirstcard = None
bust = None
def __init__(self, showfirst, **kwargs):
super().__init__(**kwargs)
stay = False
self.showfirstcard = showfirst
def stackscore(self):
total = 0
for card in self:
total += self.__bjval(card)
if total > 21:
for card in self:
if card.value == "Ace":
total -= 10
if total <= 21:
break
return total
def __bjval(self, card):
return {
'2': 2,
'3': 3,
'4': 4,
'5': 5,
'6': 6,
'7': 7,
'8': 8,
'9': 9,
'10': 10,
'Jack': 10,
'Queen': 10,
'King': 10,
'Ace':11
}[card.value]
#def receive(self, cards):
#self += cards
def setstay(self):
self.stay = True
def getsize(self):
return self.size()
def showcards(self):
for i, card in enumerate(self):
if i == 0:
if self.showfirstcard == False:
print('***FACEDOWN CARD***')
else:
print(card)
Figured it out. The object was not being instantiated as a Stack but was actually being converted to a stack upon the result of the += operator. The code for this operator instantiates and returns new stack object and the reference to the Hand object is lost altogether. Within stack.py:
def __add__(self, other):
try:
new_stack = Stack(cards=(list(self.cards) + list(other.cards)))
except:
new_stack = Stack(cards=(list(self.cards) + other))
return new_stack
I guess the only other way to go about this is to make the Hand class contain a Stack, since overriding the basic functionality of the superclass in this way seems to defeat the purpose of inheritance altogether?

In Python 3, calling a Class function by name before init with inheritance

The goal:
B in inherits from A.
A and B have a factory method create, which harmonizes different input types before initializing the actual class.
create calls different create methods create_general_1, create_general_2, create_specific_b_1 via their name, supplied as a string.
This is my current approach:
import sys
class A:
def __init__(self, text):
self.text = text
print("I got initialized: {}".format(text))
def create(create_method_str):
# This is where it breaks:
create_method = getattr(sys.modules[__name__], create_method_str)
return create_method()
def create_general_style_3():
return A("a, #3")
class B(A):
def create_b_style_1():
return B("b, #1")
if __name__ == "__main__":
B.create("create_b_style_1")
It fails with the following error:
Traceback (most recent call last): File "test.py", line 22, in
B.create("create_b_style_1") File "test.py", line 10, in
create create_method = getattr(sys.modules[__name__],
create_method_str) AttributeError: 'module' object has no attribute
'create_b_style_1'
So in a way, I'm trying to combine three things: factory methods, inheritance, and function calling by name.
It would be great if someone had a smarter approach, or knew how to get this approach to work.
Thanks a great lot!
Thanks to Two-Bit Alchemist's comment, I've created this solution, which seems to work fine. Any improvements / other proposals are more than welcome :)
All is well explained here.
import sys
class A:
def __init__(self, text):
self.text = text
print("I got initialized: {}".format(text))
#classmethod
def create(cls, create_method_str):
create_method = getattr(cls, create_method_str)
return create_method()
#classmethod
def create_general_style_3(cls):
return cls("a, #3")
class B(A):
#classmethod
def create_b_style_1(cls):
return cls("b, #1")
if __name__ == "__main__":
B.create("create_b_style_1")

Resources