Think Python Exercise 17.2

这道习题中包含了 Python 中最常见、最难找出来的错误。编写一个叫 Kangaroo 的类,包含以下方法:

  1. 一个__init__ 方法,初始化一个叫 pounch_contents 的属性为空列表。
  2. 一个叫put_in_pounch 的方法,将一个任意类型的对象加入pounch_contents。
  3. 一个__str__ 方法,返回 Kangaroo 对象的字符串表示和 pounch 中的内容。

创建两个 Kangaroo 对象, 将它们命名为 kanga 和 roo , 然后将 roo 加入 kanga 的 pounch 列表, 以此测试你写的代码。

这是我在不完全理解题目时写的代码(请不要参考):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Kangaroo:
    def __init__(self):
        self.pounch_contents = []
    def put_in_pounch(self, ayobj):
        self.pounch_contents.append(ayobj)
    def __str__(self):
        return str(self.pounch_contents)
    
kanga = Kangaroo()
roo = Kangaroo()

kanga.put_in_pounch(roo)

print(kanga)

其实这是一个找bug的练习。作者想让我们使用pylint这个工具找出他的代码里的bug。

作者的代码(含bug):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
"""This module contains a code example related to

Think Python, 2nd Edition
by Allen Downey
http://thinkpython2.com

Copyright 2015 Allen Downey

License: http://creativecommons.org/licenses/by/4.0/
"""

from __future__ import print_function, division

"""

WARNING: this program contains a NASTY bug.  I put
it there on purpose as a debugging exercise, but
you DO NOT want to emulate this example!

"""

class Kangaroo:
    """A Kangaroo is a marsupial."""
    
    def __init__(self, name, contents=[]):
        """Initialize the pouch contents.

        name: string
        contents: initial pouch contents.
        """
        self.name = name
        self.pouch_contents = contents

    def __str__(self):
        """Return a string representaion of this Kangaroo.
        """
        t = [ self.name + ' has pouch contents:' ]
        for obj in self.pouch_contents:
            s = '    ' + object.__str__(obj)
            t.append(s)
        return '\n'.join(t)

    def put_in_pouch(self, item):
        """Adds a new item to the pouch contents.

        item: object to be added
        """
        self.pouch_contents.append(item)


kanga = Kangaroo('Kanga')
roo = Kangaroo('Roo')
kanga.put_in_pouch('wallet')
kanga.put_in_pouch('car keys')
kanga.put_in_pouch(roo)

print(kanga)

# If you run this program as is, it seems to work.
# To see the problem, trying printing roo.

# Hint: to find the problem try running pylint.

用pylint分析一番:

% pylint /Users/aoyu/Downloads/ThinkPython2-master/code/BadKangaroo.py

************* Module BadKangaroo

Downloads/ThinkPython2-master/code/BadKangaroo.py:24:0: C0303: Trailing whitespace (trailing-whitespace)

Downloads/ThinkPython2-master/code/BadKangaroo.py:1:0: C0103: Module name "BadKangaroo" doesn't conform to snake_case naming style (invalid-name)

Downloads/ThinkPython2-master/code/BadKangaroo.py:14:0: W0105: String statement has no effect (pointless-string-statement)

Downloads/ThinkPython2-master/code/BadKangaroo.py:25:4: W0102: Dangerous default value [] as argument (dangerous-default-value)

Downloads/ThinkPython2-master/code/BadKangaroo.py:37:8: C0103: Variable name "t" doesn't conform to snake_case naming style (invalid-name)

Downloads/ThinkPython2-master/code/BadKangaroo.py:39:12: C0103: Variable name "s" doesn't conform to snake_case naming style (invalid-name)

------------------------------------------------------------------

Your code has been rated at 7.00/10 (previous run: 7.00/10, +0.00)

其他的都是小毛病,关键在于:

Downloads/ThinkPython2-master/code/BadKangaroo.py:25:4: W0102: Dangerous default value [] as argument (dangerous-default-value)

说把空列表作为参数的默认值,很危险。怎么就危险了呢?看一个简单的例子就明白了:

>>> a = []
>>> b = a
>>> c = a
>>> b.append('a')
>>> a
['a']
>>> b
['a']
>>> c
['a']

a, b, c三个变量都是空列表的引用,其中一个改变,其他的变量也跟着改变。

作者的代码的bug也是同理。每一个 Kangaroo 对象的 pouch_contents 属性的默认值都是一个空列表的引用,一个对象的 pouch_contents 改变,其他对象的 pouch_contents 也跟着改变。

作者的解决办法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
"""This module contains a code example related to

Think Python, 2nd Edition
by Allen Downey
http://thinkpython2.com

Copyright 2015 Allen Downey

License: http://creativecommons.org/licenses/by/4.0/
"""

from __future__ import print_function, division

"""

WARNING: this program contains a NASTY bug.  I put
it there on purpose as a debugging exercise, but
you DO NOT want to emulate this example!

"""

class Kangaroo:
    """A Kangaroo is a marsupial."""
    
    def __init__(self, name, contents=[]):
        """Initialize the pouch contents.

        name: string
        contents: initial pouch contents.
        """
        # The problem is the default value for contents.
        # Default values get evaluated ONCE, when the function
        # is defined; they don't get evaluated again when the
        # function is called.

        # In this case that means that when __init__ is defined,
        # [] gets evaluated and contents gets a reference to
        # an empty list.

        # After that, every Kangaroo that gets the default
        # value gets a reference to THE SAME list.  If any
        # Kangaroo modifies this shared list, they all see
        # the change.

        # The next version of __init__ shows an idiomatic way
        # to avoid this problem.
        self.name = name
        self.pouch_contents = contents

    def __init__(self, name, contents=None):
        """Initialize the pouch contents.

        name: string
        contents: initial pouch contents.
        """
        # In this version, the default value is None.  When
        # __init__ runs, it checks the value of contents and,
        # if necessary, creates a new empty list.  That way,
        # every Kangaroo that gets the default value gets a
        # reference to a different list.

        # As a general rule, you should avoid using a mutable
        # object as a default value, unless you really know
        # what you are doing.
        self.name = name
        if contents == None:
            contents = []
        self.pouch_contents = contents

    def __str__(self):
        """Return a string representaion of this Kangaroo.
        """
        t = [ self.name + ' has pouch contents:' ]
        for obj in self.pouch_contents:
            s = '    ' + object.__str__(obj)
            t.append(s)
        return '\n'.join(t)

    def put_in_pouch(self, item):
        """Adds a new item to the pouch contents.

        item: object to be added
        """
        self.pouch_contents.append(item)


kanga = Kangaroo('Kanga')
roo = Kangaroo('Roo')
kanga.put_in_pouch('wallet')
kanga.put_in_pouch('car keys')
kanga.put_in_pouch(roo)

print(kanga)
print(roo)

# If you run this program as is, it seems to work.
# To see the problem, trying printing roo.

代码里面的object.__str__(obj)怎样理解?

返回它的参数obj所指的对象的字符串表现形式。

举例:

>>> a = 'abc'
>>> object.__str__(a)
"'abc'"
>>> print(a)
abc
>>> b = 123
>>> object.__str__(b)
'123'
>>> print(b)
123
>>> class Point:
...     """test"""
...
>>> object.__str__(a)
'<__main__.Point object at 0x7f7db642b700>'