pythonoopabstract-base-class

Python: AttributeError while trying to create child from ABC


I am trying to refactor some old code from a beginner CS course I took in college

I've noticed a lot has changed with python since then (2017)

I also haven't been able to find any very informative online tutorial for writing and using abstract base classes with python (except the docs), So I have very little to go off of for what is the correct way to write and use ABCs.

Currently I'm having two problems, one is more explicit:

Traceback (most recent call last):
  File "Hero.py", line 10, in <module>
    from Enemy import Enemy
  File "C:\Users\Ben\vscodeSCM\CS171Assignments\Zelda_CLI_game\Enemy.py", line 15, in <module>
    class Enemy(metaclass = abc.ABCMeta):
  File "C:\Users\Ben\vscodeSCM\CS171Assignments\Zelda_CLI_game\Enemy.py", line 32, in Enemy
    def __init__(self):
AttributeError: 'function' object has no attribute '__bases__'

which comes up when I try to run 'Hero.py' a script

from Enemy import Enemy

class Hero(Enemy):
    '''Class Level Docstring: Hero Object has six attributes,
    most of which are nessecary for gameplay mechanisms.

    Name is purely descriptive (no impact on game implementation details)
    Therefore, name is the only mutable value, able to be set via constructor.
    '''


    def __init__(self,name):
        self._name = name
        self.__health = 200
        self.__defense_mode = False
        self.__bombs = 20
        self.__elixers = 5
        self.__arrows = 10
    
    def __repr__(self):
        '''Returns Hero attributes. 
    (health and defense_mode are implementation details)'''
        return "Health: {} / 200\n\
    \r\rBombs: {} / 20\n\
    \r\rElixers: {} / 6\n\
    \r\rArrows: {} / 10".format(self.__health, self.__bombs, self.__elixer, self.__arrows)

    
    def __str__(self):
        '''Returns Hero description'''
        return "{}, a young warrior, clad in green".format(self.__name)
    
    
    def health_bar(self):
        '''Returns Hero health points vs. max health points.'''
        return "{}/200".format(self.__health)
    
    ###Basic. Defense and Special attack for hero, overriden from Enemy.
    def basic_attack(self, enemy):
        '''Offensive attack that leaves Hero vulnerable.'''
        self.__defense_mode = False
        enemy.do_Damage(50)
    
    def basic_name(self):
        '''Provides name for Hero basic attack.'''
        return "used the Master sword"
    
    def defense_attack(self, enemy):
        '''Parry attack that bolsters hero defense
        while also doing small amount of damage to enemy.'''        
        self.__defense_mode = True
        enemy.do_Damage(20)
    
    def defense_name(self):
        '''Returns the name for Hero defense attack'''
        return "shield strike"
    
    def special_attack(self, enemy):
        '''A Powerful attack that does higher amount of damage 
    and also swaps defense mode. If defense mode is true,
    does the standard amount of damage. If false, does 
    greater damage but also hurts the Hero.
        '''
        if self.__bombs:
            if self.__defense_mode:
                self.__defense_mode = False
                enemy.do_damage(75)
                return "The bomb had a Direct Hit.\nYour enemy recoils"
            else:
                self.__defense_mode = True
                enemy.do_damage(110)
                self.__health -= 20
                return "The bomb was very powerful.\n\
            Your enemy was badly injured,\n\
            but you were damaged as well by the shrapnel"
        else:
            return "no bombs remaining.\nCannot use Special attack."

    def special_name(self):
        '''Returns the name of the special attack.'''
        return "Used a bomb"
    
    #Hero unique moves- Arrow and Elixer
    def use_arrow(self, enemy):
        '''Projectile attack:
        highly effective, has no effect on defense mode.
        but limited availability/ uses- 10 available arrows per game.'''        
        enemy.do_Damage(95)
        self.__arrows= self.__arrows - 1
    
    def arrow_name(self):
        '''Returns the name of arrow attack.'''
        return "{} fired an arrow".format(self.__name)
    
    def arrow_count(self):
        '''Returns the number of arrows remaining vs. maximum.'''
        return "{} / 10".format(self.__arrows)
    
    def elixer(self):
        '''Replenishes half of the Heros max health point.
    but limited uses, also turns defense mode to false.
        '''
        self.__defense_mode = False
        self.__health + 100
        self.__elixers - 1
    
    def elixer_name(self):
        '''Returns the name of the elixer move.'''
        return "drank an elixer"
    
    def elixer_count(self):
        '''Returns the amount of remaining elixers vs max elixers.'''
        return "{} / 5".format(self.__elixers)

    def do_damage(self, damage):
        '''
    subtracts damage argument from Hero health.
    If Defense mode is true, cuts value of damage argument in half.
        '''
        if(self.__defense_Mode):
            self.__health -= damage // 2
        else:
            self.__health -= damage

    def reset_health(self):
        '''Sets the health points of Hero to maximum.'''
        self.__health = 200

    def reset_ammo(self):
        '''Sets all the ammunititions to the max/initial value'''
        self.__arrows = 10
        self.__elixers = 5


if __name__ == "__main__":
    print("Running in standby mode")

and here is Enemy abstract base class

import abc
from abc import ABCMeta

#cant figure out how to inherit from metaclass = ABCe
class Enemy(metaclass = abc.ABCMeta):
    '''Class Level Docstring: creates signitures/ abstract methods
    for init (constructor), __str__ (string repr.) and all helper
    methods for gameplay.

    NOTE: getter methods are retained from version 1, despite
    this class adhering to the Descriptor protocol/ being a 
    new style class.
    
    This is done in order to facilitate documentation. 
    When users implement the enemy class they will understand 
    how use of their object attributes makes gameplay/ battle possible.
    '''

    version = 2.0

    @abc._abc_init
    def __init__(self):
        '''Construct enemy by passing in name, desc & health as args.'''
        pass
    
    @abc.abstractmethod
    def __str__(self):
        '''Returns all attributes/properties of the enemy.'''
        return NotImplemented

    @property
    @abc.abstractmethod
    def get_name(self):
        '''Returns the enemy name.'''
        return NotImplemented
    
    @property
    @abc.abstractmethod
    def get_description(self):
        '''Returns a short message describing
    the enemy and providing any nessecary information.
        '''
        return NotImplemented
    
    @property
    @abc.abstractmethod
    def get_health(self):
        '''An enemy with 0 health is defeated.'''
        return NotImplemented
    
    #gamified versions of setters/mutator methods

    @abc.abstractmethod
    def do_damage(self, damage):
        '''Positive numeric argument does damage,
        and negative numeric argument heals enemy.'''
        return NotImplemented
    
    @abc.abstractmethod
    def reset_health(self):
        '''Returns health to starting HP.'''
        return NotImplemented

    # the attacks include: basic attack, defensive attack, special attack
    # each of these has a corresponding method for returning the attack name.  
    
    @abc.abstractmethod
    def basic_attack(self, enemy):
        '''A less powerful, more commonly used attack.'''
        return NotImplemented   
    
    @abc.abstractmethod
    def basic_name(self):
        '''returns basic attack name.'''
        return NotImplemented

    @abc.abstractmethod
    def defense_attack(self, enemy):
        '''Defensive move that counters or blocks an opponent
    attack. some may infrequently do damage to opponent.
        '''
        return NotImplemented

    @abc.abstractmethod
    def defense_name(self):
        '''Returns defense attack name.'''
        return NotImplemented
    
    @abc.abstractmethod
    def special_attack(self, enemy):
        '''Special attack which has limited availability, that may deal 
    extra damanage, have increased accurarcy, or some other desired effect.
        '''
        return NotImplemented
    
    @abc.abstractmethod
    def special_name(self):
        '''Returns name of special attack,'''
        return NotImplemented

The other issue is intellisense or vscode having issues finding the files for my imports, despite them all being in the same directory. It underlines the first occurence of 'Enemy' in from Enemy import Enemy

Is it a problem that my class name and the file containing the class have the same title?

I bring up this second issue, with the imports, because I suspect it may be the cause of the first error. This import line was causing errors earlier, until I edited some things in my preferences: python language specific configuration files ( commented out the line "python.jediEnabled" : false, because of a solution from a different stackoverflow post where a user was having a similar problem) After saving these changes, the Hero.py and Enemy.py scripts worked momentarily, but soon after I started getting this AttributeError mentioned above.


Solution

  • I think the line with the issue is

    @abc._abc_init
    

    over your init function in Enemy- where did you see you need to do that? As a general note, things that start with underscores are considered private and should not be used when imported from other packages.