1

我在 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]]
4

3 回答 3

3

在您的a类中my_handother_handhands变量都是属性(即静态属性),而不是实例属性。

同样在您的clear方法中,您重新分配my_handand other_hand,但hands仍引用旧list的 s,因此其内容不会改变。如果您使用的是 python3,那么您应该使用s:的clear()方法,然后将引用两个空的 s。在 python2 上你应该做.listmy_hand.clear()other_hand.clear()handslistdel 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_handandother_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有两种不同的用途。该语句的完整语法是:

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意思是:删除列表中与 slice 中的索引相对应的所有元素:。由于切片:意味着“序列中的所有索引”,结果是清空列表。请注意,它不会列表中删除引用。它只作用于它的元素。

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

numbers[:] = []

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

两者都有效,但是我更喜欢del语法,因为它明确了您的意图。当你阅读:

del something[:]

您知道该语句将删除某些内容。然后你会看到下标[:],你就会明白它会从引用中删除元素,something而不是从引用中删除something。然而,当你看到:

something[:] = []

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

于 2013-11-09T08:39:49.000 回答
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)。

于 2013-11-09T06:34:47.543 回答
1

作为 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

于 2013-11-09T06:39:08.430 回答