Home > database >  Can I implement an abstact method by a concrete method with a different name?
Can I implement an abstact method by a concrete method with a different name?

Time:01-17

i am experimenting with implementation of the monte-carlo-tree-search algorithm for the travelling salesman problem. In this context, I have created a class that without going into details looks somewhat like these:

class Position:

    def __init__(self, salesman, cities):
        self.salesman= salesman
        self.cities= cities

   def unvisited_cities(self):
       result = {}
       for name, city in self.cities.items():
            if city.not_visited():
                result[name] = city
       return result

    def travel_to_city(self, city_name):
        new_salesman = self.salesman.travel_to(city_name)
        return Position(new_salesman, self.cities)

Now, I want to declare this class as a child class for an abstact class:

class AbstractGamePosition(metaclass=abc.ABCMeta):

  @abc.abstractmethod
  def possible_actions(self):
      return

But here, I face a problem. Abstact class demands possible_actions method. In my concrete Position class, method that returns a set of all possible actions from is called unvisited_cities, because for the salesman problem only those cities can be the next targets that have not been visited yet. Is it possible to declare a Position as a child class of an AbstactGamePosition abstact class, and somehow let Python know that abstact method possible_actions is implemented in Position class by a concrete method unvisited_cities?

In naive language it could be something like this:

Position <- AbstactGamePosition:
   AbstactGamePosition.possible_actions = Position.unvisited_cities

However, Python does not have such a construction. Can this be somehow solved?

Obviously, this can be solved through an interface class:

class Interface(AbstactGamePosition):
   
   def __init__(self, position, concrete_method):
       self.position = position
       self._possible_actions = concrete_method
   
   def possible_actions(self):
       return self._possible_actions(self.position)

position = Position(salesman, cities)
interface = Interface(position, Position.unvisited_cities)

But this looks so sloppy that I find this disgusting.

CodePudding user response:

Given the information in the comments (that you don't want to have a hard dependency between Position and AbstractGamePosition), I think you're looking for the adapter pattern. Your Interface class is pretty close, though I agree that that level of reflection is unwarranted in this situation.

I would recommend created a small class alongside Position that is specifically for position, and that class can delegate to the original.

# (Type annotations provided for clarity; they are optional)
class PositionAdapter(AbstractGamePosition):

    def __init__(self, position: Position):
        self.position = position

    def possible_actions(self):
        return self.position.unvisited_cities()

Then, when you want to use a Position in a situation where an AbstractGamePosition is expected, you simply write

my_abstract_game_position = PositionAdapter(my_original_position)

and you can call possible_actions on my_abstract_game_position.

You will have to write one of these adapter classes for each class you want adapted to this abstract parent without a hard dependency. But it's not a lot of boilerplate (five lines of code, basically), so it's worth it if your goal is to keep all of these classes as loosely coupled as possible.

CodePudding user response:

Just implement the method by its expected and required name. You can call you actual implementation internally:

class Position(AbstractGamePosition):
    ...

    def unvisited_cities(self):
        ...
   
    def possible_actions(self):
        return self.unvisited_cities()

In fact, you can simply assign the unvisited_cities method to the required name, which has the same outcome:

class Position(AbstractGamePosition):
    ...

    def unvisited_cities(self):
        ...
   
    possible_actions = unvisited_cities

CodePudding user response:

ABCMeta literally only cares about the name being defined with an object that does not have an __isabtractmethod__ attribute set to True: that's it.

You can patch Position with

Position.possible_actions = Position.unvisited_cites

then register Position as a subclass of the abstract base class.

AbstractGamePosition.register(Position)

(If fact, registering a virtual subclass doesn't even require the name to be properly defined; you can do so without defining Position.possible_actions.)

(Assuming you can't change the definition of Position to inherit from the abstract class. If you can, @deceze's answer has you covered.)

  • Related