无法在Python中使用外部函数更改类变量

时间:2018-09-14 15:36:07

标签: python python-3.x class variables

所以我做了一个游戏,试图为玩家创建一个清单,但我不能更改一个名为“ equipped_weapon”的变量。我已经尝试了所有方法,或者得到了异常,或者在打印玩家的清单时显示了先前的武器。我也是初学者,所以如果我想念一些东西,请告诉我。

# Existing items
# Need a funtion to create a random item with random stats and a random name within a certain range.

yes_list = ["yes", "yeah", "sure", "why not"]

add_attack = "Attack:"
add_defense = "Defense:"
add_HP = "HP added:"
rarity = "Rarity"

weapon_rusty_sword = {
    "Name:": "Rusty Sword",
    add_attack: 1,
    add_defense: 0,
    rarity: 1
}

weapon_sword_wooden = {
    "Name:": "Wooden Sword",
    add_attack: 1.5,
    add_defense: 0,
    rarity: 1
}

weapon_sword_iron = {
    "Name:": "Iron Sword",
    add_attack: 2,
    add_defense: 0,
    rarity: 2
}

armor_chest_rusty_mail = {
    "Name:": "Rusty Mail",
    add_attack: 0,
    add_defense: 1,
    rarity: 1
}

armor_legs_adventurers = {
    "Name:": "Adventurer's Leggings",
    add_attack: 0,
    add_defense: 1,
    rarity: 1
}

armor_legs_rash_leggings = {
    "Name:": "Rash Leggings",
    add_attack: 0,
    add_defense: 0.5,
    rarity: 1
}

armor_head_rusty_cap = {
    "Name:": "Rusty Cap",
    add_attack: 0,
    add_defense: 0.5,
    rarity: 1
}
potion_hp = {
    "Name:": "HP Potion,",
    add_HP: 4
}

class Person:

    #global equipped_weapon
    equipped_weapon = weapon_rusty_sword
    ui_equipped_weapon = {"Equipped Weapon: {}".format(weapon_rusty_sword): ""}

    equipped_armor = {
    "Head Protection:": {"Name": armor_head_rusty_cap["Name:"], "Defense": armor_head_rusty_cap[add_defense]},
    "Chest Protection:": {"Name": armor_chest_rusty_mail["Name:"], "Defense": armor_chest_rusty_mail[add_defense]},
    "Legs Protection:": {"Name": armor_legs_rash_leggings["Name:"], "Defense": armor_legs_rash_leggings[add_defense]},
    }

    ATTACK = 1 + equipped_weapon[add_attack]
    DEFENSE = 1
    HP = 20

    gold = 10

    potions = {"Potions: ": [potion_hp["Name:"]]}
    ui_gold = {"Gold: ": gold}

    def __init__(self):
        self.name = "Cavalex"

    inv = [
        equipped_armor,
        ui_equipped_weapon,
        potions,
        ui_gold
    ]

    def see_inventory(self):
        for element in self.inv:
            for k, v in element.items():
                if type(v) == list:
                    print(k, ' '.join(v))
                else:
                    print(k, v)

#    def equip_weapon(self, new_weapon_code):
#        global equipped_weapon
#        eq_val = input(
#            "Do you want to equip this weapon? ->( {} )<-\nNote that your current weapon ( {} )will be discarded.".format(new_weapon_code["Name:"], self.equipped_weapon["Name:"]))
#        if eq_val.lower() in yes_list:
#            #del self.equipped_weapon
#            self.equipped_weapon = new_weapon_code
#            print("The weapon you had was discarded.")
#        else:
#            print("The new weapon was discarded.")





# See total amount of defense.
def defense_points():
    return sum(value["Defense"] for key, value in player.equipped_armor.items())


def add_gold(amount):
    player.gold += amount

# Adding to inventory
def new_potion_to_inv(potion):
    player.potions["Potions: "].append(potion["Name:"])


def equipp_weapon(new_weapon_code):
    # global player.equipped_weapon
    eq_val = input(
        "Do you want to equip this weapon? ->( {} )<-\nNote that your current weapon ( {} )will be discarded.".format(
            new_weapon_code["Name:"], player.equipped_weapon["Name:"]))
    if eq_val.lower() in yes_list:
        del player.equipped_weapon
        player.equipped_weapon = new_weapon_code
        print("The weapon you had was discarded.")
    else:
        print("The new weapon was discarded.")

player = Person()

# game loop
while True:
    print("Your name: ", player.name)
    player.see_inventory() # Can't put print this function, else will print "None" in the end.
    print("\nThis is your total armor defense: {}".format(defense_points()))
    print()
    new_potion_to_inv(potion_hp)
    player.see_inventory()
    print(player.ATTACK)
    print()
    print("Now we are changing your weapon.")
    equipp_weapon(weapon_sword_iron)
    print()
    print(player.ATTACK)
    player.see_inventory()



    break

这是输出:

C:\Users\mateu\AppData\Local\Programs\Python\Python37-32\python.exe C:/Users/mateu/PycharmProjects/Trabalhos_Python/TESTING_THINGS/testing_armor_without_weapons_and_armor.py
Your name:  Cavalex
Head Protection: {'Name': 'Rusty Cap', 'Defense': 0.5}
Chest Protection: {'Name': 'Rusty Mail', 'Defense': 1}
Legs Protection: {'Name': 'Rash Leggings', 'Defense': 0.5}
Equipped Weapon: {'Name:': 'Rusty Sword', 'Attack:': 1, 'Defense:': 0, 'Rarity': 1} 
Potions:  HP Potion,
Gold:  10

This is your total armor defense: 2.0

Head Protection: {'Name': 'Rusty Cap', 'Defense': 0.5}
Chest Protection: {'Name': 'Rusty Mail', 'Defense': 1}
Legs Protection: {'Name': 'Rash Leggings', 'Defense': 0.5}
Equipped Weapon: {'Name:': 'Rusty Sword', 'Attack:': 1, 'Defense:': 0, 'Rarity': 1} 
Potions:  HP Potion, HP Potion,
Gold:  10
2

Now we are changing your weapon.
Do you want to equip this weapon? ->( Iron Sword )<-
Note that your current weapon ( Rusty Sword )will be discarded.yes
Traceback (most recent call last):
  File "C:/Users/mateu/PycharmProjects/Trabalhos_Python/TESTING_THINGS/testing_armor_without_weapons_and_armor.py", line 158, in <module>
    equipp_weapon(weapon_sword_iron)
  File "C:/Users/mateu/PycharmProjects/Trabalhos_Python/TESTING_THINGS/testing_armor_without_weapons_and_armor.py", line 139, in equipp_weapon
    del player.equipped_weapon
AttributeError: equipped_weapon

Process finished with exit code 1

1 个答案:

答案 0 :(得分:3)

问题是您将equipped_weapon设为类属性,而不是实例属性。 del instance.attribute 删除实例属性,甚至不查找类属性。

如果目标只是更改实例,则可以完全删除del;下一行将添加(如果不存在实例属性,则将class属性遮盖)或替换(如果已存在实例属性)实例的特定武器。

如果目标是更改班级,则需要更改作业以对班级进行操作:

type(player).equipped_weapon = new_weapon_code

或明确命名该类:

Player.equipped_weapon = new_weapon_code

无论哪种方式,del都是不必要的;分配新值将替换旧值,并以与del显式相同的方式隐式删除对旧值的引用。进行del type(player).equipped_weapondel Player.equipped_weapon来专门删除class属性是完全合法的,但这是没有意义的;不管所需的行为是什么,简单地将其分配给class或instance属性都将丢弃旧的引用。

更新:如您所述,删除del可以防止异常,但是您的输出(来自see_inventory)永远不会改变。这是因为see_inventory在查看Player.inv,而后者又包含了对Player.ui_equipped_weapon原始值的引用。问题是,虽然ui_equipped_weaponequipped_weapon是基于相同的默认武器进行初始化的,但它们并未彼此同步。更改一个对另一个没有影响(因此,对类或实例更改equipped_weapon不会影响inv,因此see_inventory永远不会更改)。

真的,这里的解决方案是建立一个理智的课程。您的所有类属性都没有道理;从逻辑上讲,它们都是实例属性,应该这样定义。在少数情况下,它们只是另一个属性的面向方便的转换;在这种情况下,它们根本不应该是属性,而应该是property,它们会根据另一个属性动态重新计算其值,从而防止它们不同步。

这是一个非常快速的(未经测试,可能有小的错别字)重写,它将所有合理的属性移动到实例,并将其余的属性转换为只读属性,这些属性从另一个实例属性获取其值:

import copy

# Factored out to avoid repetition
def clean_armor(armor):
    return {k.rstrip(':'): v
            for k, v in armor.items() if k in {'Name:', 'Defense:'}}

class Person:
    DEFAULT_WEAPON = weapon_rusty_sword
    DEFAULT_ARMOR = {
        "Head Protection:": clean_armor(armor_head_rusty_cap),
        "Chest Protection:": clean_armor(armor_chest_rusty_mail),
        "Legs Protection:": clean_armor(armor_legs_rash_leggings),
    }

    def __init__(self, name="Cavalex",
                 equipped_weapon=DEFAULT_WEAPON, equipped_armor=DEFAULT_ARMOR,
                 base_attack=1, base_defense=1,
                 hp=20, gold=10,
                 potions=(potion_hp["Name:"],)):
        self.name = name
        # deepcopy is defensive (so changing defaults doesn't retroactively
        # alter existing instance); can be removed if you guarantee defaults
        # won't be changed, or you want changes to defaults to affect
        # Players still wielding default items
        self.equipped_weapon = copy.deepcopy(equipped_weapon)
        self.equipped_armor = copy.deepcopy(equipped_armor)
        self.base_attack = int(base_attack)
        self.base_defense = int(base_defense)
        self.HP = int(hp)
        self.gold = int(gold)
        # potions only used as dictionary, but it's silly to store it as one
        # Just store list of potion names in protected attribute,
        # property can construct the expected dict on demand
        self._potions = list(potions)

    @property
    def ATTACK(self):
        return self.base_attack + self.equipped_weapon[add_attack]

    @property
    def DEFENSE(self):
        return self.base_defense + sum(armor['Defense'] for armor in self.equipped_armor.values())

    @property
    def potions(self):
        return {"Potions: ": self._potions}

    @property
    def ui_gold(self):
        return {"Gold: ": self.gold}

    @property
    def ui_equipped_weapon(self):
        return {"Equipped Weapon: {}".format(self.equipped_weapon): ""}

    @property:
    def inv(self):
        return [
            self.equipped_armor,
            self.ui_equipped_weapon,
            self.potions,
            self.ui_gold,
        ]

    def see_inventory(self):
        for element in self.inv:
            for k, v in element.items():
                if isinstance(v, list):
                    print(k, ' '.join(v))
                else:
                    print(k, v)

这仍然有很多可疑的选择(属性/属性命名不一致; dict中的奇怪键名似乎是为了简化显示而选择的,但要使所有非展示广告会带来更多烦人的事情;使用顶级功能处理仅作为实例方法等有意义的内容),但我保留了这些怪癖,以使其成为您现有课程的直接替代品(但每个玩家均适用)属性是在实例上设置的,而不是在类上设置的,并且所有通过property计算而不是静态设置的派生属性,因为独立的静态属性会增加基本属性和派生属性不同步的几率。

我保留了允许您创建player而不提供任何参数的行为,尽管name实际上应该是一个非可选参数(除非“ Cavalex”是一个通用名称,可以安全地假设大多数(如果不是所有)玩家都使用该名称。

我意识到,当您可以将数据从属性复制到派生属性时,拼出__init__并定义property似乎有点繁琐,但实际上是可以使用的代码以一种有意义的方式,没有极端的重复(以及错别字和逻辑错误的伴随风险),了解行为等,这是唯一有意义的方法; class是类属性的99%(所有方法都有效地忽略了每个实例状态)也可能只是一堆全局变量和顶级函数,而不是class

创建类的原因是允许您创建具有不同属性但共享行为的类的许多实例;至少在Python中,多范式语言设计意味着单例模式很少(如果有的话)(并且使用在定义时定义的类级别状态删除了类),通过使用纯类级别的状态使类成为准单例使类毫无意义。模式的唯一优势是,将单例的初始化推迟到需要时进行。)