命名空间
- 本文记录对 python 命名空间的思考和实践,可能逻辑上有所混乱,有时间再整理一下
命名空间的定义:变量到对象映射的集合。一般是通过字典来实现的
python中:命名空间就是字典
1、类的命名空间和对象的命名空间
class A:
count = 1
def init(self):
self.count_ += 2
A.count += 3
print(locals())
# 实例化
a = A()
# 使用 __dict__方法查看命名空间
a.__dict__
{}
# a 是有自己的命名空间的,这里有屏蔽作用,
# 对象自己有的时候,就不用去类里面找
# 但是如果对象本身找不到,就要去类的对象空间里寻找了,
# 但是并不代表类 A 的对象空间的变量属于变量 a 了
# 通过查看 a.__dict__,此时依旧为空
a.count
1
# 查看下 A 命名空间
# 是有 'count': 1
A.__dict__
mappingproxy({'__module__': '__main__',
'count': 1,
'init': <function __main__.A.init(self)>,
'__dict__': <attribute '__dict__' of 'A' objects>,
'__weakref__': <attribute '__weakref__' of 'A' objects>,
'__doc__': None})
A.count
1
a == A
False
# 神奇的来了,表明目前为止,两者的 count 是一致的
# 不过这样并不能很说明问题,让我们来查看一下它们的 id
a.count == A.count
True
id(a.count)
140735818053408
id(A.count)
140735818053408
可以看出,这两者就是同一个 count,也就是最开始 class A 中 count = 1
这一步。也很好理解,a 中没有 count,便在 A 的命名空间里寻找
2、如果我们给对象 a 创建一个 count,结果会怎么样
# 实现起来很简单,为了区分明显,赋值为字符串
a.count = "hello"
# 查看 a 的命名空间,就发生了变化
a.__dict__
{'count': 'hello'}
那么此时 A.count 和 a.count 还相等吗? 是有结论的,肯定不相等
a.count == A.count
False
id(a.count)
1756673821232
id(A.count)
140735818053408
3、尝试调用 init
# 接下来我们尝试去调用这个 init 函数
# 如果我们直接这么使用,是会被报错的
# >>> a.init()
# 报错信息如下:
# ---------------------------------------------------------------------------
# AttributeError Traceback (most recent call last)
# <ipython-input-202-6ef2be712bbb> in <module>
# 1 # 接下来我们尝试去调用这个 init 函数
# 2 # 如果我们直接这么使用,是会被报错的
# ----> 3 a.init()
# <ipython-input-186-fe727f2532f8> in init(self)
# 2 count = 1
# 3 def init(self):
# ----> 4 self.count_ += 2
# 5 A.count += 3
# 6 print(locals())
# AttributeError: 'A' object has no attribute 'count_'
稍微解读下这个报错,因为此刻对象 a 的命名空间中没有 count_ 变量,于是在 A 的命名空间中进行寻找,也没 count_ 变量,此刻 A 的命名空间中只有 count 变量,所以出现报错提示:’A’ object has no attribute ‘count_’
为了检验这一想法,两种方式:
- 在对象 a 里创建一个 count_ 变量
- 在 class A 里添加一个 count_ 变量
3.1 方式一:在对象 a 里创建一个 count_ 变量
# 方式一
a.count_ = 10
a.__dict__ #已经有 count_ 变量
{'count': 'hello', 'count_': 10}
# 再次调用 init() 函数
a.init()
{'self': <__main__.A object at 0x00000199033EE9D0>}
# 说明已经成功运行
# 已进行 self.count_ += 2 操作
a.count_
12
a.__dict__
{'count': 'hello', 'count_': 12}
3.2 方式二:在 class A 里添加一个 count_ 变量
# 方式二,为了不清除原 class,这里我新建一个 class
class C:
count = 1
count_ = 10 # 添加的 count_ 变量
def init(self):
self.count_ += 2
C.count += 3 # 之后考虑为什么写 count += 3 不行
# 同样,我们一边打印命名空间,一边进行
c = C()
c.__dict__
{}
# 注意,此时类 C 的命名空间有 count、count_
C.__dict__
mappingproxy({'__module__': '__main__',
'count': 1,
'count_': 10,
'init': <function __main__.C.init(self)>,
'__dict__': <attribute '__dict__' of 'C' objects>,
'__weakref__': <attribute '__weakref__' of 'C' objects>,
'__doc__': None})
# 同样,我们再次调用 init
c.init()
# 同样得到相同结果
c.count_
12
c.__dict__
{'count_': 12}
4、探究变量 a 的命名空间和类 A 命名空间作用域
a.__dict__
{'count': 'hello', 'count_': 12}
A.__dict__
mappingproxy({'__module__': '__main__',
'count': 4,
'init': <function __main__.A.init(self)>,
'__dict__': <attribute '__dict__' of 'A' objects>,
'__weakref__': <attribute '__weakref__' of 'A' objects>,
'__doc__': None})
# 再次调用
a.init()
{'self': <__main__.A object at 0x00000199033EE9D0>}
# 此时 count 并没有被改变,指的是没有被 A.count 所改变
# 如果理解清楚的话,可以很好解释
# 对象 a m命名空间中有的变量,就不需要去类 A 中寻找,也就是保持原来的 hello 不变
a.__dict__
{'count': 'hello', 'count_': 14}
A.__dict__
mappingproxy({'__module__': '__main__',
'count': 7,
'init': <function __main__.A.init(self)>,
'__dict__': <attribute '__dict__' of 'A' objects>,
'__weakref__': <attribute '__weakref__' of 'A' objects>,
'__doc__': None})
5、探究 self 的作用
我们在写类内函数时,都会在函数输入变量中传入 self,这是为什么?
看一下不一样的调用 init 函数方式
A.init(a) #需要传参数
{'self': <__main__.A object at 0x00000199033EE9D0>}
常规的调用方式
a.init() #可以直接调用,但是实际上也是 init(a),只不过没有这么表达罢了
{'self': <__main__.A object at 0x00000199033EE9D0>}
这里也可以解释,为什么类里面的方法,需要传入 self 作为参数,即使有时候该函数下根本用不到这个参数,比如就是一个简简单单的函数,你同样需要写 self。当然你可能还有疑问,为什么是 self,我写别的行不行?当然可以啦,一个参数名称而已,只不过写 self 已经成为一种习惯,大家也都用这个表示“对象 e ”了。
class E:
# 如果 data 里不写 self ,就会报错
# TypeError: init() takes 0 positional arguments but 1 was given
# 告诉我们,我们多传了一个参数,你说我们没传参啊,
# 用的 e.data(),没有参数啊,怎么不对?
# 这里的 but 1 was given,指的就是这个 e,对象 e 本身作为参数传进去
def data(self):
return 2
e = E()
e.data()
class E:
# 如果 data 里不写 self ,就会报错
# TypeError: init() takes 0 positional arguments but 1 was given
# 告诉我们,我们多传了一个参数,你说我们没传参啊,
# 用的 e.data(),没有参数啊,怎么不对?
# 这里的 but 1 was given,指的就是这个 e,对象 e 本身作为参数传进去
def data(self):
return 2
e = E()
e.data()
2
# 可以看到类 A 是有自己的命名空间的
# 对象 a 也是有自己的命名空间的
# 并且
A.__dict__
mappingproxy({'__module__': '__main__',
'count': 13,
'init': <function __main__.A.init(self)>,
'__dict__': <attribute '__dict__' of 'A' objects>,
'__weakref__': <attribute '__weakref__' of 'A' objects>,
'__doc__': None})
a.__dict__
{'count': 'hello', 'count_': 18}