2016-08-13 3 views
-2

У меня есть класс под названием «Movable Piece». Конечно, я хочу, чтобы все экземпляры этого класса двигались. Для этого я думал, что другой класс под названием «Движение» будет приятным и многоразовым, если мне понадобится другой материал для перемещения. Кроме того, мне очень нравится, как выглядит код my_piece.move.up.Динамически создавать методы для экземпляра в Python

Проблема возникает, когда я понимаю, что мне нужно динамически пытаться установить методы для экземпляра класса Movements, созданного Piece, поскольку функции, которые перемещают кусок, также могут быть определены пользователем. Как я могу это достичь? Я думаю, что код прояснит, что я хочу делать.

class MovablePiece(Piece): 
    class Movements: 
     def __init__(self, piece, movement_functions=None): 
      if movement_functions is None: 
       self.__default_movements(piece) 
      else: 
       self.__set_movements(movement_functions) 

     def __default_movements(self, piece): 
      def up(): return piece.move(piece.surroundings[Direction.UP]) 
      def right(): return piece.move(piece.surroundings[Direction.RIGHT]) 
      def down(): return piece.move(piece.surroundings[Direction.DOWN]) 
      def left(): return piece.move(piece.surroundings[Direction.LEFT]) 
      self.__set_movements([up, right, down, left]) 

     def __set_movements(self, movement_functions): 
      for movement_function in movement_functions: 
       setattr(self, movement_function.__name__, movement_function) 

    def __init__(self, letter, name, movements=None, walkable=False): 
     Piece.__init__(self, letter, name, walkable) 
     self.move = MovablePiece.Movements() 

Это, конечно, не будет работать: SetAttr пытается установить функцию как атрибут, который я не думаю, что имеет много смысла, но вы получите суть.

Это ошибка, когда я пытаюсь сделать my_piece.move.right:

Traceback (most recent call last): 
    File "main.py", line 45, in <module> 
    screen.show() 
    File "/home/joaquin/Documents/escape/ludema/screen.py", line 12, in show 
    function() 
    File "main.py", line 35, in control_bruma 
    mappings[action]() 
    File "/home/joaquin/Documents/escape/ludema/pieces.py", line 78, in right 
    def right(): return piece.move(piece.surroundings[Direction.RIGHT]) 
TypeError: 'Movements' object is not callable 

Аналогичная проблема, если я заставляю методы, чтобы быть staticmethods (как они фактически не требуют «я»):

Traceback (most recent call last): 
    File "main.py", line 45, in <module> 
    screen.show() 
    File "/home/joaquin/Documents/escape/ludema/screen.py", line 12, in show 
    function() 
    File "main.py", line 35, in control_bruma 
    mappings[action]() 
TypeError: 'staticmethod' object is not callable 
+0

'setattr()' должно работать нормально. Проблема может заключаться в том, что функции не определены для принятия первого аргумента 'self', поэтому они не являются надлежащими методами. – martineau

+0

Это не так. Я приложу ошибку, полученную трассировкой. – joaquinlpereyra

+0

@martineau Я добавил трассировку для тех случаев, когда я пытаюсь сделать их staticmethods, которые вы не без параметров легко :) – joaquinlpereyra

ответ

1

IMHO вы должны были указать mvce в вопросе, что этот ответ мог бы добавить некоторые дополнительные советы, в любом случае, вот рабочий пример, угадывающий недостающие биты вашего кода:

class Piece(object): 

    def __init__(self, letter, name, walkable): 
     self.letter = letter 
     self.name = name 
     self.walkable = walkable 


class Movements: 

    def __init__(self, piece, movement_functions=None): 
     if movement_functions is None: 
      self.__default_movements(piece) 
     else: 
      self.__set_movements(movement_functions) 

    def __default_movements(self, piece): 
     def up(): print("up") 

     def right(): print("right") 

     def down(): print("down") 

     def left(): print("left") 
     self.__set_movements([up, right, down, left]) 

    def __set_movements(self, movement_functions): 
     for movement_function in movement_functions: 
      setattr(self, movement_function.__name__, movement_function) 


class MovablePiece(Piece): 

    def __init__(self, letter, name, movements=None, walkable=False): 
     Piece.__init__(self, letter, name, walkable) 
     self.move = Movements(self) 

p = MovablePiece("foo", "foo") 
for direction in ["up", "right", "down", "left"]: 
    getattr(p.move, direction)() 

Другого выбор будет кодирование что-то вроде этого:

class UpMovement(object): 

    def __init__(self, piece): 
     self.piece = piece 
     self.name = "up" 

    def move(self): 
     if self.piece.walkable: 
      print("up") 
     else: 
      print("piece not walkable to go up") 


class DownMovement(object): 

    def __init__(self, piece): 
     self.piece = piece 
     self.name = "down" 

    def move(self): 
     if self.piece.walkable: 
      print("down") 
     else: 
      print("piece not walkable to go down") 


class LeftMovement(object): 

    def __init__(self, piece): 
     self.piece = piece 
     self.name = "left" 

    def move(self): 
     if self.piece.walkable: 
      print("left") 
     else: 
      print("piece not walkable to go left") 


class RightMovement(object): 

    def __init__(self, piece): 
     self.piece = piece 
     self.name = "right" 

    def move(self): 
     if self.piece.walkable: 
      print("right") 
     else: 
      print("piece not walkable to go right") 


class Piece(object): 

    def __init__(self, letter, name, walkable): 
     self.letter = letter 
     self.name = name 
     self.walkable = walkable 


class Movements(object): 

    def __init__(self): 
     pass 


class MovablePiece(Piece): 

    def __init__(self, letter, name): 
     Piece.__init__(self, letter, name, True) 
     movements = [ 
      UpMovement(self), 
      DownMovement(self), 
      LeftMovement(self), 
      RightMovement(self) 
     ] 

     self.move = Movements() 
     for m in movements: 
      setattr(self.move, m.name, m.move) 


class StaticPiece(Piece): 

    def __init__(self, letter, name): 
     Piece.__init__(self, letter, name, False) 
     movements = [ 
      UpMovement(self), 
      DownMovement(self), 
      LeftMovement(self), 
      RightMovement(self) 
     ] 

     self.move = Movements() 
     for m in movements: 
      setattr(self.move, m.name, m.move) 

p1 = MovablePiece("foo1", "foo1") 

for name in ["up", "down", "left", "right"]: 
    getattr(p1.move, name)() 

p2 = StaticPiece("foo2", "foo2") 

for name in ["up", "down", "left", "right"]: 
    getattr(p2.move, name)() 

Конечно, вы могли бы overengineer вещи абстрагироваться классами здесь и там, что делает дизайн класса намного лучше и применение принципов SOLID дизайна. В любом случае вопрос был в основном в том, как приложить динамический материал к Pieces, так что вот возможное решение :)

+0

Спасибо за ваш ответ! К сожалению, первый вариант требует много строк от конечного программиста и может привести к путанице (я программирую библиотеку, поэтому конечный пользователь тоже программист): я бы очень хотел приложить создание пьесы с помощью создание движений. Во вторых вариантах создается много ненужных классов. Я, наконец, решил это, я отвечу на свой вопрос, если вы заинтересованы в том, как я это сделал. – joaquinlpereyra

+0

@joaquinlpereyra Рад, что вы нашли свой ответ. Я не предлагал много других решений проблемы, потому что вопрос не указывал никаких требований, и он был довольно открытым, так как вы можете видеть, что я догадался много. В следующий раз просто предоставьте [mcve] (http://stackoverflow.com/help/mcve) больше ограничений, и вы получите более качественные ответы. Удачи. – BPL

0

Вот как я, наконец, закончил это. Мне жаль, что этот пример не воспроизводится, но в миксе слишком много классов, и я думаю, что это просто повлияет на читаемость и понимание этой точной проблемы. Тем не менее, вы можете просмотреть код на github.

Примечательно, что мне не пришлось заставлять функции быть статическими, даже если они не принимают никаких параметров. Видимо, Python делает это для вас как-то.

class MovablePiece(Piece): 

    class Movements: 
     """A simple interface to represent the movements of the MovablePiece. 
     """ 
     def __init__(self, piece, movement_functions=None): 
      if movement_functions is None: 
       self.__default_movements(piece) 
      else: 
       self.__set_movements(movement_functions) 

     def __default_movements(self, piece): 
      def up(): return piece.move_to_tile(piece.surroundings[Direction.UP]) 
      def right(): return piece.move_to_tile(piece.surroundings[Direction.RIGHT]) 
      def down(): return piece.move_to_tile(piece.surroundings[Direction.DOWN]) 
      def left(): return piece.move_to_tile(piece.surroundings[Direction.LEFT]) 
      self.__set_movements([up, right, down, left]) 

     def __set_movements(self, movement_functions): 
      for movement_function in movement_functions: 
       setattr(self, movement_function.__name__, movement_function) 

    def __init__(self, letter, name, movements=None, walkable=False): 
     Piece.__init__(self, letter, name, walkable) 
     self.move = MovablePiece.Movements(self) 

    def _unsafe_move_to_tile(self, tile): 
     """Move the object in a certain direction, if it can: 
     That means: unlink the piece from its current tile and link it 
     to the new tile; unless there's a piece in the destiny tile already. 

     Return True if could move there, False is possition was already 
     ocuppied. 

     Can raise a PieceIsNotOnATileError if the piece hasn't been put on a 
     map prior to moving or a PieceIsNotOnThisBoardError if the piece 
     you're trying to move has an associated tile in another board, not 
     the one where the destinity tile is. 
     """ 
     if not self.home_tile: 
      raise PieceIsNotOnATileError 
     if self.home_tile.board is not tile.board: 
      raise PieceIsNotOnThisBoardError 

     if tile.piece is not None: 
      tile.piece.on_touch_do(touching_piece=self) 
      if not tile.piece.walkable: 
       return False 

     self.home_tile.piece = None 
     tile.piece = self 
     return True 

    def move_to_tile(self, tile): 
     if tile: 
      try: 
       return self._unsafe_move_to_tile(tile) 
      except (PieceIsNotOnATileError, PieceIsNotOnThisBoardError): 
       return False 
     else: 
      return False 
+0

FWIW: Вам не нужно было выполнять функции staticmethods, потому что вы добавляете их в экземпляр класса - 'self' - не класс. См. Принятый ответ на вопрос [_Добавление метода к существующему экземпляру объекта] (http://stackoverflow.com/questions/972/adding-a-method-to-an-existing-object-instance) для получения дополнительной информации. – martineau

Смежные вопросы