【问题标题】:Python - Unexpected behaviour of class attributesPython - 类属性的意外行为
【发布时间】:2018-11-29 18:25:56
【问题描述】:

简单的文字编辑

代码:

class temp:
    attr1 = 0
    attr2 = []

t1 = temp()
t2 = temp()
t1.attr1 = 50
t1.attr2.append(50)
print(t1.attr1)
print(t1.attr2)
print(t2.attr1)
print(t2.attr2)

输出:

50
[50]
0
[50]

我只在attr2 对象t1 上调用了append,但append 更改了两个对象的attr2。如果attr2 是共享的(类属性),那么为什么attr1 的值对于t1t2 是不同的。什么可能导致了这种意外行为?

老问题

我正在为二十一点编写 Python 代码。我写的代码如下。

from random import randint
from IPython.display import clear_output

deck = ["S","D","C","H"]
class Player:
    cards = []
    total = 0
    amount = 0

    def __init__(self,money=0):
        self.amount = money

    def busted(self):
        return self.total > 21

    def showCards(self):
        for i in self.cards:
            print("| {}{} |".format(i%13,deck[i//13]),end = " ")
        print()

    def hit(self):
        no = randint(1,53)
        self.cards.append(no)
        if no % 13 == 1:
            if self.total + 11 > 21:
                self.total+=1
            else:
                self.total+=11
        else:
            self.total += (no%13 if no%13 <= 10 else 10)


dealer = Player(10000)
p1 = Player(0)
print("Welcome to BlackJack ....")
while True:
    try:
        p1.amount = int(input("Enter the amount you currrently have for the game"))
    except:
        print("invalid Value")
        continue
    else:
        break
Game = True

while Game:
    print(dealer.cards)
    print(p1.cards)
    dealer.hit()
    print(dealer.cards)
    print(p1.cards)
    print(dealer.total)
    print(p1.total)
    Game = False

这段代码的输出如下

Welcome to BlackJack ....
Enter the amount you currrently have for the game55
[]
[]
[45]
[45]
6
0

如您所见,我在dealer 对象上只调用了一次hit(),但它会将其附加到dealerp1 对象的cards 属性。但是total 属性不同。谁能解释导致这种意外行为的原因?

【问题讨论】:

  • 代替卡片,试试 self.cards
  • 具体在哪里定义?
  • 在__init__()中,添加self.cards = []
  • 是的,它成功了。但这并不能解释为什么total 不同而cards 相同
  • 您只添加到一个玩家,即,dealer.hit() 将数量添加到经销商实例而不是玩家实例。

标签: python python-3.x class object class-attributes


【解决方案1】:

当您执行t1.attr1 = 50 时,您将attr1 重新绑定到t1 对象的属性命名空间中的新值。它以前允许您访问绑定在类命名空间中的值,但是当您绑定一个新值时,您会从类中隐藏该值(仅适用于该实例)。

相反,当您执行t1.attr2.append(50) 时,您正在改变现有列表(绑定在类命名空间中,但通过所有实例可见),根本不会重新绑定变量。这就是您在t2 中看到变化的原因。变量t1.attr2t2.attr2 都是对同一个对象的引用(您可以使用is 运算符进行验证:t1.attr2 is t2.attr2)。

一般来说,如果您不希望所有实例共享列表或其他可变值作为类变量,通常不是一个好主意。但这并不是禁止的,因为有时您确实确实想要共享行为。

【讨论】:

  • 谢谢...您的回答清除了很多概念...您能解释一下什么是重新绑定吗?听起来像是将类属性复制到对象属性。
  • 重新绑定只是“重新分配”的另一种说法。 Python 中的名称与其他语言中的变量略有不同,因此有时使用不同的词很有用。 This article 非常适合理解 Python 的做事方式。您在此处遇到的额外复杂性是类属性在它们的实例中是可见的,但如果您分配回属性(使用实际分配,例如foo.attr = whatever),您将设置一个隐藏类属性的实例属性。
【解决方案2】:

我明白了你的要求。您需要将所有卡片与玩家卡片区分开来。所以,我建议不要将所有东西都命名为卡片,而是这样做:

class Player:
    all_cards = []
    total = 0
    amount = 0

并将__init__ 更新为:

def __init__(self, money=0):
    self.amount = money
    self.player_cards = []

在执行追加操作时,将其追加到all_cardsplayer_cards。无论如何,您只打印玩家卡片,您可以看到不同的卡片列表。

这里是完整的代码:

from random import randint
from IPython.display import clear_output

deck = ["S","D","C","H"]
class Player:
    all_cards = []
    total = 0
    amount = 0

    def __init__(self,money=0):
        self.player_cards = []
        self.amount = money

    def busted(self):
        return self.total > 21

    def showCards(self):
        for i in self.player_cards:
            print("| {}{} |".format(i%13,deck[i//13]),end = " ")
        print()

    def hit(self):
        no = randint(1,53)
        self.player_cards.append(no)
        self.all_cards.append(no)
        if no % 13 == 1:
            if self.total + 11 > 21:
                self.total+=1
            else:
                self.total+=11
        else:
            self.total += (no%13 if no%13 <= 10 else 10)


dealer = Player(10000)
p1 = Player(0)
print("Welcome to BlackJack ....")
while True:
    try:
        p1.amount = int(input("Enter the amount you currrently have for the game"))
    except:
        print("invalid Value")
        continue
    else:
        break
Game = True

while Game:
    print(dealer.player_cards)
    print(p1.player_cards)
    dealer.hit()
    print(dealer.player_cards)
    print(p1.player_cards)
    print(dealer.total)
    print(p1.total)
    Game = False

这是因为 list 是一个可变对象,并且它只在定义类时创建一次,这就是为什么在创建两个实例时它成为共享的原因。因此,为了解决这个问题,我们可以使用我上面提到的构造函数。当我们将列表放入构造函数时,每当实例化对象时,也会创建新的列表。

【讨论】:

    猜你喜欢
    • 2016-09-16
    • 2017-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-27
    • 1970-01-01
    • 2021-06-27
    相关资源
    最近更新 更多