I'm trying to make a text-based RPG. I have some code like:
heroes.py:
class Hero():
def __init__(self):
pass
def Attack(self, target):
# ...
def TakeDamage(self, amount):
# ...
monsters.py:
class Monster():
def __init__(self):
pass
def Attack(self, target):
# ...
def TakeDamage(self, amount):
# ...
The whole file structure looks like this:
|__ backend
__init__.py
monsters.py
heroes.py
MainGame.py
Let's say I want Monster
and Hero
to access each other's Attack
and TakeDamage
functions, for example:
class Monster():
def __init__(self):
pass
def Attack(self, target):
# ...
def TakeDamage(self, amount, target:Hero):
damage = # damage calculation here
target.TakeDamage(damage)
How can I do this? So far I've tried:
- Importing each other (e.g.
from .monsters import Monster
) in their respective files - this gives me an error readingImportError: cannot import name 'Monster' from partially initialized module 'backend.monsters' (most likely due to a circular import)
.
CodePudding user response:
Broadly speaking, classes don't have methods; instances do. The purpose of a class is to define a data type. You don't need to see that definition, in Python, in order to use instances.
Consider the code in the Monster
class:
def TakeDamage(self, amount, target:Hero):
damage = # damage calculation here
Hero.TakeDamage(damage)
(I will ignore for the moment that the logic here probably doesn't make that much sense.)
Writing :Hero
is a hint - to the person reading the code, and possibly to third-party tools; Python itself does not care - that target
will be an instance of the Hero
class.
We want to call a method on that instance, not on the class. The class doesn't have a TakeDamage
method; it only has a TakeDamage
function, which is used to create the method when we look it up via an instance.
Therefore, the code should not say Hero.TakeDamage(damage)
. It should say target.TakeDamage(damage)
, because target
is the name of the Hero
instance whose method we will call.
To do this, we do not require the definition of the Hero
class. monsters.py
should not import
anything to make this work.
When the code is running, at the moment that the method call is attempted, Python will check whether the thing that is named target
has a TakeDamage
attribute. When it doesn't find one directly attached to the instance, it will look in the class, and find the TakeDamage
function in that class. It will automatically create a method from that. Then it will check that the TakeDamage
that it got from this process is callable (spoiler: it is, because it's a method), and call it.
CodePudding user response:
Here is a way for me to design this game:
├── backend
│ ├── __init__.py
│ ├── character.py
│ ├── heros.py
│ └── monsters.py
└── main.py
character.py
is the common class between hero and monster.
# character.py
class Character:
def __init__(self):
self.health = 100
def attack(self, target):
target.take_damage(1)
def take_damage(self, amount):
self.health -= amount
def __repr__(self):
return (
f"{self.__class__.__name__}("
f"health={self.health!r}"
f")"
)
# heros.py
from .character import Character
class Hero(Character):
pass
# monsters.py
from .character import Character
class Monster(Character):
def attack(self, target):
target.take_damage(5)
# main.py
from backend.heros import Hero
from backend.monsters import Monster
h = Hero()
m = Monster()
h.attack(m)
m.attack(h)
print(h)
print(m)
Output:
Hero(health=95)
Monster(health=99)
The key here is happening inside the attack
method: it calls target.take_damage()
with the amount.
Note that heros.py
does not import monsters.py
and vice versa. Doing so could result in circular reference, which is messy.