Reputation: 79
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
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
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
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