Context:
Coming from C# I am building a python 3.6 texas hold'em to get acquainted with different python features.
I am currently buidling enums
for card-colors and -faces and classes for cards and DeckOfCards etc.
DeckOfCards -> 52 * Card -> (CardColor, CardFace)
I want the cards to print themself using different ("en","de", ..) languages. Each class has its own dict of languages and some setLang/getLang method to change it. Each class __str__()
uses the dict to output itself differently depending on the __lang__
set for each class.
setLang(..)
propagates down from DeckOfCards
->Card
->CardColor
->CardFace
- this is tedious.
I was considering a "global" Translater for all classes that can be used inside each class’s self.__str__
method.
How would one provide multi-lang support 'usually' in python?
remarks:
- I glanced through PEP 8 but I am not going 100% conform with it - using my C# style mostly
- I haven't found a suitable tag for multi-lang, globalisation or translation
CardColor
from enum import Enum,unique
@unique
class CardColor(Enum):
Pikes = 4
Hearts = 3
Tiles = 2
Clovers = 1
__lang__ = "de"
__nameDict__ = { "de" : {Pikes: "Pik", Hearst: "Herz", Tiles: "Karo", Clovers: "Kreuz"},
"en" : {Pikes: "Pikes", Hearst: "Hearts", Tiles:"Tiles", Clovers: "Clovers"} }
def getLang(self):
'''returns available languages'''
return self.__nameDict__.keys()[:]
def setLang(self, lang):
'''sets the language of this instance'''
if lang in self.__nameDict__:
self.__lang__ = lang
def __str__(self):
'''returns the DisplayName'''
return '{0}'.format(self.__nameDict__[self.__lang__][self.value])
CardFace
from enum import Enum,unique
@unique
class CardFace(Enum):
Two = 2
Three = 3
Four = 4
Five = 5
Six = 6
Seven = 7
Eight = 8
Nine = 9
Ten = 10
Jack = 11
Queen = 12
King = 13
Ace = 14
__lang__ = ""
__nameDict__ = { "de" : { Two : "Zwei", Three : "Drei", Four : "Vier", Five : "Fünf", Six : "Sechs", Seven : "Sieben", Eight : "Acht",
Nine : "Neun", Ten : "Zehn", Jack : "Bube", Queen : "Dame", King : "König", Ace : "As" },
"en" : { Two : "Two", Three : "Three", Four : "Four", Five : "Five", Six : "Six", Seven : "Seven", Eight : "Eight",
Nine : "Nine", Ten : "Ten", Jack : "Jack",Queen : "Queen",King : "King", Ace : "Ace"} }
def getLang(self):
'''returns available languages'''
return self.__nameDict__.keys()
def setLang(self, lang):
'''sets the language of this instance'''
if lang in self.__nameDict__:
self.__lang__ = lang
def __str__(self):
'''returns the DisplayName'''
return '{0}'.format(self.__nameDict__.get(self.__lang__, self.__nameDict__.get('en'))[self.value])
Card
from enum import Enum,unique
from cardcolor import CardColor
from cardface import CardFace
class Card:
__lang__ = "de"
__nameDict__ = { "de" : "{1} {0}", "en" : "{0} of {1}"}
def __init__(self, cardColor, cardValue):
self.c = cardColor
self.v = cardValue
def __str__(self):
'''returns the cards DisplayName'''
return self.__nameDict__.get(self.__lang__, self.__nameDict__.get('en')).format(self.v, self.c)
def setLang(self, lang):
if (lang in self.__nameDict__) and lang in self.v.getLang() and lang in self.c.getLang() :
self.__lang__ = lang
self.v.setLang(lang)
self.c.setLang(lang)
DeckOfCards
from card import Card
from cardcolor import CardColor
from cardface import CardFace
from random import shuffle
class DeckOfCards:
__lang__ = ""
__nameDict__ = { "de" : "Kartenspiel", "en" : "Deck of Cards"}
__deck__ = None
def __init__(self):
self.__deck__= self.__newDeck__()
self.__shuffle__()
def __shuffle__(self):
shuffle(self.__deck__)
def __newDeck__(self):
return [Card(c,v) for c in CardColor for v in CardFace]
def __str__(self):
'''returns the cards DisplayName'''
return "{0}:\n {1}{2}".format(self.__nameDict__.get(self.__lang__, self.__nameDict__.get('en')),
''.join([', '.join([str(c) for c in self.__deck__[x:x+6]])+',\n ' for x in range(0,len(self.__deck__), 6) ]).strip().strip(",\n"),
"\n")
def Cards(self):
return self.__deck__[:]
def setLang(self, lang):
if (lang in self.__nameDict__): # Todo: check if all support lang
self.__lang__ = lang
for c in self.__deck__:
c.setLang(lang)
@staticmethod
def printOneShuffledDeck(lang=None):
d = DeckOfCards()
if lang:
d.setLang(lang)
print(d)
To generate printouts for testing:
from deckofcards import DeckOfCards
DeckOfCards.printOneShuffledDeck("en")
DeckOfCards.printOneShuffledDeck("de")
Output:
Deck of Cards:
Jack of Hearts, Nine of Hearts, King of Diamonds, Eight of Hearts, Seven of Diamonds, Eight of Clovers,
Five of Hearts, Six of Clovers, Seven of Pikes, Nine of Clovers, Six of Hearts, Three of Diamonds,
Five of Clovers, Eight of Diamonds, Three of Pikes, Six of Diamonds, Three of Clovers, Ten of Pikes,
Three of Hearts, Jack of Pikes, Five of Diamonds, Eight of Pikes, Queen of Diamonds, Two of Pikes,
Queen of Hearts, Ace of Hearts, Two of Diamonds, Five of Pikes, Four of Diamonds, Four of Clovers,
Seven of Clovers, Two of Clovers, Nine of Pikes, Ten of Hearts, Queen of Clovers, Six of Pikes,
King of Hearts, King of Clovers, Ten of Diamonds, Four of Hearts, Ace of Clovers, Two of Hearts,
Jack of Clovers, Queen of Pikes, Ace of Diamonds, King of Pikes, Nine of Diamonds, Jack of Diamonds,
Ace of Pikes, Four of Pikes, Ten of Clovers, Seven of Hearts
Kartenspiel:
Karo König, Pik Sieben, Herz Vier, Pik As, Kreuz Bube, Herz Bube,
Herz Fünf, Herz Sieben, Pik Sechs, Pik Acht, Herz Dame, Karo Neun,
Kreuz Drei, Karo Zwei, Karo Zehn, Kreuz König, Karo Sieben, Kreuz Neun,
Kreuz As, Pik König, Karo Acht, Herz Sechs, Karo Bube, Pik Zehn,
Kreuz Sieben, Kreuz Zwei, Herz As, Karo Drei, Karo As, Pik Drei,
Herz König, Pik Dame, Kreuz Fünf, Pik Vier, Herz Zwei, Pik Fünf,
Kreuz Acht, Herz Neun, Pik Zwei, Kreuz Zehn, Kreuz Dame, Karo Fünf,
Herz Zehn, Kreuz Sechs, Kreuz Vier, Herz Drei, Karo Sechs, Karo Dame,
Herz Acht, Pik Neun, Pik Bube, Karo Vier
1 Answer 1
Dunder Methods
I'd drop the double-underscores in most of your methods. These conventions are usually reserved for magic methods, of which are things like __dict__
, __str__
, __iadd__
, etc. Otherwise, underscores are used to separate names in instances of inheritance, which is not the case here. In your case, it impairs readability and makes other developers have to guess "is this really a magic method?"
Naming Variables
Variable names should be separated by underscores, such as lang_dict
instead of langDict
.
Getting keys from dict
Iterating over a dict
returns the keys, no need to manually call dict.keys()
when you can do list(some_dict)
or ', '.join(some_dict)
. Also, dict.keys
is like a set
, so it is not able to be sliced and the call some_dict.keys()[:]
should raise a TypeError
Building Your Language Dictionaries
Enum
is built on OrderedDict
. The beautiful part? It's ordered based on the item insertion in the enum:
from enum import Enum
class CardColor(Enum):
Pikes = 4
Hearts = 3
Tiles = 2
Clovers = 1
', '.join(CardColor.__members__)
# Pikes, Hearts, Tiles, Clovers
With this in mind, you don't have to keep repeating your call to the respective field names, use the dict
constructor with zip
against your translation. Also, I'd call this enum CardSuit
, since CardColor
would imply Red
or Black
:
from enum import Enum
class CardSuit(Enum):
Pikes = 4
Hearts = 3
Tiles = 2
Clovers = 1
__lang_dict__ = {
'de': dict(zip(CardSuit.__members__, ('Pik', 'Herz', 'Karo', 'Kreuz'))),
'en': dict(zip(CardSuit.__members__, ('Pikes', 'Hearts', 'Tiles', 'Clovers')))
}
Much less typing.
Default lang
I'd set a default lang, as you have already, on the instance as a keyword arg, and when set_lang
is called, do a check against the keys in the dictionary and raise ValueError
otherwise:
class Card:
lang_dict = { "de" : "{1} {0}", "en" : "{0} of {1}"}
def __init__(self, card_color, card_value, lang='de'):
self.c = card_color
self.v = card_value
# We can set this here, but this will be overruled when setting
# the language at the deck level, this is done *after*
# c and v are set on self
self.lang = lang if lang == 'de' else self.set_lang(lang)
def __str__(self):
'''returns the cards DisplayName'''
# You shouldn't have to use a .get here, since on set_lang
# it already checks for a valid key
return self.lang_dict[self.lang].format(self.v, self.c)
def set_lang(self, lang):
lang = lang.lower().strip()
if lang not in self.lang_dict:
raise ValueError(
f"Tried to set invalid lang in Card, expected one of "
f"{', '.join(self.lang_dict)}, got {lang}"
)
self.lang = lang
self.c.set_lang(lang)
self.v.set_lang(lang)
And on the Enum
types, since you need the dunder var names to avoid them being options. Note the refactor on __str__
here:
class CardSuit(Enum):
Pikes = 4
Hearts = 3
Tiles = 2
Clovers = 1
__lang_dict__ = {
'de': dict(zip(CardSuit.__members__, ('Pik', 'Herz', 'Karo', 'Kreuz'))),
'en': dict(zip(CardSuit.__members__, ('Pikes', 'Hearts', 'Tiles', 'Clovers')))
}
__lang__ = 'de'
def get_langs(self):
# will get all the keys for you, though I'm not sure you need this?
return list(self.__lang_dict__)
def set_lang(self, lang):
if lang not in self.__lang_dict__:
raise ValueError(
f"Tried to set invalid lang in CardSuit, expected one of "
f"{', '.join(self.__lang_dict__)}, got {lang}"
)
self.__lang__ = lang
def __str__(self):
# re-factoring this for clarity
# I think f-strings are nice here, however,
# you might decide to keep the .format convention
# since you use it in Card, which works well
return f'{self.__lang_dict__[self.__lang__].value}'
Now, looking at the deck:
class DeckOfCards:
lang_dict = { "de" : "Kartenspiel", "en" : "Deck of Cards"}
# set a default here of 'de'
def __init__(self, lang='de'):
self.lang = lang.lower().strip()
if self.lang not in self.lang_dict:
raise ValueError(f'Got invalid lang, expected one of {", ".join(self.lang_dict)}, got {self.lang}')
# note we are dropping all underscores here
self.deck = self.new_deck()
self.shuffle()
def set_lang(self, lang):
"""
Set a language at the deck level that propogates through the child
classes.
"""
# grab a default to roll back with
# in case a non-supported language is chosen
_lang = self.lang
for i, card in enumerate(self.deck):
try:
card.set_lang(lang)
except ValueError as e:
print(e)
break
else:
self.lang = lang
return
# set back to default by traversing backward through
# indices, then you can set mismatching dictionaries
# and it will still only handle where the languages
# line up
while i >= 0:
self.deck[i].set_lang(_lang)
i -= 1
Refactoring __str__
Now, the one area that I think will clean up __str__
on the DeckOfCards
class is using zip
to pair up slice-points:
def __str__(self):
# I've pulled these out of the comprehension for readability
slice_start = range(0, len(self.deck), 6) # 0, 6, 12, ..., 48
slice_end = range(6, len(self.deck) + 6, 6) # 6, 12, ..., 54
# You could do this in fewer lines, but I think the explicitness
# is better for the example. Again, I like the f-string here,
# but you can decide if you want to retain str.format
dict_str = f'{self.lang_dict[self.lang]}:\n\t'
a = '\n\t'.join(
', '.join(map(str, self.deck[i:j])) for i, j
in zip(slice_start, slice_end))
return dict_str + a
Again, the point here being that you shouldn't be leaning on .get
inside the method because it is expected that the language was checked ahead of time.
DeckOfCards.deck
The NewDeck
should be re-named and whitespace should be inserted between variables/operators. Also, you can have the self.lang
applied to each Card
so you can select the language on creation of the Deck
, rather than only using a default and having to switch it every time:
# functions should be snake-case
def new_deck(self):
# include whitespaces between variables in Card declaration
return [Card(c, v, self.lang) for c in CardColor for v in CardFace]
Deck.Cards
?
You don't need this method, you already have a deck of cards. Just do a lookup on self.deck
, it's also already a list, so no need to slice
PrintOneShuffledDeck
This should be a standalone method, IMO, not a staticmethod
on a class. You can refactor it by just printing the created instance:
# this default matches the default for the other classes
print_shuffled_deck(lang='de'):
print(DeckOfCards(lang))
And because of the refactor in how set_lang
is implemented, you don't need to call set_lang
externally here, you can specify the language you want at the time of deck creation.
Final Caveat
Only issue here is that because Enum
doesn't give you an instance, your binding of __lang__
will hold individually for options, but not between decks. To show you what I mean:
from enum import Enum
class X(Enum):
A = 1,
B = 2
__var__ = 1
a = X.A
b = X.B
a.__var__ += 1
print(b.__var__)
1
print(a.__var__)
2
print(X.A.__var__)
2
Not sure how much that matters to you in your use-case, but figured I'd highlight it anyways. You can't have more than one deck if you want each deck to support a different language.
-
\$\begingroup\$ Thanks for taking the time and multiple good suggestions - gotta mull over them a bit \$\endgroup\$Patrick Artner– Patrick Artner2019年10月09日 07:10:04 +00:00Commented Oct 9, 2019 at 7:10
Explore related questions
See similar questions with these tags.
glanced through PEP 8
check whether your IDE supports PEP 8 hinting (much the same way the spelling checker you don't seem to be using would). Have a look at docstring/PEP 257, too. \$\endgroup\$spelling checker
: have your computer warn you aboutaquainted
,buidling
,themselfs
,Translater
,eachs
,throuhg
,stlye
,havent
,mulit
, evencolor
guessing you learned English closer to Merry Old than to God Bless. \$\endgroup\$