为什么在__init__中实例化的对象无法看到它们的创建者?

2 投票
3 回答
2269 浏览
提问于 2025-04-17 11:17

我刚开始接触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__的过程中初始化和运行的——现在我想这可能不是个好主意……

我有几个问题:

  1. 为什么会这样呢?
  2. 通过把实例化放在__init__调用之外,能否避免由此产生的问题?
  3. 还有其他设计上的影响吗?
  4. self传给一个新实例,似乎可能会引发问题,因为它会自我引用,这个也应该避免吗?
  5. 还有其他有趣的影响吗?我是不是漏掉了什么?

谢谢你的帮助。

3 个回答

1

让我给你举个例子,帮助你理解一下。

如果我们想要处理一群希腊神话中的神祇,我们需要先创建一个非常简单的 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 的作用,它代表了类里面的一个对象实例。

4

我想这和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中访问它。

5

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

撰写回答