2016-08-13 20 views
-2

我有一個名爲'Movable Piece'的類。當然,我希望這個班級的每個實例都可以移動。爲此,我認爲另一個名爲'運動'的課程會很好,並且可以重複使用以防我需要其他東西移動。此外,我很喜歡my_piece.move.up在代碼中的外觀。爲Python中的實例動態創建方法

當我意識到需要動態地嘗試設置由Piece實例化的Movements類的實例的方法時,問題就出現了,因爲移動該部分的功能也可以由用戶定義。我怎樣才能做到這一點?我認爲該代碼將闡明我想要做的事情。

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()'應該可以正常工作。問題可能是函數沒有被定義爲接受「自我」第一個參數,所以它們不是合適的方法。 – martineau

+0

它沒有。我會附上回溯給出的錯誤。 – joaquinlpereyra

+0

@martineau當我嘗試使它們成爲靜態方法時,我添加了回溯函數,它不容易獲取參數:) – joaquinlpereyra

回答

1

恕我直言,你應該提供一個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的東西在這裏和那裏抽象類,使得類設計更好和應用的設計原則。無論如何,這個問題基本上是如何將動態東西附加到件,所以這裏是一個可能的解決方案:)

+0

謝謝你的迴應!不幸的是,第一個選項需要來自最終程序員的很多行,並且可能容易導致混淆(我正在編程一個庫,所以最終用戶也是程序員):我真的很想將Piece的創建與運動的創造。第二個選項創建了很多不必要的類。我終於解決了它,如果你有興趣瞭解我是如何做到的,我會回答自己的問題。 – 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