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.
