为什么在__init__中实例化的对象无法看到它们的创建者?
我刚开始接触StackOverflow、面向对象编程和Python,所以请多多包涵;)
我在其他地方找了很多关于这个作用域问题的讨论和解释,但都没有找到。希望能得到一些帮助。
示例代码:
class Zeus(object):
def __init__(self):
self.name='zeus'
self.maze=Maze()
self.maze.get_zeus_name_1()
self.maze.get_zeus_name_2(self)
self.get_name_1()
self.get_name_2(self)
def get_name_1(self):
try:
print zeus.name
except:
print "impossible!?"
def get_name_2(self,scope):
print scope.name
class Maze(object):
def get_zeus_name_1(self):
try:
print zeus.name
except:
print "can't be done!?"
def get_zeus_name_2(self,scope):
print scope.name
zeus=Zeus()
print 'now external calls:'
zeus.maze.get_zeus_name_1()
zeus.maze.get_zeus_name_2(zeus)
zeus.get_name_1()
zeus.get_name_2(zeus)
输出:
can't be done!?
zeus
impossible!?
zeus
now external calls:
zeus
zeus
zeus
zeus
在创建zeus
这个对象的时候,如果__init__
方法里创建了另一个类的实例maze
,那么这个新创建的实例就无法访问它的创建者对象zeus
(除非把self
传给它)。
另外,如果__init__
方法调用了自己类里的一个方法get_name_1
,这个方法也无法访问对象的属性(同样,除非把self
传给它)。
不过,在两个对象都创建完成后,第二个对象maze
就能识别并访问它的创建者对象zeus
了。
这种行为让我有些困惑,因为我在写代码时,所有东西都是在__init__
的过程中初始化和运行的——现在我想这可能不是个好主意……
我有几个问题:
- 为什么会这样呢?
- 通过把实例化放在
__init__
调用之外,能否避免由此产生的问题? - 还有其他设计上的影响吗?
- 把
self
传给一个新实例,似乎可能会引发问题,因为它会自我引用,这个也应该避免吗? - 还有其他有趣的影响吗?我是不是漏掉了什么?
谢谢你的帮助。
3 个回答
让我给你举个例子,帮助你理解一下。
如果我们想要处理一群希腊神话中的神祇,我们需要先创建一个非常简单的 GreekGod
类:
class GreekGod(object):
def __init__(self, name):
self.name = name
zeus = GreekGod('Zeus')
athena = GreekGod('Athena')
print zeus.name
如果我们想做得更多,比如给每个希腊神祇添加一个自己的 称号,那么我们就需要在这个类里添加一个称号的属性:
class GreekGod(object):
def __init__(self, name, epithet):
self.name = name
self.epithet = epithet
def get_catch_frase(self):
return self.name + ' ' + self.epithet
zeus = GreekGod('Zeus', 'Father of the gods')
print zeus.get_catch_frase() # -> Zeus Father of the gods
你可以看到,方法 get_catch_frase
返回的是一个字符串,而不是直接打印这个字符串。这个行为是正常的,因为当你调用一个叫“get_something”的方法时,大家都期待它会返回某个东西,而不是做其他的事情。
我也希望你能理解 self
的作用,它代表了类里面的一个对象实例。
我想这和Python的特性有关——它是“逐行执行”源代码的。在这一行:zeus=Zeus()
,你创建了一个对象,同时调用了__init__
方法。当这行代码执行时,全球范围内还没有zeus
这个变量。我猜你遇到了NameError:名称'zeus'未定义(顺便说一下,捕获所有异常是不好的做法except: ...
;你应该只捕获你预期的异常;except NameError: ...
)。
你需要记住,在Python中,一切都是在运行时发生的。这里
x = 1 # x is just created, y 2 isn't created yet, you can't access it
y = 2 # y is just created, you can use it
这也可能导致循环导入的问题,比如模块A导入模块B,而模块B又从模块A导入某些东西,但模块A还没有创建——这就会出错。
在你的情况下,最好是把原始Zeus对象的链接传递给Maze对象,就像在__init__
中那样,并且只有在Zeus对象完全创建后再从Maze中访问它。
get_zeus_name_1()
这个函数在被调用的时候,试图访问一个全局变量 zeus
,但这个变量还没有被初始化。
get_zeus_name_2()
则是接收一个参数(scope
),并通过这个参数来访问数据,这样就没问题了。它没有去访问全局变量 zeus
。
get_name_1()
和 get_name_2()
也是同样的情况。
关键在于理解 Python 是如何执行这一行代码的:
zeus=Zeus()
这一行代码告诉 Python:执行 Zeus()
这个方法(它其实是 Zeus
类中的 __init__(self)
方法的另一种叫法),然后把这个方法返回的对象赋值给全局变量 zeus
。
Python 首先会执行 Zeus()
方法,然后在这个初始化方法执行完并返回一个 Zeus
类的对象实例之后,才会把这个对象赋值给全局变量 zeus
。所以在初始化方法完成之前,全局变量 zeus
还没有被定义,因此 get_name_1()
和 get_zeus_name_1()
无法访问它。
get_name_2()
和 get_zeus_name_2()
访问的是和 get_name_1()
和 get_zeus_name_1()
尝试访问的同一个 Zeus
类的对象实例,但它们是通过传入的参数来访问的,而不是通过全局变量,所以就没有遇到这个问题。
这里有一个更简单的例子,展示了同样的问题:
>>> class Foo:
... def __init__(self):
... self.name = 'foobar'
... print self.name
... print foo.name
...
>>> foo = Foo()
foobar
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in __init__
NameError: global name 'foo' is not defined
>>>
print self.name
这一行运行得很好(相当于 get_name_2()
和 get_zeus_name_2()
),但是 print foo.name
这一行就会出错(相当于 get_name_1()
和 get_zeus_name_1()
)。