Python中类内函数的前向声明
我第一次接触Python,现在遇到了一些问题:
class A:
def __init__(self):
a = foo("baa")
class B(A):
b = foo("boo")
def foo(string):
return string
在这个时候,我加载了上面的文件(叫做 classes
),然后出现了这样的情况:
$ python
Python 2.6.1 (r261:67515, Jun 24 2010, 21:47:49)
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from classes import *
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "classes.py", line 5, in <module>
class B(A):
File "classes.py", line 6, in B
b = foo("boo")
NameError: name 'foo' is not defined
注意错误出现在类B中,那里直接调用了 foo
,而不是从 __init__
中调用。还有,我还没有实例化类 B
。
第一个问题:
- 为什么会返回错误?我并没有实例化一个类。
接下来。这个“问题”通过把 foo()
的定义往上移动几行来解决:
def foo(string):
return string
class A:
def __init__(self):
a = foo("baa")
class B(A):
b = foo("boo")
现在我可以这样做:
>>> x = B()
>>> x.b
'boo'
但我不能这样做:
>>> y = A()
>>> y.a
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: A instance has no attribute 'a'
进一步的问题:
- 我对
__init__
的理解有什么不对的地方?
我觉得这和前向声明不太一样,所以我希望这个问题不是重复的。
顺便说一下,我的目标是实现一个DSL,但这主要是为了让我自己学习Python。
3 个回答
你可能没有理解的是,__init__
这个方法只有在你创建类 A
的实例时才会被调用。因为在你的代码中并没有实际创建这个类的实例,所以它的初始化方法就没有被调用,自然也就不会遇到前向声明的问题。
一般来说,前向声明在 Python 中并不是个大问题,因为引用只有在你调用相关函数时才会被评估。这里的问题是,类的定义是在定义时就被执行的。具体来说,当 Python 解释器遇到 class
语句时,会发生以下几件事:
- 它会定义一个新的命名空间(就像一个局部变量的字典)来存放所有的代码。
- 它会执行类定义中的代码。
- 它会把存储在局部命名空间中的内容放入类的
__dict__
中。 - 它会关闭这个新的命名空间,并将新类添加到模块的命名空间中。
当然,你不需要了解这些细节就能定义一个类;你只需要记住,类定义中的任何内容在类第一次定义时都会被执行!(这通常不是个问题,因为类级别的变量并不常见,通常也不需要在定义时动态设置。)
这个问题在函数定义中也会出现:当你执行
def foo(x=bar()): pass
时,bar
只会在 foo
被定义时被调用一次,之后就不会再调用了。
试着运行以下代码,看看是否能帮助你理解:
class A():
def __init__(self):
self.a = foo()
a = A()
def foo():
pass
class A:
def __init__(self):
a = foo("baa")
class B(A):
b = foo("boo")
a 是一个实例属性 - 每个 A 类的对象都有自己的 a 变量,而 foo("baa") 只有在你创建 A 对象的时候才会被计算。
b 是一个类属性 - 每个 B 类的对象共享同一个 b 的副本,而 foo("boo") 在类定义的时候就会被计算。
一般来说,你可以引用那些还不存在的函数和类,只要在你实际调用这个函数之前,它们被定义好了。
首先,在类 B
中,函数 foo()
是在被声明之前就被调用了。而类 A
没有这个问题,因为 foo()
只有在类被实例化后才会被调用,也就是在函数 foo
定义之后。
关于你的第二个问题,y.a 是不行的,因为你没有写 self.a = foo('stirng')
。a = foo('stirng')
只是创建了一个变量 a,这个变量只在 __ init __
的范围内有效。
一个理想的 __ init __
函数会把变量分配给实例 self
。