Python – неправильная передача переменных между функциями класса?

Программа довольно понятная. Я начал играть с основами Python и действительно потерялся в этом. Я привык к C++ и прекрасной возможности передавать вещи по ссылке. Но в этом переменная класса, которую я пытаюсь изменить (Fighter.statHEALTH), не изменится, и я читал, что это потому, что целые числа неизменяемы, и он просто создает новый объект локально. Итак, как я могу применить изменение к исходной переменной? Я гуглил и гуглил, но безрезультатно. Я не хочу выполнять какой-то уродливый маневр, например, составлять список и передавать его, если мне это не нужно.

#python 3.2.2

#   Create a small test project to have combat between two entities.    #
#   Combat should include 3 different stats: statATK, statDEF, and statHEALTH.  #
#   The two entities should be of the same class.   #

class Fighter:
    def __init__(self):
        self.statHEALTH = 10
        self.statATK = 3
        self.statDEF = 3

    def attack(self, enemyhealth):
        enemyhealth = (enemyhealth - self.statATK)
        return enemyhealth

    def defend(self):
        statDEF += 1


def main():
    James = Fighter()
    Keaton = Fighter()
    while James.statHEALTH > 0:
        print("Do you wish to attack or defend?")
        print("1. Attack")
        print("2. Defend")
        choice = input()
        if choice == "1":
            James.attack(Keaton.statHEALTH)
            print("You did", James.statATK, "damage!") 
            Keaton.attack(James.statHEALTH)
            print("Keaton has", Keaton.statHEALTH, "health left.")
            print("Keaton did", Keaton.statATK, "damage!")
            print("You have", James.statHEALTH, "health left.")
        #elif choice == "2":
            #James.defend()
            #Keaton.attack(James.statHEALTH)

main()

person James Frederick Eversole    schedule 14.01.2014    source источник
comment
Китон должен атаковать Джеймса, а не здоровье Джеймса ;)   -  person zhangxaochen    schedule 14.01.2014


Ответы (4)


def attack(self, enemyhealth):
    enemyhealth = (enemyhealth - self.statATK)
    return enemyhealth

Это действительно сработает, если вы измените свой вызов на

Keaton.statHEALTH = James.attack(Keaton.statHEALTH)

.. так как вы return урон от атаки. Очевидно, это уродливо; повторяться нехорошо. Вместо этого вы можете сделать attack похожим на:

def attack(self, other):
    other.statHEALTH -= self.statATK

А потом просто сделай

James.attack(Keaton)

когда вы его называете.

person roippi    schedule 14.01.2014
comment
Это проблема. Очень просто, но я пропустил это. +1 - person aIKid; 14.01.2014
comment
На самом деле я не хотел оставлять обе эти вещи там, потому что я повторялся, возврат был частью решения, которое я нашел после поиска, и я надеялся, что оно сработает. - person James Frederick Eversole; 14.01.2014

Может быть, вы могли бы подумать об этом по-другому. В вашем примере Fighter.attack() просто возвращает значение здоровья врагов после атаки. Так что на самом деле это должен быть вызов метода для объекта противника. Вы можете добавить метод, уменьшающий здоровье бойца, когда его атакуют:

def attack(self, enemy):
    enemy.getAttacked(self.statATK)

def getAttacked(self, ATK):
    self.statHEALTH -= ATK
person desired login    schedule 14.01.2014

Попробуйте сделать:

while James.statHEALTH > 0:
    #print statements
    if choice == "1":
        the_attack = James.attack(Keaton)

Затем определите свои классы как:

class Fighter(object):
    def __init__(self):
        self.statHEALTH = 10
        self.statATK = 3
        self.statDEF = 3

    def attack(self,target):
        attack_details = target.takedamage(self.statATK)
        return attack_details

    def takedamage(self,dmg):
        modified_dmg = dmg-self.statDEF
        self.statHEALTH -= modified_dmg
        return modified_dmg

Это имеет дополнительное преимущество, заключающееся в том, что его легко расширять, например. вы можете добавить таблицу попаданий (for i in random.randint(1,100) if i < 20: #miss; elif 20 <= i < 80: #hit; elif 80<= i: #crit) или сопротивления для определенных элементов или добавить флаг, который позволяет вашему защитнику контратаковать в их функции takedamage (возможно, вызывая новую функцию getcountered для предотвращения бесконечного зацикливания).

person Adam Smith    schedule 14.01.2014

Проблема не в том, что вы не можете передавать вещи по ссылкам в Python. На самом деле вы всегда передаете значения по ссылке; Python никогда не копирует значения, если вы его об этом не попросите.

Или, точнее, вся эта терминология, основанная на C, в Python вводит в заблуждение.

В любом случае, когда вы делаете это:

James.attack(Keaton.statHEALTH)

Это не делает копию значения в Keaton.statHEALTH, оно передает ссылку на точно такое же значение. Итак, когда начинается attack, ваша переменная enemyhealth является именем для этого значения.

И затем вы делаете это:

enemyhealth = (enemyhealth - self.statATK)

enemyhealth - self.statATK возвращает новое значение, а затем вы привязываете это новое значение к имени enemyhealth. Это не влияет на любые другие имена для старого значения.

Есть два способа решить эту проблему.


Во-первых, вам фактически не нужно нужно что-либо видоизменять здесь. Вы уже делаете return enemyhealth в конце. Это означает, что вызывающая сторона может получить новое значение, которое вы вычислили. Так почему бы просто не использовать это?

Keaton.statHEALTH = James.attack(Keaton.statHEALTH)

И тогда все работает.


Конечно, в C++ вы можете изменять целочисленные значения. Это кажется немного глупым, если подумать — превращение числа 24 в число 19 нарушило бы большую часть математики и, вероятно, заставило бы вселенную перестать работать, а Python просто не настолько мощен.

Но вы можете легко создать тип, который может быть разумно видоизменен. Фактически, вы уже построили его: Fighter содержит целочисленное значение здоровья, но вместо этого его можно изменить, чтобы оно содержало другое. Итак, вы могли бы написать это:

def attack(self, enemy):
    enemy.health = enemy.health - self.statATK

А потом:

James.attack(Keaton)

Точно так же, как вызов старого метода с Keaton.health превращает enemyhealth в другую ссылку на тот же номер, вызов нового метода с Keaton превращает enemy в ссылку на тот же Fighter. Если бы вы просто переназначили новое значение enemy, это никак не повлияло бы на старое значение, на которое ссылается Keaton. Но если вы измените значение на месте, очевидно, что Keaton по-прежнему относится к этому уже измененному значению.

person abarnert    schedule 14.01.2014
comment
Спасибо, но вас опередили с ответом. Я все еще очень ценю это, и ваш ответ дал мне немного более глубокое понимание проблемы. - person James Frederick Eversole; 14.01.2014