Python class: TypeError: 'NoneType' object is not callable

Viewed 1973

I'm trying to create a Python version of Monopoly. I have a separate class that I am using to shuffle and track the Chance and Community Chest cards. The cards are stored in lists chest_cards and chance_cards.

def __init__(self):
    self.chance = random.shuffle(chance_cards)
    self.chest = random.shuffle(chest_cards)
    self.chance_count = 0
    self.chest_count = 0
    
def chance(self):
    self.chance_count += 1
    return self.chance[self.chance_count - 1]

In my main code, I am just running

p = cards()
print (p.chance())

to test my code, but I get TypeError: 'NoneType' object is not callable for the print line.

Any ideas? Or do you need to see more code? TIA

EDIT: Here is the full cards class, if it helps

import random
global chance_count
global chest_count

class cards:
    global chest_cards
    global chance_cards
    chest_cards = (["Go to Jail","Get Out of Jail Free","Advance to Go (Collect $200)",
"Bank error in your favor (Collect $200)","Doctor's fee (Pay $50)", 
"From sale of stock you get $50", "Grand Opera Night — Collect $50 from every player", 
"Holiday Fund matures (Collect $100)", "Income tax refund (Collect $20)",
"It is your birthday (Collect $10)","Life insurance matures (Collect $100)",
"Pay hospital fees of $100", "Pay school fees of $150", "Receive $25 consultancy fee",
"You are assessed for street repairs – $40 per house – $115 per hotel",
"You have won second prize in a beauty contest (Collect $10)", "You inherit $100"])

    chance_cards = (["Go to Jail","Get Out of Jail Free","Advance to Go (Collect $200)",
"Advance to Illinois Ave — If you pass Go, collect $200",
"Advance to St. Charles Place – If you pass Go, collect $200",
"Advance token to nearest Utility. If unowned, you may buy it from the Bank. If owned, throw dice and pay owner a total ten times the amount thrown.",
"Advance token to the nearest Railroad and pay owner twice the rental to which he/she is otherwise entitled. If Railroad is unowned, you may buy it from the Bank.",
"Bank pays you dividend of $50", "Go Back 3 Spaces",
"Make general repairs on all your property – For each house pay $25  –For each hotel $100",
"Pay poor tax of $15","Take a trip to Reading Railroad – If you pass Go, collect $200",
"Take a walk on the Boardwalk – Advance token to Boardwalk", 
"You have been elected Chairman of the Board – Pay each player $50",
"Your building and loan matures — Collect $150", "You have won a crossword competition (Collect $100)"])

    def __init__(self):
        self.chance = random.shuffle(chance_cards)
        self.chest = random.shuffle(chest_cards)
        self.chance_count = 0
        self.chest_count = 0
        
    def chance(self):
        self.chance_count += 1
        return self.chance[self.chance_count - 1]
2 Answers

when you create an instance of your class (assuming it's class cards: before your __init__ function), you create an object with a method called chance, but then during __init__ you overwrite this method with an attribute called chance with the return value of random.shuffle which is always None because shuffle "shuffles" the list in-place rather than creating a new list with a random order:

>>> chance_cards = ['card1', 'card2', 'card3']
>>> chance = random.shuffle(chance_cards)
>>> print(chance)
None

EDIT: A note on globals

options to get rid of global (you should really do some outside learning on variable scope on your own...):

  1. Move your variables outside the class into the "module scope". You can still refer to them in your class.
import random, copy #copy isn't strictly needed but used for clarity
# `new_list=some_list[:]` is functionally equivalent to `new_list=copy.copy(some_list)`
chest_cards = (["Go to..."])
chance_cards = (["Go to Ja..."])

class cards:
    def __init__(self):
        self.chest_cards = copy.copy(chest_cards) #make a local copy so you don't alter the master list
        random.shuffle(self.chest_cards)
  1. If you only need them within the class, leave them as a class attribute and refer to them with either self or by the name of the class.
import random, copy
class cards:
    chest_cards = (["Go to..."])
    chance_cards = (["Go to Ja..."])
    def __init__(self):
        #before you over-write self.chest_cards, it refers to the class attribute
        self.chest_cards = copy.copy(self.chest_cards) #make a local copy so you don't alter the master list
        #after you over-write it, it will refer to the instance attribute as long as you made a copy.
        #you can also refer to the class directly to access class-attributes
        self.chest_cards = copy.copy(cards.chest_cards)
        #if you want to get the class without calling it by name (in case you want to change the name of the class)
        self.chest_cards = copy.copy(type(self).chest_cards)
        random.shuffle(self.chest_cards)
  1. There are definitely more ways... see if you can find any :)

Firstly :

You have a data attribute called chance, and a method called chance; so when you set self.chance in your init method, it overwrites the reference to the method of the same name - come up with a diffferent name for your chance attribute`

Secondly:

random.shuffle() is an in-place function - it changes the list that you pass it, and retuens NONE - which is why your chance attribute is set to None. If you want your chance attribute to be a version of your chance global which is shuffled - then do this :

def __init__(self):
     self.chance = chance[:]
     random.shuffle(self.chance)

or

def __init__(self):
     self.chance = list(random.choices(chance, k=len(chance))

Thirdly:

Globals - why (they are very bad thing to get into the habit of using)- and if you are going to use them (why) don't set from a class body - that is entirely unneccessary, and confusing.

Related