2016-08-13 67 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