【问题标题】:Can't change class variable with outside function in Python无法在 Python 中使用外部函数更改类变量
【发布时间】:2019-02-19 10:57:11
【问题描述】:

所以我制作了一个游戏并尝试为玩家创建一个库存,但我无法更改名为“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

【问题讨论】:

  • 这段代码太长了。您能否将其简化为最小工作示例(MWE)以便于诊断?
  • 我看到你将我的代码放入你的see_inventory 很酷,看看你正在处理的整个事情,一分钟后解决这个问题

标签: python python-3.x class variables


【解决方案1】:

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

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

如果目标是换班,需要换作业才能对班进行操作:

type(player).equipped_weapon = new_weapon_code

或明确命名类:

Player.equipped_weapon = new_weapon_code

无论哪种方式,del 都不是必需的;分配新值替换旧值,隐式删除对旧值的引用,就像del 明确所做的那样。使用del type(player).equipped_weapondel Player.equipped_weapon 专门删除类属性是完全合法的,但这是没有意义的;无论期望的行为是什么,对类或实例属性的简单赋值无论如何都会丢弃旧的引用。

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

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

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

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)

这仍然有很多有问题的选择(不一致的属性/属性命名;dicts 中的 奇怪 键名似乎已被选择以使显示更容易,但使所有非display 使用更烦人;将顶级函数用于仅作为实例方法有意义的事物等),但我保留了这些怪癖以使其成为现有类的替代品(但设置了所有每个玩家的属性在实例上,而不是类上,所有派生属性通过propertys 计算而不是静态设置,因为独立的静态属性会增加基本属性和派生属性不同步的几率)。

我保留了允许您在不提供任何参数的情况下创建 player 的行为,尽管 name 实际上应该是一个非可选参数(除非“Cavalex”是一个常见的名称,您可以放心地假设大多数如果不是所有玩家都有这个名字)。

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

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

【讨论】:

  • 我按照您说的进行了删除操作,但它打印的是旧武器而不是新武器,并且没有错误。我该怎么办?
  • @Cavalex:除了提示更换新武器之外,您发布的代码从不打印武器,所以我不知道您的问题出在哪里。只要您的所有代码始终与player.equipped_weapon 一起使用,它就可以正常工作;只有混合使用Player.equipped_weapon(类属性,未更改)和player.equipped_weapon(如果设置了实例属性,更改),您才会遇到问题。
  • 进一步检查,看起来Player.see_inventory 可能是您的问题。它正在查看Player.inv 的内容,其中包含定义类时ui_equipped_weapon 的值。但是更改equipped_weapon(在类或实例上)不会更改ui_equipped_weaponinv;属性之间没有 live 关联(inv 确实存储了对可变 ui_equipped_weapon 的引用,因此如果 ui_equipped_weapon 已就地更新,而不是重新分配,see_inventory 会改变,但是equipped_weapon 无关。
  • @Cavalex:我已经更新了我的答案,以解决您看似静态/未更改的武器状态的原因(即,您使用具有相同初始值但没有持续连接的多个属性,所以更新一个不会改变其他任何一个)。希望对您有所帮助。
  • 谢谢你!我会研究代码并玩弄它,这真的很有帮助!
猜你喜欢
  • 2011-06-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多