关于mutable:Python对象初始化错误

Python object initialization bug. Or am I misunderstanding how objects work?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  1 import sys
  2
  3 class dummy(object):
  4     def __init__(self, val):
  5         self.val = val
  6
  7 class myobj(object):
  8     def __init__(self, resources):
  9         self._resources = resources
 10
 11 class ext(myobj):
 12     def __init__(self, resources=[]):
 13         #myobj.__init__(self, resources)
 14         self._resources = resources
 15
 16 one = ext()
 17 one._resources.append(1)
 18 two = ext()
 19
 20 print one._resources
 21 print two._resources
 22
 23 sys.exit(0)

这将为onetwo对象打印指定给one._resources的对象的引用。我认为two将是一个空数组,因为如果在创建对象时没有定义它,那么它显然是这样设置的。取消对myobj.__init__(self, resources)的注释也会做同样的事情。使用super(ext, self).__init__(resources)也可以做同样的事情。

我能让它工作的唯一方法是使用以下方法:

1
two = ext(dummy(2))

我不需要在创建对象时手动设置默认值来实现这一点。或许我会这么做。有什么想法吗?

我用python 2.5和2.6尝试了这个方法。


你应该换一下

1
2
def __init__(self, resources=[]):
    self._resources = resources

1
2
3
4
def __init__(self, resources=None):
    if resources is None:
        resources = []
    self._resources = resources

一切都会好起来的。如果默认参数是可变的,那么这是处理默认参数的方式的详细信息。在本页的讨论部分有更多信息。


您的问题是在函数定义时评估默认值。这意味着相同的列表对象在实例之间共享。有关更多讨论,请参阅此问题的答案。


请阅读此答案,讨论如何从__init__()设置类。您遇到了一个众所周知的python怪癖:您试图设置一个可变的,当__init__()编译时,会对可变的进行一次评估。标准的解决方法是:

1
2
3
4
5
6
class ext(myobj):
    def __init__(self, resources=None):
        if resources is None:
            resources = []
        #myobj.__init__(self, resources)
        self._resources = resources

从http://docs.python.org/3.1/tutorial/controlflow.html:

The default value is evaluated only
once. This makes a difference when the
default is a mutable object such as a
list, dictionary, or instances of most
classes.


这是一个已知的python gotcha。

必须避免在调用函数/方法时使用可变对象。

The objects that provide the default values are not created at the time that the function/method is called. They are created at the time that the statement that defines the function is executed. (See the discussion at Default arguments in Python: two easy blunders:"Expressions in default arguments are calculated when the function is defined, not when it’s called.")

This behavior is not a wart in the Python language. It really is a feature, not a bug. There are times when you really do want to use mutable default arguments. One thing they can do (for example) is retain a list of results from previous invocations, something that might be very handy.

But for most programmers — especially beginning Pythonistas — this behavior is a gotcha. So for most cases we adopt the following rules.

  • Never use a mutable object — that is: a list, a dictionary, or a class instance — as the default value of an argument.
  • Ignore rule 1 only if you really, really, REALLY know what you're doing.

So... we plan always to follow rule #1. Now, the question is how to do it... how to code functionF in order to get the behavior that we want.

Fortunately, the solution is straightforward. The mutable objects used as defaults are replaced by None, and then the arguments are tested for None.

So how can one do it correctly? One solution is avoid using mutable default values for arguments. But this is hardly satisfactory, as from time to time a new list is a useful default. There are some complex solutions like defining a decorator for functions that deep-copies all arguments. This is an overkill, and the problem can be solved easily as follows:

1
2
3
4
5
class ext(myobj):
    def __init__(self, resources=None):
        if not resources:
            resources = []
        self._resources = resources