Definition of a simple class hierarchy for immutable objects without a formal equality test or hash function. This will somewhat work as expected. However, there will also be anomalies.
class Card: insure= False def __init__( self, rank, suit, hard, soft ): self.rank= rank self.suit= suit self.hard= hard self.soft= soft def __repr__( self ): return "{__class__.__name__}(suit={suit!r}, rank={rank!r})".format(__class__=self.__class__, **self.__dict__) def __str__( self ): return "{rank}{suit}".format(**self.__dict__) class NumberCard( Card ): def __init__( self, rank, suit ): super().__init__( str(rank), suit, rank, rank ) class AceCard( Card ): def __init__( self, rank, suit ): super().__init__( "A", suit, 1, 11 ) class FaceCard( Card ): def __init__( self, rank, suit ): super().__init__( {11: 'J', 12: 'Q', 13: 'K' }[rank], suit, 10, 10 ) Suits = '♣', '♦', '♥', '♠'
Some Use cases for two seemingly equal cards.
c1 = AceCard( 1, '♣' ) c2 = AceCard( 1, '♣' ) print( id(c1), id(c2) ) print( c1 is c2 ) print( hash(c1), hash(c2) ) print( c1 == c2 ) print( set( [c1, c2] ) )
Definition of a more sophisticated class hierarchy with a formal equality and hash test. This will create properly immutable objects. There will be no anomalies with seemingly equal objects.
class Card2: insure= False def __init__( self, rank, suit, hard, soft ): self.rank= rank self.suit= suit self.hard= hard self.soft= soft def __repr__( self ): return "{__class__.__name__}(suit={suit!r}, rank={rank!r})".format(__class__=self.__class__, **self.__dict__) def __str__( self ): return "{rank}{suit}".format(**self.__dict__) def __eq__( self, other ): return self.suit == other.suit and self.rank == other.rank # and self.hard == other.hard and self.soft == other.soft def __hash__( self ): return hash(self.suit) ^ hash(self.rank) def __format__( self, format_spec=None ): if format_spec is None or len(format_spec) == 0: return str(self) return format_spec.replace("%r",self.rank).replace("%s",self.suit).replace("%%","%") def __bytes__( self ): class_code= self.__class__.__name__[0] rank_number_str = {'A': '1', 'J': '11', 'Q': '12', 'K': '13'}.get( self.rank, self.rank ) string= "("+" ".join([class_code, rank_number_str, self.suit,] ) + ")" return bytes(string,encoding="utf8") class NumberCard2( Card2 ): def __init__( self, rank, suit ): super().__init__( str(rank), suit, rank, rank ) class AceCard2( Card2 ): insure= True def __init__( self, rank, suit ): super().__init__( "A", suit, 1, 11 ) class FaceCard2( Card2 ): def __init__( self, rank, suit ): super().__init__( {11: 'J', 12: 'Q', 13: 'K' }[rank], suit, 10, 10 )
Some Use cases for two seemingly equal cards.
c1 = AceCard2( 1, '♣' ) c2 = AceCard2( 1, '♣' ) print( id(c1), id(c2) ) print( c1 is c2 ) print( hash(c1), hash(c2) ) print( c1 == c2 ) print( set( [c1, c2] ) )
Definition of a weird class hierarchy with a formal equality but no hash test. This will create properly mutable objects that can't be put into sets.
class Card3: insure= False def __init__( self, rank, suit, hard, soft ): self.rank= rank self.suit= suit self.hard= hard self.soft= soft def __repr__( self ): return "{__class__.__name__}(suit={suit!r}, rank={rank!r})".format(__class__=self.__class__, **self.__dict__) def __str__( self ): return "{rank}{suit}".format(**self.__dict__) def __eq__( self, other ): return self.suit == other.suit and self.rank == other.rank # and self.hard == other.hard and self.soft == other.soft __hash__ = None class AceCard3( Card3 ): insure= True def __init__( self, rank, suit ): super().__init__( "A", suit, 1, 11 ) class NumberCard3( Card3 ): def __init__( self, rank, suit ): super().__init__( str(rank), suit, rank, rank ) class FaceCard3( Card3 ): def __init__( self, rank, suit ): super().__init__( {11: 'J', 12: 'Q', 13: 'K' }[rank], suit, 10, 10 )
Some Use cases for two seemingly equal cards.
c1 = AceCard3( 1, '♣' ) c2 = AceCard3( 1, '♣' ) print( id(c1), id(c2) ) print( c1 is c2 ) try: print( hash(c1), hash(c2) ) except TypeError as e: print( e ) print( c1 == c2 ) try: print( set( [c1, c2] ) ) except TypeError as e: print( e )
Better Mutable Object Example
Definition of a simple class hierarchy with a formal equality and hash test.
class Hand: def __init__( self, dealer_card, *cards ): self.dealer_card= dealer_card self.cards= list(cards) def __str__( self ): return ", ".join( map(str, self.cards) ) def __repr__( self ): return "{__class__.__name__}({dealer_card!r}, {_cards_str})".format( __class__=self.__class__, _cards_str=", ".join( map(repr, self.cards) ), **self.__dict__ ) def __format__( self, format_specification=None ): if format_specification is None or len(format_specification) == 0: return str(self) return ", ".join( "{0:{fs}}".format(c, fs=format_specification) for c in self.cards ) def __eq__( self, other ): if isinstance(other,Hand): return self.cards == other.cards and self.dealer_card == other.dealer_card if isinstance(other,int): return self.total() == other return NotImplemented def __lt__( self, other ): if isinstance(other,Hand): return self.total() < other.total() if isinstance(other,int): return self.total() < other return NotImplemented def __le__( self, other ): if isinstance(other,Hand): return self.total() <= other.total() if isinstance(other,int): return self.total() <= other return NotImplemented __hash__ = None def total( self ): delta_soft = max( c.soft-c.hard for c in self.cards ) hard = sum( c.hard for c in self.cards ) if hard+delta_soft <= 21: return hard+delta_soft return hard class FrozenHand( Hand ): def __init__( self, *args, **kw ): if len(args) == 1 and isinstance(args[0], Hand): # Clone a hand other= args[0] self.dealer_card= other.dealer_card self.cards= other.cards else: # Build a fresh hand super().__init__( *args, **kw ) def __hash__( self ): """xor-reduce operation.""" h= 0 for c in self.cards: h ^= hash(c) return h def card( rank, suit ): if rank == 1: return AceCard2( rank, suit ) elif 2 <= rank < 11: return NumberCard2( rank, suit ) elif 11 <= rank < 14: return FaceCard2( rank, suit ) else: raise Exception( "Rank out of range" ) import random class Deck( list ): def __init__( self ): super().__init__( card(r+1,s) for r in range(13) for s in Suits ) random.shuffle( self ) from collections import defaultdict stats = defaultdict(int) d= Deck() h = Hand( d.pop(), d.pop(), d.pop() ) h_f = FrozenHand( h ) stats[h_f] += 1 print( stats )
Really, this belongs with __str__() and __repr__()
Examples of how __format__ gets invoked
c = card( 2, '♠' ) print( "function", format( c ) ) print( "Card plain {0}".format(c) ) print( "Card !r {0!r}".format(c) ) print( "Card !s {0!s}".format(c) )
Our own unique formatting language uses "r" and "s" for rank and suit.
print( "Card :%s {0:%s}".format(c) ) print( "Card :%r {0:%r}".format(c) ) print( "Card :%r of %s {0:%r of %s}".format(c) ) print( "Card :%s%r {0:%s%r}".format(c) )
Extra literals we leave alone.
print( "Card nested {0:{fill}{align}16s}".format(c, fill="*", align="<") )
RE to parse the specification.
import re spec_pat = re.compile( r"(?P<fill_align>.?[\<\>=\^])?" "(?P<sign>[-+ ])?" "(?P<alt>#)?" "(?P<padding>0)?" "(?P<width>\d*)" "(?P<comma>,)?" "(?P<precision>\.\d*)?" "(?P<type>[bcdeEfFgGnosxX%])?" ) for spec in ("<30", ">30", "^30", "*^30", "+f", "-f", " f", "d", "x", "o", "b", "#x", "#o", "#b", ",", ".2%", "06.4f"): print( spec, spec_pat.match(spec).groupdict() )
Nested {}'s
stats = defaultdict(int) d= Deck() h1 = FrozenHand( d.pop(), d.pop(), d.pop() ) stats[h1] += 1 h2 = FrozenHand( d.pop(), d.pop(), d.pop() ) stats[h2] += 1 width=6 for hand,count in stats.items(): print( "{hand:%r%s} {count:{width}d}".format(hand=hand,count=count,width=width) )
# Export card as a bytes #:: d= Deck() c= d.pop() b= bytes(c) print( b ) def card_from_bytes( buffer ): string = buffer.decode("utf8") assert string[0 ]=="(" and string[-1] == ")" code, rank_number, suit = string[1:-1].split() class_ = { 'A': AceCard, 'N': NumberCard, 'F': FaceCard }[code] return class_( int(rank_number), suit ) print( card_from_bytes(b) )
The object resolution for comparison special methods. A partial class to see what happens.
class BlackJackCard_p: def __init__( self, rank, suit ): self.rank= rank self.suit= suit def __lt__( self, other ): print( "Compare {0} < {1}".format( self, other ) ) return self.rank < other.rank def __str__( self ): return "{rank}{suit}".format( **self.__dict__ ) two = BlackJackCard_p( 2, '♠' ) three = BlackJackCard_p( 3, '♠' ) print( "{0} < {1} :: {2!r}".format( two, three, two < three ) ) print( "{0} > {1} :: {2!r}".format( two, three, two > three ) ) print( "{0} == {1} :: {2!r}".format( two, three, two == three ) ) try: print( "{0} <= {1} :: {2!r}".format( two, three, two <= three ) ) except TypeError: pass two_c = BlackJackCard_p( 2, '♣' ) print( "{0} == {1} :: {2!r}".format( two, two_c, two == two_c ) )
A more complete class to show same-class comparisons.
class BlackJackCard: def __init__( self, rank, suit, hard, soft ): self.rank= rank self.suit= suit self.hard= hard self.soft= soft def __lt__( self, other ): if not isinstance( other, BlackJackCard ): return NotImplemented return self.rank < other.rank def __le__( self, other ): if not isinstance( other, BlackJackCard ): return NotImplemented return self.rank <= other.rank def __gt__( self, other ): if not isinstance( other, BlackJackCard ): return NotImplemented return self.rank > other.rank def __ge__( self, other ): if not isinstance( other, BlackJackCard ): return NotImplemented return self.rank >= other.rank def __eq__( self, other ): if not isinstance( other, BlackJackCard ): return NotImplemented return self.rank == other.rank and self.suit == other.suit def __ne__( self, other ): if not isinstance( other, BlackJackCard ): return NotImplemented return self.rank != other.rank and self.suit == other.suit def __str__( self ): return "{rank}{suit}".format( **self.__dict__ ) class Ace21Card( BlackJackCard ): def __init__( self, rank, suit ): super().__init__( 'A', suit, 1, 11 ) class Face21Card( BlackJackCard ): def __init__( self, rank, suit ): super().__init__( {11:'J', 12:'Q', 13:'K'}[rank], suit, 10, 10 ) class Number21Card( BlackJackCard ): def __init__( self, rank, suit ): super().__init__( str(rank), suit, rank, rank ) def card21( rank, suit ): if rank == 1: return Ace21Card( rank, suit ) elif 2 <= rank < 11: return Number21Card( rank, suit ) elif 11 <= rank < 14: return Face21Card( rank, suit ) else: raise TypeError two = card21( 2, '♠' ) three = card21( 3, '♠' ) print( "{0} < {1} :: {2!r}".format( two, three, two < three ) ) print( "{0} > {1} :: {2!r}".format( two, three, two > three ) ) print( "{0} == {1} :: {2!r}".format( two, three, two == three ) ) print( "{0} <= {1} :: {2!r}".format( two, three, two <= three ) ) two_c = card21( 2, '♣' ) print( "{0} == {1} :: {2!r}".format( two, two_c, two == two_c ) )
A mixed class comparison See Hand above.
Noisy Exit
class Noisy: def __del__( self ): print( "Removing {0}".format(id(self)) )
Simple create and delete.
x= Noisy() del x
Shallow copy and multiple references.
ln = [ Noisy(), Noisy() ] ln2= ln[:] del ln del ln2
Circularity
class Parent: def __init__( self, *children ): self.children= list(children) for child in self.children: child.parent= self def __del__( self ): print( "Removing {__class__.__name__} {id:d}".format( __class__=self.__class__, id=id(self)) ) class Child: def __del__( self ): print( "Removing {__class__.__name__} {id:d}".format( __class__=self.__class__, id=id(self)) ) p = Parent( Child(), Child() ) del p import gc gc.collect() print( gc.garbage )
No circularity via weak references.
import weakref class Parent2: def __init__( self, *children ): self.children= list(children) for child in self.children: child.parent= weakref.ref(self) def __del__( self ): print( "Removing {__class__.__name__} {id:d}".format( __class__=self.__class__, id=id(self)) ) p = Parent2( Child(), Child() ) del p
Doesn't work. Can't use this form of __init__ with immutable classes.
class Float_Fail( float ): def __init__( self, value, unit ): super().__init__( value ) self.unit = unit
This is how we can tweak an immutable object __init__ is invoked.
class Float_Units( float ): def __new__( cls, value, unit ): obj= super().__new__( cls, value ) obj.unit= unit return obj
class Unit: """Generic definition of a unit. This unit is not the "standard" used for conversions. This is converted to a standard or a standard is converted to this. :: class X( Unit ): '''X full name''' name= "x" # Abbreviation factor= 123.45 Create a standard value from input in units of X. :: m_std= X.to_std( value ) Convert a standard value into units of X. :: m_x= X.from_std( m_std ) Note that for Temperature, a simple "factor" isn't appropriate and the :meth:`to_std` and :meth:`from_std` need to be overridden. For all subclasses, the long version of the unit's name should be the docstring. """ factor= 1.0 standard= None # Reference to the StandardUnit name= "" # Abbreviation of the unit's name. @classmethod def to_std( class_, value ): if value is None: return None return value/class_.factor @classmethod def from_std( class_, value ): if value is None: return None return value*class_.factor class UnitMeta(type): """Metaclass for Standard_Unit's to insert a circular reference. That way ``SomeStandardUnit.standard is SomeStandardUnit``. """ def __new__(mcs, name, bases, dict): new_class= type.__new__(mcs, name, bases, dict) new_class.standard = new_class return new_class class Standard_Unit( Unit, metaclass=UnitMeta ): """The standard unit used for conversions. Other units will convert to this. This will convert to other units. This is still a unit, but a conversion factor is not used. For all subclasses, the long version of the unit's name should be the docstring. """ name= "" # Abbreviation of the unit's name @classmethod def to_std( class_, value ): return value @classmethod def from_std( class_, value ): return value def convert( value, unit, *to ): """Convert a value from one set of units to another. :param value: A value, measured in the source unit. :param unit: A subclass of :class:`Unit` describing value. :param to: Subclasses of :class:`Unit`. If only a single unit is supplied, then a single value is returned; If multiple units are supplied, a tuple of values is returned. :return: value converted to the defined units. """ std_value= unit.to_std( value ) if len(to) == 1: converted= to[0].from_std( std_value ) else: converted= tuple( u.from_std(std_value) for u in to ) return converted class INCH( Standard_Unit ): """Decimal Inches""" name= "in" class FOOT( Unit ): """Decimal Feet""" name= "ft" standard= INCH factor= 1/12 class CENTIMETRE( Unit ): """Decimal Centimetres""" name= "in" standard= INCH factor= 2.54 class METRE( Unit ): """Decimal Metres""" name= "in" standard= INCH factor= .0254
>>> from p1_c02 import * >>> x= decifrac.INCH.to_std( 159.625 ) >>> decifrac.FOOT.from_std( x ) 13.302083333333332 >>> decifrac.METRE.from_std( x ) 4.054475