This is an iterative review.Previous IterationMy snake now moves and grows and successfully kills itself.Eventually, the game board will handle state and things like the list of fatal coordinates and telling a snake whether it's dead, but for now that's just a function called fatal_board_positions paired with is_fatal_position.Fatal coordinates are (X = 1-10, Y = 7-10)So, how pythonic is my snake?And how can I make it better?Module utilities.py(N.B. there are other things in the utiltiies module, but they're not used here so I left them out)import numpy as npDIRECTION_VECTORS = {    'n': (0, 1),    's': (0, -1),    'e': (1, 0),    'w': (-1, 0)    }def unit_vector_from_cardinal(cardinal: str) -> np.array:    if cardinal in DIRECTION_VECTORS:        return np.array(DIRECTION_VECTORS[cardinal])    else:        raise ValueError(An invalid cardinal direction was provided)Module Snake.py(code laid out in the order presented here, but split up for convenience)Headerimport numpy as npfrom utilities import unit_vector_from_cardinal'''Game Board:    X by Y array of numbers    Derived from Snake Object and base parameters (height, width, obstacles?)Board Values:    0 - Empty    1 - Snake Head    2 - Snake BodySnake:    Ordered list of X,Y coordinates for each body segmentActions:    The board is responsible for handling state    The snake is therefore ignorant of state and should not decide *how* to move    The board should decide whether the snake:        Grows (food)        Moves (non-food)        Is Dead'''Static Methodsdef get_initial_body_coordinates(x_pos: int, y_pos: int, facing: str, length: int) -> list:    first_coordinate = np.array([x_pos, y_pos])    unit_vector = unit_vector_from_cardinal(facing)    return [        first_coordinate        - unit_vector * i        for i in range(0, length)        ]def is_fatal_position(position, *args):    # *args should be (lists of numpy arrays) of fatal coordinates    for pos_list in args:        if any((position == pos).all() for pos in pos_list):            return True    return Falsedef fatal_board_positions() -> list:    # Will eventually be handled by game board    return [        np.array((x, y))        for x in range(1, 10 + 1)        for y in range(7, 10 + 1)    ]Class Snakeclass Snake:    # An ordered list of X,Y coordinates representing the position of body segments    # Grow = add new coordinate in relevant direction    # Contract = remove last coordinate    # move = (grow then contract)    body_coordinates = list()    def __init__(self, x_pos: int, y_pos: int, facing: str, length: int):        self.new_body(x_pos, y_pos, facing, length)    def get_non_head_coordinates(self) -> list:        return self.body_coordinates[1:]    def get_head_pos(self):        return self.body_coordinates[0]    def add_new_pos(self, new_pos):        self.body_coordinates = [new_pos] + self.body_coordinates    def remove_last_pos(self):        del self.body_coordinates[-1]    def new_body(self, x_pos: int, y_pos: int, facing: str, length: int):        self.body_coordinates = get_initial_body_coordinates(x_pos, y_pos, facing, length)    def grow(self, direction: str):        # Add a new coordinate to the head of the body list        direction_vector = unit_vector_from_cardinal(direction)        current_head_pos = self.get_head_pos()        new_pos = current_head_pos + direction_vector        self.add_new_pos(new_pos)    def contract(self):        self.remove_last_pos()    def move(self, direction: str):        self.grow(direction)        self.contract()Testsdef test_move(target_snake, direction):    # Move in direction    # Check for death    # Print body coordinates    target_snake.move(direction)    print ('Move ' + direction)    print ('Is Dead?')    print(is_fatal_position(target_snake.get_head_pos(), fatal_board_positions(), target_snake.get_non_head_coordinates()))    for i in target_snake.body_coordinates:        print (i)def test_hit_north():    # continue north until death at Y = 7    print(('='*13))    print('= Hit north =')    print(('='*13))    new_snake = Snake(5, 5, 'n', 3)    for i in new_snake.body_coordinates:        print(i)    test_move(new_snake, 'n')    test_move(new_snake, 'n') # Should print Dead = Truedef test_hit_self():    print(('='*12))    print('= Hit self =')    print(('='*12))    new_snake = Snake(5, 5, 'n', 5)    for i in new_snake.body_coordinates:        print(i)    test_move(new_snake, 'e')    test_move(new_snake, 's')    test_move(new_snake, 'w') # Should print Dead = Trueif __name__ == '__main__':    test_hit_north()    test_hit_self()Current Output============== Hit north ==============[5 5][5 4][5 3]Move nIs Dead?False[5 6][5 5][5 4]Move nIs Dead?True[5 7][5 6][5 5]============= Hit self =============[5 5][5 4][5 3][5 2][5 1]Move eIs Dead?False[6 5][5 5][5 4][5 3][5 2]Move sIs Dead?False[6 4][6 5][5 5][5 4][5 3]Move wIs Dead?True[5 4][6 4][6 5][5 5][5 4]
Snake is dead. Long live snake
python;beginner;python 3.x;snake game
Glad to see iteration #2 of your code, and that you've used some of my suggestions!  :)Got some more for you though.DocstringsWhile you have comments explaining what different functions seem to do (or they are already fairly clear to someone who understands what's going on with the code), you should consider using docstrings in your code.Style: PEP8 GuidelinesJust some minor things here.There should not be any spaces between print and the ( that goes with it.In line comments at the end of a line should have two spaces between the end of the code line, and the start of the comment (the #)Snake class: Multiple suggestionsMake body_positions list a protected item in the class, add a Getter that works in this case.This is more or less to protect against modification of the Snake positions and to use a Getter that returns the list of positions, but don't directly access the protected variable inside the class.  (We have other 'getter' and 'setter' items too, so I'll touch base on that in a few minutes)class Snake:    ...    _body_coordinates = list()    ...    @property    def body_coordinates(self) -> list:        return self._body_coordinates    ...Note that I also add a property called body_coordinates which basically does what you were doing before.  (Python won't yell for accessing the protected property, necessarily, but we should use Getters to get data from the class's items unless we are directly manipulating that class's properties with a function or code outside of the class, which doesn't seem to be being done here.  Accessing protected properties can cause undefined behavior at times, so it's better to be careful here and use Getters / Setters / Modifiers instead.)'Getter' functions as propertiesThese are getters:def get_non_head_coordinates(self) -> list:    return self.body_coordinates[1:]def get_head_pos(self):    return self.body_coordinates[0]The getter functions are simply returning information.  We can handle them as properties instead of standard functions (thereby it's not 'callable', so we save some parentheses later on, and refer to them without parentheses later).  We can also change their name by setting them as properties.class Snake:    ...    @property    def non_head_coordinates(self) -> list:        return self._body_coordinates[1:]    @property    def head_pos(self):        return self._body_coordinates[0]    ...Anywhere you had .get_* for getting info from the Snake class, you can replace it with just .head_pos and .non_head_coordinates.  No need for parentheses since you're not really needing to worry about calling anything here as we have nothing more to do but getting the value from an internal item to the Snake class.grow: Unnecessary variable creation inside classYou don't need to create a new variable - current_head_pos will just use a property of the class with the above recommendations, and since the variable is only being used inside of the grow function, we don't need to have a variable here.  Just use the property in place, so you'll end up with this:def grow(self, direction: str):    # Add a new coordinate to the head of the body list    direction_vector = unit_vector_from_cardinal(direction)    new_pos = self.head_pos + direction_vector    self.add_new_pos(new_pos)There may be more suggestions to come, though this is the first set of my recommendations.