命名空间


命名空间

  • 本文记录对 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_’

为了检验这一想法,两种方式:

  1. 在对象 a 里创建一个 count_ 变量
  2. 在 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}

文章作者: Terence Cai
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Terence Cai !
  目录