First we'll perform a small refactoring and extract the seat designator parsing and validation logic into it's own method, _parse_seat(). We use a leading underscore here because this method is an implementation detail:
class Flight:
# ...
def _parse_seat(self, seat):
"""Parse a seat designator into a valid row and letter.
Args:
seat: A seat designator such as 12F
Returns:
A tuple containing an integer,string for row and seat.
"""
row_numbers, seat_letters = self._aircraft.seating_plan()
letter = seat[-1]
if letter not in seat_letters:
raise ValueError("Invalid seat letter {}".format(letter))
row_text = seat[:-1]
try:
row = int(row_text)
except ValueError:
raise ValueError("Invalid seat row {}".format(row_text))
if row not in row_numbers:
raise ValueError("Invalid row number {}".format(row))
return row, letter
The new _parse_seat() method returns a tuple with an integer row number and a seat letter string. This has made allocate_seat() much simpler:
def allocate_seat(self, seat, passenger):
"""Allocate a seat to a passenger.
Args:
seat: A seat designator such as '12C' or '21F'.
passenger: The passenger name.
Raises:
ValueError: If the seat is unavailable.
"""
row, letter = self._parse_seat(seat)
if self._seating[row][letter] is not None:
raise ValueError("Seat {} already occupied".format(seat))
self._seating[row][letter] = passenger
Notice how the call to _parse_seat() also requires explicit qualification with the self prefix.