# Outcome Class¶

In Outcome Analysis we’ll look at the responsibilities and collaborators of Outcome objects.

In Design Decision – Object Identity we’ll look at how we can implement the notion of object identity and object equality. This is important because we will be matching Outcome objects based on bets and spinning the Roulette wheel.

We’ll look forward to some other use cases in Looking Forward. Specifically, we know that players, games, and tables will all share references to single outcome objects. How do we do this properly?

In Outcome Design – Complex we’ll detail the design for this class. In Outcome Design – Simple we’ll detail a simpler design using `@dataclass` definitions from the standard library.

In Outcome Deliverables we’ll provide a list of classes and tests to be built.

We’ll look at a Python programming topic in Message Formatting. This is a kind of appendix for beginning programmers.

## Outcome Analysis¶

There will be several hundred instances of `Outcome` objects on a given Roulette table. The bins on the wheel, similarly, collect various `Outcome` instances together. The minimum set of `Outcome` instances we will need are the 38 numbers, Red, and Black. The other instances will add details to our simulation.

In Roulette, the amount won is a simple multiplication of the amount bet and the odds. In other games, however, there may be a more complex calculation because the house keeps 5% of the winnings, called the “rake”. While it is not part of Roulette, it is good to have our `Outcome` class designed to cope with these more complex payout rules.

Also, we know that other casino games, like Craps, are stateful. An `Outcome` may change the game state. We can foresee reworking this class to add in the necessary features to change the state of the game.

While we are also aware that some odds are not stated as , we won’t include these other kinds of odds in this initial design. Since all Roulette odds are , we’ll simply assume that the denominator is always 1. We can foresee reworking this class to handle more complex odds, but we don’t need to handle the other cases yet.

The fundamental consideration here is comparing two instances of a `Outcome`. One will be part of a `Bet`, the other instance will be part of the `Wheel`. We need to know if the bet’s `Outcome` matches the wheel’s `Outcome`.

How does all object comparison work in Python?

Hint: The default rules aren’t helpful.

The rules reveal the foundations of object state and identity. While this class is seemingly insignificantly trivial, it exposes central Python concepts.

## Design Decision – Object Identity¶

Our design must match `Outcome` objects. We’ll be testing objects for equality.

The player will be placing bets that contain `Outcome` instances; the table will be holding bets. The wheel will select the winning `Outcome` instances. We need a simple test to see if two objects of the `Outcome` class are the same.

Was the `Outcome` for a bet equal to the `Outcome` contained in a spin of the wheel?

It turns out that this comparison between objects has some subtlety to it.

Here’s the naïve approach to class definition that doesn’t include any provision for equality tests.

Naïve Class Definition

```>>> class Outcome:
...    def __init__(self, name: str, odds: int) -> None:
...        self.name = name
...        self.odds = odds
```

This seems elegant enough. Sadly, it doesn’t work out when we need to make equality tests.

In Python, if we do nothing special, the default special method, `__eq__()`, will simply compare the internal object id values. These object id values are unique to each distinct object, irrespective of the attribute values.

This default behavior of objects is shown by the following example:

Equality Test Failure

```>>> oc1 = Outcome("Any Craps", 8)
>>> oc2 = Outcome("Any Craps", 8)

>>> oc1 == oc2
False
>>> id(oc1) == id(oc2)
False
>>> hash(oc1) == hash(oc2)
False
>>> oc1 is oc2
False
```

This example shows that we can have two objects that appear equal, but don’t compare as equal. While we can see they have the same attribute values, The `is` test shows they are distinct objects. This makes them not equal according to the default methods inherited from `object`. However, we would like to have two of these objects test as equal.

Actually, we want more than simple equality. We need to have them behave identically when used in sets and dictionaries.

### More than equal¶

We’ll be creating collections of `Outcome` objects, and we may need to create sets or maps where hash codes are used in addition to the simple equality tests.

Hash Codes?

Every object has a hash code. The hash code is simply an integer. It is a summary of the bits that make up the object. Python computes hash codes and uses these as a quick test for set membership and dictionary keys.

If two hash codes don’t match, the objects can’t possibly be equal. Further comparisons aren’t necessary. If two hash codes do match, there’s a possibility the two objects are equal.

The rare case of equal hash values for unequal objects is called a “hash collision.” It’s a consequence of digesting large and complex objects into small numeric values.

As we look forward, the Python `set` and `dict` depend on a `__hash__()` method and an `__eq__()` method of each object in the collection.

Hash Code Failure

```>>> hash(oc1)
270386794
>>> hash(oc2)
270392959
```

Note

Exact ID values will vary.

This shows that two objects that look the same to us can have distinct hash codes. Clearly, this is unacceptable, since we want to be able to create a set of `Outcome` objects without having things that look like repeats.

### Layers of Meaning¶

The lesson here is there are three distinct layers of meaning for comparing objects to see if they are “equal”. Here are the layers:

• Objects have the same hash code. We can call this “hash equality”.

We need the `__hash__()` method for several objects that represent the same `Outcome` to have the same hash code. When we put an object into a set or a dictionary, Python uses the `hash()` function, implemented by the `__hash__()` method.

Sometimes the hash codes are equal, but the object attributes aren’t actually equal. This is called a hash collision, and it’s rare but not unexpected.

If we don’t implement `__hash__()`, each instance is considered distinct making equality tests and set membership awkwardly wrong.

• Objects compare as equal. We can call this “attribute equality”.

This means that the `__eq__()` method returns `True`. When we use the == operator, this is evaluated by using the `__eq__()` method. This must be overridden by a class to implement attribute equality.

If we don’t implement this, the default implementation of this method isn’t too useful for our `Outcome` objects.

• Variables are two references to the same underlying object. We can call this “identity”.

We can test that two objects are the same by using the is comparison operator. This uses the internal Python identifier for each object. The identifier is revealed by the `id()` function.

When we use the is comparison, we’re asserting that the two variables are references to the same underlying object. This is most often used when comparing an object against the `None` object. In that exceptional case, we often use `x is None`.

How can we get the `__hash__()` and `__eq__()` implemented properly? We have several choices. We’ll start by looking at the `typing.NamedTuple` class.

#### Named Tuples¶

The naïve class definition shown above didn’t work out well. A significant improvement is to use the built-in `tuple` class. This can bind a name and a payoff amount together. Look at the following example; this shows behavior we would like to see:

```>>> oc1 = Outcome("Any Craps", 8)
>>> oc2 = Outcome("Any Craps", 8)
>>> oc1 == oc2
True
>>> id(oc1) == id(oc2)
False
>>> hash(oc1) == hash(oc2)
True
>>> oc1 is oc2
False
```

This is the behavior we want. Two distinct instances of an outcome compare as equal when all of their individual attribute values are equal.

We can leverage the `typing.NamedTuple` to provide this equality test for our unique classes.

```from typing import NamedTuple

class Outcome(NamedTuple):
name: str
odds: int
```

This definition provides us with a minimal definition of an outcome with all of the right behaviors.

The type hints in this class definition allow us to use mypy to confirm our design is likely to work. This is an important part of static analysis to be sure that the code is likely to work reliably and consistently.

This isn’t the only available solution to building `__hash__()` and `__eq__()` methods. We can also use a `dataclasses.dataclass`.

#### Frozen Dataclasses¶

We can leverage the `dataclasses.dataclass` to provide an equality test that’s similar to the features of a `typing.NamedTuple`. This gives us a useful equality test for our unique classes.

```from dataclasses import dataclass

@dataclass(frozen=True)
class Outcome_d:
name: str
odds: int
```

This definition provides us with a minimal definition of an outcome with all of the right behaviors.

```>>> oc1 = Outcome("Any Craps", 8)
>>> oc2 = Outcome("Any Craps", 8)
>>> oc1 == oc2
True
>>> id(oc1) == id(oc2)
False
>>> hash(oc1) == hash(oc2)
True
>>> oc1
Outcome(name='Any Craps', odds=8)
```

For the purposes we have in mind, both a `typing.NamedTuple` and a frozen `dataclasses.dataclass` have similar behaviors. There are differences, of course, but they don’t impact anything we’re going to do.

We’ll use the dataclasses in a number of later exercises. For that reason, we’ll start with a frozen dataclass as a way to define the `Outcome` class with useful equality comparisons.

### Special Method Implementation¶

Instead of using the available standard library class definitions, we can implement a few special methods that will provide useful equality tests. This is more complex than using one of the built-in library classes, but can be helpful for understanding how Python works.

We note that each instance of `Outcome` has a distinct `Outcome.name` value, it seems simple enough to compare names. This is one sense of “equal” that seems to be appropriate.

We can define the `__eq__()` and `__ne__()` special methods to provide useful equality tests:

• When comparing with a string value, compare the `self.name` attribute.

• When comparing with an `Outcome` instance, compare `self.name` and `other.name`.

Similarly, we can compute the value of the `__hash__()` method to use only the string name, and not the odds. This seems elegantly simple to return the hash of the string name rather than compute a hash.

The definition for `__hash__()` in section 3.3.1 of the Language Reference Manual tells us to do the calculation using a modulus based on `sys.hash_info.width`. This is the number of bits, the actual value we want to use is We’d use `sys.hash_info.modulus`.

We’ll be looking at `Outcome` objects in several contexts.

• We’ll have them in bins of a wheel as winning outcomes from each spin of the wheel.

• We’ll have them in bets that have been placed on the table.

• The player will have a set of outcomes they can bet on.

All these uses of `Outcome` objects forces us to consider an interesting question:

How do we implement Don’t Repeat Yourself (DRY) when creating `Outcome` objects?

If a player always bets on black, we don’t want to include the odds when the player names the “black” `Outcome` instance. Repeating the odds violates the DRY principle.

What are some alternatives?

• Global Outcome Objects. We can declare global variables for the various outcomes and use those global objects as needed. Generally, globals variables are often undesirable because changes to those variables can have unexpected consequences in a large application. Global constants are no problem at all. A pool of `Outcome` instances are proper constant values used to create bins and bets. There would be a lot of them, and they would all be assigned to distinct variables. Because of the way Python module imports work, we can define all of these in a single module and share the pool throughout the application.

• Outcome Factory. We can create an object which is a Factory for individual `Outcome` objects. When some part of the application – for example, the player – needs an `Outcome` object, the factory provides the object. The Factory can create use the unique name to locate the complete `Outcome` instance. This allows a player to request the “Black” `Outcome` instance. If we identify `Outcome` instances by their names, we can avoid repeating the payout odds. When we look at the Roulette Wheel, this seems to be the perfect factory for `Outcome` instances. A player can request the domain of all `Outcome` instances from the wheel.

• Singleton Outcome Class. A Singleton class creates and maintains a single instance of itself. This involves some unpleasant meta-programming to change the way object creation works. It’s not particularly difficult, but it seems needless when the other alternatives appear to be simpler. This has the profound disadvantage that each distinct outcome would need to be a distinct subclass of `Outcome`. This is an unappealing level of complexity. Further, it doesn’t solve the DRY problem of repeating the details of each Outcome.

A Factory seems like a good way to proceed. It can maintain a collection, and provide values from that collection. We can use class strings to identify `Outcome` objects. We don’t have to repeat the odds.

We’ll look forward to this in later chapters. For now, we’ll start with the basic class.

In the next section we’ll look at a complete and detailed definition of the `Outcome` class.

## Outcome Design – Complex¶

This is the design for an `Outcome` class separate from any dataclass definition. This is rather complex. The simpler approach is described below under Outcome Design – Simple.

class `Outcome`

`Outcome` contains a single outcome on which a bet can be placed.

In Roulette, each spin of the wheel has a number of `Outcome` objects with bets that will be paid off. For example, the “1” bin has the following winning `Outcome` instances: “1”, “Red”, “Odd”, “Low”, “Column 1”, “Dozen 1-12”, “Split 1-2”, “Split 1-4”, “Street 1-2-3”, “Corner 1-2-4-5”, “Five Bet”, “Line 1-2-3-4-5-6”, “00-0-1-2-3”, “Dozen 1”, “Low” and “Column 1”.

All of thee above-named bets will pay off if the wheel spins a “1”. This makes a Wheel and a Bin fairly complex containers of `Outcome` objects.

### Fields¶

`Outcome.``name`

Holds the name of the `Outcome`. Examples include `"1"`, `"Red"`.

`Outcome.``odds`

Holds the payout odds for this `Outcome`. Most odds are stated as 1:1 or 17:1, we only keep the numerator (17) and assume the denominator is 1.

We can use `name` to as the basis for computing hash codes and doing equality tests.

### Constructors¶

`Outcome.``__init__`(self, name: str, odds: int) → None
Parameters
• name (str) – The name of this outcome

• odds (int) – The payout odds of this outcome.

Sets the instance name and odds from the parameter name and odds.

### Methods¶

For now, we’ll assume that we’re going to have global instances of each `Outcome`. Later we’ll introduce some kind of Factory.

`Outcome.``winAmount`(self, amount: float) → float

Multiply this `Outcome`’s odds by the given amount. The product is returned.

Parameters

amount (float) – amount being bet

`Outcome.``__eq__`(self, other) → bool

Compare the `name` attributes of `self` and `other`.

Parameters

other (Outcome) – Another `Outcome` to compare against.

Returns

True if this name matches the other name.

Return type

bool

`Outcome.``__ne__`(self, other) → bool

Compare the `name` attributes of `self` and `other`.

Parameters

other (Outcome) – Another `Outcome` to compare against.

Returns

True if this name does not match the other name.

Return type

bool

`Outcome.``__hash__`(self) → int

Hash value for this outcome.

Returns

The hash value of the name, `hash(self.name)`.

Return type

int

A hash calculation must include all of the attributes of an object that are essential to it’s distinct identity.

In this case, we can return `hash(self.name)` because the odds aren’t really part of what makes an outcome distinct. Each outcome is an abstraction and a string name is all that identifies them.

The definition for `__hash__()` in section 3.3.1 of the Language Reference Manual tells us to do the calculation using a modulus based on `sys.hash_info.width`. That value is the number of bits, the actual value we want to use is `sys.hash_info.modulus`, which is based on the width.

`Outcome.``__str__`(self) → str

Easy-to-read representation of this outcome. See Message Formatting.

This easy-to-read String output method is essential. This should return a `String` representation of the name and the odds. A form that looks like `1-2 Split (17:1)` works nicely.

Returns

String of the form `name (odds:1)`.

Return type

str

`Outcome.``__repr__`(self) → str

Detailed representation of this outcome. See Message Formatting.

Returns

String of the form `Outcome(name=name, odds=odds)`.

Return type

str

## Outcome Design – Simple¶

A simpler variation on the `Outcome` class can be based on `@dataclass(frozen=True)`.

See above, in the fields section, the two fields required.

The default methods created by the `@dataclass` decorator should work perfectly.

The `__str__()` method will have to be written based on the description above, under methods.

This should pass all of the unit tests described in the Outcome Deliverables section.

## Example Unit Test Case¶

The `Outcome` class doesn’t have too many features. The unit tests should reflect the core simplicity.

```def test_outcome():
o1 = Outcome("Red", 1)
o2 = Outcome("Red", 1)
o3 = Outcome("Black", 2)
assert str(o1) == "Red 1:1"
assert repr(o2) == "Outcome(name='Red', odds=1)"
assert o1 == o2
assert o1.odds == 1
assert o1.name == "Red"
assert o1 != o3
assert o2 != o3
```

This seems to cover almost everything imaginable with respect to the `Outcome` class.

## Outcome Deliverables¶

There are two deliverables for this exercise. Both will have Python docstrings.

• The `Outcome` class. This can be put into a `roulette.py` file. A single file can include all of the class definitions.

• Unit tests of the `Outcome` class. This can be doctest strings inside the class itself, or it can be a separate `test_outcome.py` module.

The unit test should create a three instances of `Outcome`, two of which have the same name. It should use a number of individual tests to establish that two `Outcome` with the same name will test true for equality, have the same hash code, and establish that the `winAmount()` method works correctly.

We’ve provided an example as part of this section, to clarify what the test case should include.

## Message Formatting¶

For the very-new-to-Python, there are few variations on creating a formatted message string. The `str()` function makes use of a class `__str__()` method to provide the class-unique string. The `repr()` function makes use of the class `__repr__()` method to provide the class unit representation.

The `str()` function is intended to produce easy-to-read strings. The `repr()` function is intended to provide detailed strings, often in Python syntax.

Generally, we simply use something like this.

```def __str__(self):
return f"{self.name:s} ({self.odds:d}:1)"
```

The format f-string uses `:s` and `:d` as detailed specifications for the values to interpolate into the string. There’s a lot of flexibility in how numbers are formatted.

There’s another variation that can be handy.

```def __repr__( self ):
return f"{self.__class__.__name__:s}(name={self.name!r}, odds={self.odds!r})"
```

This exposes the class name as well as the attribute values.

We’ve used the `!r` specification to request the internal representation for each attribute values. For a string, it means it will be explicitly quoted.

## Looking Forward¶

In the next chapter, we’ll combine `Outcome` instances into `Bin` collections. These `Bin` objects will become part of the Roulette `Wheel` collection.