Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- """
- WizCoin is a class to represent a quantity of coins in a wizard currency.
- In this currency, there are knuts, sickles (worth 29 knuts), and galleons
- (worth 17 sickles or 493 knuts).
- """
- __version__ = '0.0.1'
- import copy
- import operator
- # Constants used in this module:
- KNUTS_PER_SICKLE = 29
- SICKLES_PER_GALLEON = 17
- KNUTS_PER_GALLEON = SICKLES_PER_GALLEON * KNUTS_PER_SICKLE
- class WizCoinException(Exception):
- """Exceptions of this class are raised by the wizcoin module for incorrect
- use of the module. If wizcoin is the source of any other raised exceptions,
- assume that it is caused by a bug in the module instead of misuse."""
- pass
- class CoinBag:
- """CoinBag objects represent an amount of coins, not money. They cannot
- have half a coin, or a negative number of coins."""
- issuer = 'gb' # The ISO 2-letter country code of who issues this currency.
- def __init__(self, galleons=0, sickles=0, knuts=0):
- """Create a new CoinBag object with galleons, sickles, and knuts."""
- self.galleons = galleons
- self.sickles = sickles
- self.knuts = knuts
- @property
- def galleons(self):
- """The number of galleons in the CoinBag."""
- return self._galleons
- @galleons.setter
- def galleons(self, value):
- if not isinstance(value, int) or value < 0:
- raise WizCoinException('galleons attr must be a positive int')
- self._galleons = value
- @galleons.deleter
- def galleons(self):
- self._galleons = 0
- @property
- def sickles(self):
- """The number of sickles in the CoinBag."""
- return self._sickles
- @sickles.setter
- def sickles(self, value):
- if not isinstance(value, int) or value < 0:
- raise WizCoinException('sickles attr must be a positive int')
- self._sickles = value
- @sickles.deleter
- def sickles(self):
- self._sickles = 0
- @property
- def knuts(self):
- """The number of knuts in the CoinBag."""
- return self._knuts
- @knuts.setter
- def knuts(self, value):
- if not isinstance(value, int) or value < 0 :
- raise WizCoinException('knuts attr must be a positive int')
- self._knuts = value
- @knuts.deleter
- def knuts(self):
- self._knuts = 0
- @property
- def value(self):
- """The value (in knuts) of all the coins in this CoinBag."""
- return (self._galleons * KNUTS_PER_GALLEON) + (self._sickles * KNUTS_PER_SICKLE) + (self._knuts)
- def convertToGalleons(self):
- """Modifies the CoinBag in-place, converting knuts and sickles to
- galleons. There may knuts and sickles leftover as change."""
- # Convert knuts to sickles, then sickles to galleons.
- self._sickles += self._knuts // KNUTS_PER_SICKLE
- self._knuts %= KNUTS_PER_SICKLE # Knuts may be remaining as change.
- self._galleons += self._sickles // SICKLES_PER_GALLEON
- self._sickles %= SICKLES_PER_GALLEON # Sickles might remain as change.
- def convertToSickles(self):
- """Modifies the CoinBag object in-place, converting knuts and
- galleons to sickles. There may knuts leftover as change."""
- self._sickles += (self._galleons * SICKLES_PER_GALLEON) + (self._knuts // KNUTS_PER_SICKLE)
- self._knuts %= KNUTS_PER_SICKLE # Knuts might remain as change.
- self._galleons = 0
- def convertToKnuts(self):
- """Modifies the CoinBag object in-place, converting galleons and
- sickles to knuts."""
- self._knuts += (self._galleons * KNUTS_PER_GALLEON) + (self._sickles * KNUTS_PER_SICKLE)
- self._galleons = 0
- self._sickles = 0
- def __repr__(self):
- """Returns a string representation of this CoinBag object that can be
- fed into the interactive shell to make an identical CoinBag object."""
- className = type(self).__name__
- return '%s(galleons=%s, sickles=%s, knuts=%s)' % (className, self._galleons, self._sickles, self._knuts)
- def __len__(self):
- """Returns the number of coins in this CoinBag."""
- return self._galleons + self._sickles + self._knuts
- def __copy__(self):
- """Returns a new, duplicate CoinBag object of this CoinBag."""
- return CoinBag(self._galleons, self._sickles, self._knuts)
- def __deepcopy__(self, memo):
- """Returns a new, duplicate CoinBag object of this CoinBag. This
- method reuses __copy__() since CoinBags don't need deep copies."""
- return self.__copy__()
- def __str__(self):
- """Returns a string representation of the CoinBag object, formatted
- like '2g,5s,10k' for a CoinBag of 2 galleons, 5 sickles, 10 knuts."""
- return '%sg,%ss,%sk' % (self._galleons, self._sickles, self._knuts)
- def __int__(self):
- """Returns the value of the coins in this CoinBag as an int."""
- return self.value
- def __float__(self):
- """Returns the value of the coins in this CoinBag as a float."""
- return float(self.value)
- def __bool__(self):
- """Returns the Boolean value of the CoinBag."""
- return not (self._galleons == 0 and self._sickles == 0 and self._knuts == 0)
- @classmethod
- def fromStr(cls, coinStr):
- """An alternative constructor that gets the coin amounts from
- `coinStr`, which is formatted like '2g,5s,10k'."""
- try:
- if coinStr == '':
- return cls(galleons=0, sickles=0, knuts=0)
- gTotal = 0
- sTotal = 0
- kTotal = 0
- for coinStrPart in coinStr.split(','):
- if coinStrPart.endswith('g'):
- gTotal += int(coinStrPart[:-1])
- elif coinStrPart.endswith('s'):
- sTotal += int(coinStrPart[:-1])
- elif coinStrPart.endswith('k'):
- kTotal += int(coinStrPart[:-1])
- else:
- raise Exception()
- except:
- raise WizCoinException('coinStr has an invalid format')
- return cls(galleons=gTotal, sickles=sTotal, knuts=kTotal)
- @classmethod
- def isEuropeanCurrency(cls):
- """A helper method that returns if this currency is used in Europe."""
- return cls.issuer in {'ad', 'al', 'am', 'at', 'ba', 'be', 'bg', 'by', 'ch', 'cy', 'cz', 'de', 'dk', 'ee', 'es', 'fi', 'fo', 'fr', 'gb', 'ge', 'gi', 'gr', 'hr', 'hu', 'ie', 'im', 'is', 'it', 'li', 'lt', 'lu', 'lv', 'mc', 'md', 'me', 'mk', 'mt', 'nl', 'no', 'pl', 'po', 'pt', 'ro', 'rs', 'ru', 'se', 'si', 'sk', 'sm', 'tr', 'ua', 'va'}
- @staticmethod
- def _isCoinBagType(obj): # This should be a module-level function.
- """A helper function that returns True if `obj` has `galleons`,
- `sickles`, `knuts`, and `value` attributes, otherwise returns
- False."""
- return hasattr(obj, 'galleons') and hasattr(obj, 'sickles') and hasattr(obj, 'galleons') and hasattr(obj, 'value')
- # Overloading comparison operators:
- def _comparisonOperatorHelper(self, operatorFunc, other):
- """A helper method that carries out a comparison operation."""
- if CoinBag._isCoinBagType(other):
- # Compare this CoinBag's value with another CoinBag's value.
- return operatorFunc(self.value, other.value)
- elif isinstance(other, (int, float)):
- # Compare this CoinBag's value with an int or float.
- return operatorFunc(self.value, other)
- elif operatorFunc == operator.eq:
- return False # Not equal to all non CoinBag/int/float values.
- elif operatorFunc == operator.ne:
- return True # Not equal to all non CoinBag/int/float values.
- else:
- # Can't compare with whatever data type `other` is.
- raise WizCoinException("'%s' not supported between instances of '%s' and '%s'" % (operatorFunc.__name__, self.__class__.__name__, other.__class__.__name__))
- def __eq__(self, other):
- """Overloads the == operator to compare CoinBag objects with ints,
- floats, and other CoinBag objects."""
- return self._comparisonOperatorHelper(operator.eq, other)
- def __ne__(self, other):
- """Overloads the != operator to compare CoinBag objects with ints,
- floats, and other CoinBag objects."""
- return self._comparisonOperatorHelper(operator.ne, other)
- def __lt__(self, other):
- """Overloads the < operator to compare CoinBag objects with ints,
- floats, and other CoinBag objects."""
- return self._comparisonOperatorHelper(operator.lt, other)
- def __le__(self, other):
- """Overloads the <= operator to compare CoinBag objects with ints,
- floats, and other CoinBag objects."""
- return self._comparisonOperatorHelper(operator.le, other)
- def __gt__(self, other):
- """Overloads the > operator to compare CoinBag objects with ints,
- floats, and other CoinBag objects."""
- return self._comparisonOperatorHelper(operator.gt, other)
- def __ge__(self, other):
- """Overloads the >= operator to compare CoinBag objects with ints,
- floats, and other CoinBag objects."""
- return self._comparisonOperatorHelper(operator.ge, other)
- # Overloading math operators:
- def __mul__(self, other):
- """Overloads the * operator to produce a new CoinBag object with the
- product amount. `other` must be a positive int."""
- if isinstance(other, int) and other >= 0:
- return CoinBag(self._galleons * other,
- self._sickles * other,
- self._knuts * other)
- else:
- raise WizCoinException('%s objects can only multiply with positive ints' % (self.__class__.__name__))
- def __rmul__(self, other):
- """Overloads the * operator to produce a new CoinBag object with the
- product amount. `other` must be a positive int."""
- return self.__mul__(other) # * is commutative, reuse __mul__().
- def __imul__(self, other):
- """Overloads the * operator to modify a CoinBag object in-place with
- the product amount. `other` must be a positive int."""
- if isinstance(other, int) and other >= 0:
- self._galleons *= other # In-place modification.
- self._sickles *= other
- self._knuts *= other
- else:
- raise WizCoinException('%s objects can only multiply with positive ints' % (self.__class__.__name__))
- return self
- def __add__(self, other):
- """Overloads the + operator to produce a new CoinBag object with the
- sum amount. `other` must be a CoinBag."""
- if CoinBag._isCoinBagType(other):
- return CoinBag(self._galleons + other.galleons,
- self._sickles + other.sickles,
- self._knuts + other.knuts)
- else:
- raise WizCoinException('%s objects can only add with other wizcoin.CoinBag objects' % (self.__class__.__name__))
- def __iadd__(self, other):
- """Overloads the += operator to modify this CoinBag in-place with the
- sum amount. `other` must be a CoinBag."""
- if CoinBag._isCoinBagType(other):
- self._galleons += other.galleons # In-place modification.
- self._sickles += other.sickles
- self._knuts += other.knuts
- else:
- raise WizCoinException('%s objects can only add with other wizcoin.CoinBag objects' % (self.__class__.__name__))
- return self
- def __sub__(self, other):
- """Overloads the - operator to produce a new CoinBag object with the
- difference amount. `other` must be a CoinBag object with less than or
- equal number of coins of each type as this CoinBag object."""
- if CoinBag._isCoinBagType(other):
- if self._galleons < other.galleons or self._sickles < other.sickles or self._knuts < other.knuts:
- raise WizCoinException('subtracting %s from %s would result in negative quantity of coins' % (other, self))
- return CoinBag(self._galleons - other.galleons,
- self._sickles - other.sickles,
- self._knuts - other.knuts)
- else:
- raise WizCoinException('%s objects can only subtract with other wizcoin.CoinBag objects' % (self.__class__.__name__))
- def __isub__(self, other):
- """Overloads the -= operator to modify this CoinBag in-place with the
- difference amount. `other` must be a CoinBag object with less than or
- equal number of coins of each type as this CoinBag object."""
- if CoinBag._isCoinBagType(other):
- if self._galleons < other.galleons or self._sickles < other.sickles or self._knuts < other.knuts:
- raise WizCoinException('subtracting %s from %s would result in negative quantity of coins' % (other, self))
- self._galleons -= other.galleons
- self._sickles -= other.sickles
- self._knuts -= other.knuts
- else:
- raise WizCoinException('%s objects can only subtract with other wizcoin.CoinBag objects' % (self.__class__.__name__))
- return self
- def __lshift__(self, other):
- """Overloads the << operator to transfer all coins from the CoinBag on
- the right side to the CoinBag on the left side."""
- if not CoinBag._isCoinBagType(other):
- raise WizCoinException('CoinBag can only use << on other CoinBag objects')
- self._galleons += other.galleons # Add to this CoinBag.
- self._sickles += other.sickles
- self._knuts += other.knuts
- other.galleons = 0 # Empty the other CoinBag.
- other.sickles = 0
- other.knuts = 0
- def __rshift__(self, other):
- """Overloads the >> operator to transfer all coins from the CoinBag on
- the left side to the CoinBag on the right side."""
- if not CoinBag._isCoinBagType(other):
- raise WizCoinException('CoinBag can only use >> on other CoinBag objects')
- other.galleons += self._galleons # Add to the other CoinBag.
- other.sickles += self._sickles
- other.knuts += self._knuts
- self._galleons = 0 # Empty this CoinBag.
- self._sickles = 0
- self._knuts = 0
- def __getitem__(self, idx):
- """Overloads the [] operator to access what kind of coin is at index
- `idx`. The order of coins is galleons, then sickles, then knutes."""
- if idx >= len(self) or idx < -len(self):
- raise WizCoinException('index out of range')
- if idx < 0:
- idx = len(self) + idx # Convert negative index to positive.
- if idx < self._galleons:
- return 'galleon'
- elif idx < self._galleons + self._sickles:
- return 'sickle'
- else:
- return 'knut'
- def __setitem__(self, idx, coinType):
- """Overloads the [] operator to access what kind of coin is at index
- `idx`. The order of coins is galleons, then sickles, then knutes."""
- if coinType not in ('galleon', 'sickle', 'knut'):
- raise WizCoinException("coinType must be one of 'galleon', 'sickle', or 'knut'")
- try:
- coin = self[idx]
- except Exception as exc:
- raise WizCoinException(str(exc))
- if coin == 'galleon':
- self._galleons -= 1
- elif coin == 'sickle':
- self._sickles -= 1
- elif coin == 'knut':
- self._knuts -= 1
- # Add a coin of type `coinType`.
- if coinType == 'galleon':
- self._galleons += 1
- elif coinType == 'sickle':
- self._sickles += 1
- elif coinType == 'knut':
- self._knuts += 1
- def __delitem__(self, idx):
- """Overloads the [] operator to remove the kind of coin at index
- `idx`."""
- try:
- coin = self[idx]
- except Exception as exc:
- raise WizCoinException(str(exc))
- if coin == 'galleon':
- self._galleons -= 1
- elif coin == 'sickle':
- self._sickles -= 1
- elif coin == 'knut':
- self._knuts -= 1
- def __iter__(self):
- """Returns an iterator that iterates over the coins in this CoinBag.
- The order of coins is galleons, then sickles, then knuts."""
- return CoinBagIterator(self)
- class CoinBagIterator:
- def __init__(self, coinBagObj):
- """Creates an iterator for the given CoinBag object."""
- self.nextIndex = 0
- self.coinBagObj = coinBagObj
- def __next__(self):
- """Returns the next coin from the CoinBag. The order of coins is
- galleons, then sickles, then knuts."""
- if self.nextIndex >= len(self.coinBagObj):
- raise StopIteration
- nextCoin = self.coinBagObj[self.nextIndex]
- self.nextIndex += 1
- return nextCoin
- class CoinBagCollection:
- def __init__(self, coinBags):
- self.coinBags = tuple(coinBags)
- self._origAmounts = tuple([copy.copy(bag) for bag in self.coinBags])
- for bag in self.coinBags:
- if not CoinBag._isCoinBagType(bag):
- raise WizCoinException('all arguments to CoinBagCollection must be CoinBag objects')
- def __enter__(self):
- self.expectedTotal = sum([bag.value for bag in self.coinBags])
- return tuple(self.coinBags)
- def __exit__(self, excType, excValue, excTraceback):
- total = sum([bag.value for bag in self.coinBags])
- if total == self.expectedTotal and excType is None:
- return # Everything is fine.
- # Reset bags to their original amounts.
- for i, bag in enumerate(self.coinBags):
- bag._galleons = self._origAmounts[i]._galleons
- bag._sickles = self._origAmounts[i]._sickles
- bag._knuts = self._origAmounts[i]._knuts
- if total != self.expectedTotal:
- raise WizCoinException('expected total value (%s) does not match current total value (%s)' % (self.expectedTotal, total))
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement