[Tutor] Type annotation errors

Peter Otten __peter__ at web.de
Thu Jun 4 09:46:51 EDT 2020


boB Stepp wrote:

One more (remember I'm very new to mypy):

> The following function yields type annotation errors and I do not
> understand why:
> 
> def evaluate_portfolio(
>      portfolio: List[Dict[str, Union[str, int, float]]], stock_prices:
>      Dict[str, float]
> ) -> Tuple[float, float]:
>      """Compute the current value and gain/loss of a portfolio."""
>      portfolio_value = 0.0
>      gain_loss = 0.0
>      for stock in portfolio:
>          current_value = stock["shares"] * stock_prices[stock["name"]]
>          current_gain_loss = current_value - (stock["shares"] *
>          stock["price"]) portfolio_value += current_value
>          gain_loss += current_gain_loss
>      return portfolio_value, gain_loss
> 
> The error messages generated are:
> 
> report.py|47 col 43 info| Left operand is of type "Union[str, int, float]"
> report.py|47 col 43 error| Unsupported operand types for * ("str" and
> "float") 

Given 

x: Union[str, int, float] 
y: float
x * y

mypy ensures that * is defined for

str * float  # error
int * float  # OK
float * float  # OK

There might be a way to tell mypy that two cases cannot occur and still use 
dicts as pseudo-structs, but I expect that there will be a big impact of 
optional typing on the design, and unlike unit tests it will not just 
improve the structure, it will also make the code less flexible.
Replacing the somewhat amorphous dict with a dataclass:

Money = float  # fixme

@dataclass
class Record:
    shares: int
    name: str
    price: Money


def evaluate_portfolio(
        portfolio: List[Record],
        stock_prices: Dict[str, Money]
) -> Tuple[float, float]:
     """Compute the current value and gain/loss of a portfolio."""
     portfolio_value = 0.0
     gain_loss = 0.0
     for stock in portfolio:
         current_value = stock.shares * stock_prices[stock.name]
         current_gain_loss = current_value - stock.shares * stock.price
         portfolio_value += current_value
         gain_loss += current_gain_loss
     return portfolio_value, gain_loss

PS: In spite of my defeatist remark above I prefer this version over the one 
you posted; maybe not all is lost in Python land ;)

PPS: For someone who knows a little C the obvious fix for your version is to 
cast the union to the expected type:

    for stock in portfolio:
         shares = cast(int, stock["shares"])
         name = cast(str, stock["name"])
         price = cast(float, stock["price"])

         current_value =  shares * stock_prices[name]
         current_gain_loss = current_value - shares * price
         portfolio_value += current_value
         gain_loss += current_gain_loss


> report.py|47 col 56 error| Invalid index type "Union[str, int,
> float]" for "Dict[str, float]"; expected type "str" report.py|48 col 29
> info| Both left and right operands are unions report.py|48 col 29 error|
> Unsupported operand types for - ("float" and "str") report.py|48 col 46
> error| Unsupported operand types for * ("str" and "str") report.py|48 col
> 46 error| Unsupported operand types for * ("str" and "float") report.py|48
> col 46 error| Unsupported operand types for * ("float" and "str")
> 
> For reference line 47 is the line in the for loop beginning
> "current_value..."
> 
> portfolio is a list of dictionaries where each dictionary is of the form
> {"name": "stock name", "shares": number_of_shares_owned, "price":
> price_per_share}
> 
> The code runs flawlessly and gives the expected results.  Where am I going
> wrong in my type annotations?
> 




More information about the Tutor mailing list