Python类意外行为

时间:2013-11-09 06:31:59

标签: python class

我正在OOP开发一款卡片游戏,仅仅是为了练习,到目前为止,我有一些奇怪的行为。 。当我使用clear方法清空双手时,会发生的事情是单手显示它们是空的,但是当我看到手变量(显示双手)时,它们不是空的。我的问题是为什么,哈哈?我将下面的代码放在下面,结果如下。 。 。提前感谢任何帮助。

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]]

3 个答案:

答案 0 :(得分:3)

a课程中,my_handother_handhands变量都是 class 属性(即 static > attributes),而不是 instance 属性。

同样在您的clear方法中,您重新分配了my_handother_hand,但hands仍然引用了旧的list,因此其内容并非如此改变。如果您使用的是python3,则应使用clear()的{​​{1}}方法:listmy_hand.clear(),然后other_hand.clear()将引用两个空hands }}秒。 在python2上,你应该list

如果要分配实例属性,则必须执行del my_hand[:]。 为了做到这一点,应该将赋值放在self.attribute = value方法中,该方法是类的构造函数。

由于它目前代表您的代码,如果您创建两个__init__实例,那么它们将共享手,这可能是您想要的。

正确的代码是:

a

使用iPython输出示例:

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()

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

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]]


我在这里更多地解释发生了什么,以避免写太多评论。

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

other_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声明。正如我在评论中已经说过del有两种不同的用法。该语句的完整语法为:

del

其中“sequence,of,del_targets”是逗号分隔的列表,我将其称为del 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 aname[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]) 语句时,python调用对象的del方法,传入索引或切片。这意味着:

__delitem__

表示:删除与切片del numbers[:] 中的索引对应的列表numbers的所有元素。由于切片:表示“序列中的所有索引”,因此结果将清空列表。请注意,它从列表中删除引用。它只对其元素起作用。

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

:

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

两者都有效,但我更喜欢[2]语法,因为它明确了你的意图。 当你读到:

del

您知道该声明将删除某些内容。然后,您会看到下标del something[:] ,并且您了解它将从[:]而不是参考something中删除元素。但是当你看到:

something

首先你想,好吧这是一个任务。然后你看到something[:] = [] 并且你明白你正在“覆盖”列表的内容。然后你看右边看[:],所以我们要用空列表覆盖元素......最后你明白语句将只删除列表中的所有元素。

答案 1 :(得分:1)

您正在创建新的空列表,但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)。

答案 2 :(得分:1)

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

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

并对代码的其他部分执行相同的操作。

您遇到的问题是另一个问题:重新分配a.handa.other_hand时,您正在创建两个新的空数组,因此a.hands会一直指向{{1}中创建的旧数组}}

将代码更改为

h

或更惯用的

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

将解决这个问题。

在Python中,del a.hand[:] del a.other_hand[:] 语法表示“所有元素”并允许切片(例如[:]a.hand[1:-1]中除了第一个和最后一个元素之外的元素列表。