S. Stromberg
S. Stromberg

Reputation: 79

How can I override a class definition in python?

I have a nested class structure where when I instantiate the top level class it instantiates a bunch of objects of other classes as attributes, and those instantiate a few other classes as their own attributes.

As I develop my code I want to override a class definition in a new module (this would be a good way for me to avoid breaking existing functionality in other scripts while adding new functionality).

As a toy example, there is a module where I define two classes where one has a list of the other, Deck and Card. When I instantiate a Deck object it instantiates a list of Card objects.

module_1.py

class Card(object):
    def __init__(self, suit, number):
        self.suit = suit
        self.number = number

class Deck(object):
    def __init__(self):
        suit_list = ['heart', 'diamond', 'club', 'spade']
        self.cards = []
        for suit in suit_list:
            for number in range(14):
                self.cards.append(Card(suit, number))

I have another type of Card and I want to make a Deck of those, so I import Deck into a new module and define a new Card class in the new module, but when I instantiate Deck, it has cards from module_1.py

module_2.py

from module_1 import Deck

class Card(object):
    def __init__(self, suit, number):
        self.suit = suit
        self.number = number
        self.index = [suit, number]

if __name__ == '__main__':
    new_deck = Deck()
    print new_deck.cards[0]

Output:

>python module_2.py
<module_1.Card object at 0x000001>

Is there some way that I can use my Deck class from module_1.py but have it use my new Card class?

Passing the class definition is cumbersome because the actual case is deeply nested and I may want to also develop other classes contained within the top level object.

Also, I expected this to be consistent with object oriented paradigms. Is it?

Upvotes: 1

Views: 1279

Answers (3)

Matt Anderson
Matt Anderson

Reputation: 19759

To make collections of interrelated classes customizable, I would not hard-code a class choice in the code of the class. Instead, I would make use of class-variables to allow for the selection of an alternate class.

# module 1

class Card(object):
    def __init__(self, suit, number):
        self.suit = suit
        self.number = number

class Deck(object):

    CardCls = None

    def __init__(self, card_type=Card):
        suit_list = ['heart', 'diamond', 'club', 'spade']
        self.cards = []
        CardCls = self.CardCls or Card
        for suit in suit_list:
            for number in range(14):
                self.cards.append(CardCls(suit, number))

and

# module 2
from module_1 import Deck

class NewCard(object):
    def __init__(self, suit, number):
        self.suit = suit
        self.number = number
        self.index = [suit, number]

class NewDeck(Deck):
    CardCls = NewCard

Alternatively, you could initially code Deck like this instead:

class Deck(object):

    CardCls = Card

    def __init__(self, card_type=Card):
        suit_list = ['heart', 'diamond', 'club', 'spade']
        self.cards = []
        for suit in suit_list:
            for number in range(14):
                self.cards.append(self.CardCls(suit, number))

But this requires Card to be defined before Deck in the originating module, or to be wired into the class later like:

Deck.CardCls = Card

So, I find it more flexible to use a pattern like:

CardCls = self.CardCls or Card

This way, the order of class definitions in a module doesn't matter. If the instance of the Deck subclass in question has a variable self.CardCls set (a "truthy" value -- which it will if you set it as a class-level variable in a subclass) it will use that. If it doesn't, it will use the Card class defined in the same module with the Deck base class.

Upvotes: 0

Patrick Maupin
Patrick Maupin

Reputation: 8127

Make the Card class an optional parameter to the Deck instance initializer that defaults to your initial Card class, and override it by passing a second parameter of your second Card class when you initialize a deck of your new cards.

EDIT Made names more Pythonic and made index a tuple as suggested by cyphase .

class Card(object):
    def __init__(self, suit, number):
        self.suit = suit
        self.number = number

class Deck(object):
    def __init__(self, card_type=Card):
        suit_list = ['heart', 'diamond', 'club', 'spade']
        self.cards = []
        for suit in suit_list:
            for number in range(14):
                self.cards.append(card_type(suit, number))

Your second module will pass its Card class into the Deck:

from module_1 import Deck

class FancyCard(object):
    def __init__(self, suit, number):
        self.suit = suit
        self.number = number
        self.index = suit, number

if __name__ == '__main__':
    new_deck = Deck(FancyCard)
    print new_deck.cards[0]

Upvotes: 5

cdonts
cdonts

Reputation: 9599

Patrick's answer is the recommended way. However, answering to your specific question, it is actually possible.

import module_1

class Card(object):
    def __init__(self, suit, number):
        self.suit = suit
        self.number = number
        self.index = [suit, number]

if __name__ == '__main__':
    # Keep reference to the original Card class.
    original_card = module_1.Card
    # Replace with my custom Card class.
    module_1.Card = Card  
    new_deck = module_1.Deck()
    print new_deck.cards[0]
    # Restore.
    module_1.Card = original_card

Upvotes: 2

Related Questions