Python类意外行为

2024-04-20 06:11:57 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在用OOP开发一个纸牌游戏只是为了练习,我在我写的东西里有一些奇怪的行为。当我使用clear方法清空双手时,所发生的是单个的手显示它们是空的,但是当我查看hands变量(显示两只手)时,它们不是空的。我的问题是为什么,哈哈?我将把代码放在下面,结果放在下面。谢谢你的帮助。你知道吗

import random

class a:
    my_hand = []
    other_hand = []
    hands = [my_hand, other_hand]

    def h(self):
        for rounds in range(5):
            for hand in a.hands:
                hand.append(rounds)

    def shuffle(self):
        random.shuffle(a.my_hand)
        random.shuffle(a.other_hand)

    def clear(self):
        a.my_hand = []
        a.other_hand = []


x = a()
x.h()
x.shuffle()


x.hands
[[1, 0, 4, 2, 3], [3, 4, 2, 0, 1]]

x.clear()
[[1, 0, 4, 2, 3], [3, 4, 2, 0, 1]]

x.my_hand
[]

x.other_hand
[]

x.hands
[[1, 0, 4, 2, 3], [3, 4, 2, 0, 1]]

Tags: inself游戏formydefrandomoop
3条回答

您正在创建新的空列表,但是hands仍然引用旧列表。你知道吗

您可以简单地确保hands引用新的空列表。你知道吗

def clear(self):
    a.my_hand = []
    a.other_hand = []
    a.hands = [a.my_hand, a.other_hand]

或者可以通过删除所有元素来修改列表本身。你知道吗

def clear(self):
    del a.my_hand[:]
    del a.other_hand[:]

仅供参考,所有函数都定义为成员方法,但变量始终是静态的。您可能希望引用self而不是a,或者使您的方法成为静态的(使用@staticmethod)。你知道吗

a类中,my_handother_handhands变量都是属性(即静态属性),而不是实例属性。你知道吗

同样在clear方法中,您重新分配了my_handother_hand,但是hands仍然引用旧的list,因此它的内容没有改变。如果您使用的是python3,那么应该使用listclear()方法:my_hand.clear()other_hand.clear(),那么hands将引用两个空的list。 在python2上,您应该执行del my_hand[:]。你知道吗

如果要分配实例属性,必须执行self.attribute = value。 为此,赋值应该放在类的构造函数__init__方法中。你知道吗

就目前的代码而言,如果您创建两个a实例,那么它们将共享双手,这可能是您所不希望的。你知道吗

正确的代码是:

import random

class ClassesUseCamelCase(object):
    # if you are using python2 do *not* forget to inherit object.
    def __init__(self):
        self.my_hand = []
        self.other_hand = []
        self.hands = [self.my_hand, self.other_hand]

    def init_hands(self):
        for round in range(5):
            for hand in self.hands:
                hand.append(round)
        # or simpler:
        # for hand in self.hands:
        #     hand.extend(range(5))

    def shuffle(self):
        random.shuffle(self.my_hand)
        random.shuffle(self.other_hand)

    def clear(self):
        self.my_hand.clear()    # or del self.my_hand[:] in python2
        self.other_hand.clear()

使用iPython的示例输出:

In [2]: inst = ClassesUseCamelCase()

In [3]: inst.init_hands()

In [4]: inst.shuffle()

In [5]: inst.hands
Out[5]: [[3, 2, 4, 0, 1], [3, 1, 4, 0, 2]]

In [6]: inst.clear()

In [7]: inst.hands
Out[7]: [[], []]

请注意,它可用于多个实例:

In [9]: inst.init_hands()

In [10]: other = ClassesUseCamelCase()

In [11]: other.init_hands()

In [12]: inst.hands, other.hands
Out[12]: ([[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]])

In [13]: inst.shuffle(); other.shuffle()
    ...: 

In [14]: inst.hands
Out[14]: [[0, 1, 3, 2, 4], [1, 4, 0, 3, 2]]

In [15]: other.hands
Out[15]: [[1, 4, 2, 0, 3], [1, 4, 3, 2, 0]]

您的代码将在两个实例之间共享hands。你知道吗


为了避免写太多的评论,我在这里对发生的事情做了更多的解释。你知道吗

首先,在代码中my_handother_hand属性。为什么这很重要?这很重要,因为类属性在实例之间共享:

In [1]: class MyClass(object):
   ...:     my_hand = []
   ...:     other_hand = []
   ...:     hands = [my_hand, other_hand]
   ...:     

In [2]: instance_one = MyClass()

In [3]: instance_two = MyClass()

In [4]: instance_one.my_hand.append(1)

In [5]: instance_two.my_hand   # ops!
Out[5]: [1]

调用clear修改类属性,从而修改使用它们的所有实例。这通常是你做的而不是你想要的。你知道吗

t实例没有任何其他实例属性,这意味着所有实例实际上是相等的;它们的行为和提供的数据都相同。唯一的区别是他们的身份。如果您不打算使用标识,那么拥有一个类和多个实例是没有用的,因为您可以简单地使用单个对象。你知道吗

如果希望实例拥有自己的数据,独立于其他实例的数据,则必须使用实例属性。你知道吗

关于del语句。正如我在评论中已经说过的del有两种不同的用法。语句的full语法是:

del sequence, of, del_targets

其中“sequence,of,del_targets”是一个逗号分隔的列表,我称之为del_targets。根据del_target,行为会发生变化。你知道吗

如果它是一个大于del的标识符,则从作用域中删除引用,减少标识符引用的对象的引用计数。你知道吗

例如:

a = b = []
# Now that empty list has two references: a and b
del b    # now it has only one reference: a
print(b)   # This raises a NameError because b doesn't exist anymore
del a      # Now the empty list doesn't have references

如果一个对象没有引用,它将被销毁,因此在del a之后,空列表将被解释器销毁。你知道吗

目标也可以是“下标”,即像name[key-or-index]name[a:slice](或name[start:stop:step])这样的表达式。 切片语法(带冒号的语法)用于指定一系列索引:

In [17]: numbers = list(range(10))  # [0, 1, ..., 9]

In [18]: numbers[::2], numbers[1::2], numbers[2:7:3]
Out[18]: ([0, 2, 4, 6, 8], [1, 3, 5, 7, 9], [2, 5])

当将del语句与这样的表达式一起使用时,python调用对象的__delitem__方法,传递索引或切片。这意味着:

del numbers[:]

意思是:删除列表numbers中与片:中的索引相对应的所有元素。因为切片:意味着“序列中的所有索引”,所以结果是清空列表。请注意,它不会从列表中删除引用。它只作用于它的元素。你知道吗

您可以使用以下方法获得相同的效果:

numbers[:] = []

这告诉python用[]中的元素替换片:对应的元素序列。因为:表示“所有元素”,而[]是空的,所以效果是从列表中删除所有元素。 这种语法在引擎盖下调用list.__setitem__而不是list.__delitem__,但结果是相同的。 但是您也可以这样做:numbers[:] = [2],然后numbers的所有元素都将被删除并且将插入2,从而产生列表[2]。你知道吗

这两种方法都有效,但是我更喜欢del语法,因为它可以明确您的意图。 当你读到:

del something[:]

你知道这个语句会删除一些东西。然后您看到了下标[:],您知道它将删除something中的元素,而不是引用something。但是当你看到:

something[:] = []

首先你想,好吧,这是一项任务。然后您会看到[:],并且您知道您正在“覆盖”列表的内容。然后看右边的[],我们将用一个空列表覆盖元素。。。最后,您知道该语句将从列表中删除所有元素。你知道吗

作为OOP的练习,你的代码很奇怪。。。您正在实例方法中使用类成员。方法中的类实例名为self,而不是a。将代码更改为

 def clear(self):
     self.hand = []
     self.other_hand = []

对代码的其他部分也一样。你知道吗

然而,您面临的问题是另一个:当重新分配a.handa.other_hand时,您正在创建两个新的空数组,因此a.hands一直指向在h中创建的旧数组。你知道吗

将代码更改为

a.hand[:] = []
a.other_hand[:] = []

或者更惯用一点

del a.hand[:]
del a.other_hand[:]

会解决这个问题。你知道吗

在Python中[:]语法表示“的所有元素”,还允许切片(例如a.hand[1:-1]a.hand中除first和last之外的元素列表)。你知道吗

相关问题 更多 >