Monthly Archives: 四月 2015

Python命名空间和作用域窥探

这篇文章写的

Namespace and Scope(命名空间和作用域)

namespace

Namespace(只)是 从名字到对象的一个映射(a mapping from name to objects)

大部分namespace都是按Python中的字典来实现的。

有一些常见的namespace:built-in中的集合( abs() 函数等)、一个模块中的全局变量等。

从某种意义上来说,一个对象(object)的所有属性(attribute)也构成了一个namespace.

在程序执行期间,可能(其实是肯定)会有多个名空间同时存在。

不同namespace的创建/销毁时间也不同。

此外,两个不同namespace中的两个相同名字的变量之间没有任何联系。

scope

有了namespace基础之后,让我们再来看看scope.

Scope是Python程序的一块文本区域(textual region)。

在该文本区域中,对namespace是可以直接访问,而不需要通过属性来访问。

Scope是定义程序该如何搜索确切地“名字-对象”的名空间的层级关系。
(The “scope” in Python defines the “hirerchy level” in which we search namespaces for
certain “name-to-object” mappings.)

Tip

直接访问:对一个变量名的引用会在所有namespace中查找该变量,而不是通过属性访问。

属性访问:所有名字后加 . 的都认为是属性访问。

module_name.func_name ,需要指定 func_name 的名空间,属于属性访问。
abs(-1)abs 属于直接访问。

两者之间有什么联系呢?

Important

在Python中,scope是由namespace按特定的层级结构组合起来的。

scope一定是namespace,但namespace不一定是scope.

LEGB-rule

在一个Python程序运行中,至少有4个scopes是存在的。

直接访问一个变量可能在这四个namespace中逐一搜索。

  • Local(innermost)

    包含局部变量。
    比如一个函数/方法内部。

  • Enclosing

    包含了非局部(non-local)也非全局(non-global)的变量。
    比如两个嵌套函数,内层函数可能搜索外层函数的namespace,但该namespace对内层函数而言既非局部也非全局。

  • Global(next-to-last)

    当前脚本的最外层。
    比如当前模块的全局变量。

  • Built-in(outtermost)

    Python __builtin__ 模块。
    包含了内建的变量/关键字等。

那么,这么多的作用域,Python是按什么顺序搜索对应作用域的呢?

著名的”LEGB-rule”,即scope的搜索顺序:

Important

Local -> Enclosing -> Global -> Built-in

怎么个意思呢?

当有一个变量在 local 域中找不到时,Python会找上一层的作用域,即 enclosing 域(该域不一定存在)。
enclosing 域还找不到的时候,再往上一层,搜索模块内的 global 域。最后,会在 built-in 域中搜索。
对于最终没有搜索到时,Python会抛出一个 NameError 异常。

Note

作用域可以嵌套。比如模块导入时。

这也是为什么不推荐使用 from a_module import * 的原因,导入的变量可能被当前模块覆盖。

Assignment rule

看似python作用域到此为止已经很清晰了,让我们再看一段代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def outer():
    a = 0
    b = 1

    def inner():
        print a
        print b

    inner()

outer()

你觉得结果是什么呢?So easy是不是?

cipher@Rachel ~/Development/Workspace/test_Python $ python2 a.py
0
1

如果多加一句呢?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def outer():
    a = 0
    b = 1

    def inner():
        print a
        print b

        # b += 1        # A

        b = 4           # B

    inner()

outer()

结果又会是什么呢?

cipher@Rachel ~/Development/Workspace/test_Python $ python2 a.py
0
Traceback (most recent call last):
  File "a.py", line 34, in <module>
    outer()
  File "a.py", line 32, in outer
    inner()
  File "a.py", line 29, in inner
    print b
UnboundLocalError: local variable 'b' referenced before assignment

是不是很奇怪?

原因是这样的:

Python解释器执行到 inner() 中的 print b 时,发现有个变量 b 在当前作用域(local)中
无法找到该变量。它继续尝试把整块代码解释完。

Bingo! 找到了。那么 b 是属于 inner() 作用域的。
既然对变量 b 的赋值(声明)发生在 print 语句之后, print 语句执行时
变量 b 是还未被声明的,于是抛出错误:变量在赋值前就被引用。

Note

在这个例子中,只有A语句没有B语句也会导致同样的结果。
因为 b += 1 等同于 b = b + 1。

对于变量的作用域查找有了了解之后,还有两条很重要的规则:

Important

  1. 赋值语句通常隐式地会创建一个局部(local)变量,即便该变量名已存在于赋值语句发生的上一层作用域中;
  2. 如果没有 global 关键字声明变量,对一个变量的赋值总是认为该变量存在于最内层(innermost)的作用域中;

也就是说在作用域内有没有发生赋值是不一样的。

但是,在这点上,Python 2和Python 3又有不同, Python access non-local variable:

Note

Python’s scoping rules indicate that a function defines a new scope level,
and a name is bound to a value in only one scope level – it is statically scoped.

In Python 2.x, it is not possible to modify a non-local variable;
1) you have either read-only access to a global or non-local variable,
2) or read-write access to a global variable by using the global statement,
3) or read-write access to a local variable (by default).

In Python 3.x, the nonlocal statement has been introduced with a similar effect
to global, but for an intermediate scope.

for 循环

为什么讲到作用域要说到 for 循环呢?难道!@#$%^&*()???

对于大部分语言(比如 C 语言)而言, for-loop 会引入一个新的作用域。
但Python有点一样却又不太一样。

让我们先来看个例子:

CipherChen@CIPHERC ~/Development/Workspace/test_python $ python2
Python 2.7.9 (default, Jan 25 2015, 13:42:57)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.56)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> for i in range(10): print i
...
0
1
2
3
4
5
6
7
8
9
>>> print i
9
>>>

有点不可思议是不是?

Python 2.x for语句 中是这么说的:

Note

The for-loop makes assignments to the variable(s) in the target list.
This overwrites all previous assignments to those variablees including those made in the suite of the for-loop.

The target list is not deleted when the loop is finished.
But if the sequence is empty, they will not have been assigned to at all the loop.

for 后面跟着的变量(target list)在循环结束后是不会被删除的,
但如果 for 循环的序列为空,这些变量是完全不会被赋值的。

这在Python中是个大坑啊。

避免这个坑的解决办法就是规范命名规范。
比如用于循环的变量尽量使用单字符。在任何有疑议的情况可以直接将索引值初始化。

很不幸,Python 3中这点没有改变。

List Comprehension vs. Generator Expression

关于Python作用域这堂课已经上了很久了,我们先休息一下,说个题外话吧。

  • 列表推导式(List Comprehension)

    # List comprehension
    [expression for var in iterable]
    

    简单的理解列表推导式:

    def _lc(arg):
        result = []
        for i in arg:
            result.append(i * i)
        return result
    
    <expr_value> = _lc(x)
    
    • 列表推导式会把所有数据都加载到内存。适合 “结果需要多次被使用” 或者 “需要使用list相关的方法(分片等)” 等的情况。
  • 生成器表达式(Generator Expression)

    # Generator expression
    (expression for var in iterable)
    

    简单的理解生成器表达式:

    def _ge(arg):
        for i in arg:
            yield i * i
    
    <expr_value> = _ge(x)
    
    • 使用生成器实现。适合“数据量非常大或者无限”的情况。

它们的表现效果分别是这样的:

>>> [i for i in range(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> (i for i in range(10))
<generator object <genexpr> at 0x7fd5ab625b88>

Python 作用域,我已经完全掌握了!

稍作小憩之后,看来大家对Python作用域很有信心了。

好的。那我们来测试一下。

这是类(class)定义中的一个小问题:

1
2
3
class A(object):
    a = 3
    b = list(a + i for i in range(10))

这段代码执行起来是不是跟你想的有点一样但又不那么一样呢?

cipher@Rachel ~/Development/Workspace/test_Python $ python a.py
Traceback (most recent call last):
  File "a.py", line 3, in <module>
    class A(object):
  File "a.py", line 5, in A
    b = list(a + i for i in range(10))
  File "a.py", line 5, in <genexpr>
    b = list(a + i for i in range(10))
NameError: global name 'a' is not defined

刚刚总结的规则完全用不上啊!!!

“元芳,你怎么看?”

真相只有一个:

class没有作用域(scope),但有一个局部的名空间(namespace),它并不构成一个作用域。
这意味着在类定义中的表达式可以访问该名空间。

但在类体(class body)中, 对 b 的赋值表达式中,该表达式引入了一个新的作用域,该作用域并不能访问类的名空间。

就像刚刚说的,函数会引入一个新的作用域。

比如说:

class C(object):
    a = 2
    def foo(self):
        return a    # NameError: name 'a' is not defined, use return self.__class__.a

在Python 2中,列表推导式没有引入一个新的作用域。所以:

Python 2.7.9 (default, Jan 25 2015, 13:42:57)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.56)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> [a for a in range(3)]
[0, 1, 2]
>>> print a
2

而对于Python 2和Python 3,生成器表达式都有引入新的作用域。

为了让列表推导式和生成器表达式的表现一致,
在Python 3中,列表推导式也有引入一个新的作用域。所以:

Python 3.4.2 (default, Apr 25 2015, 15:59:50)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> [a for a in range(3)]
[0, 1, 2]
>>> print(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined

解决方案

所以,要解决这个问题,有几种解决办法:

  1. 用生成器表达式
b = [a + i for i in range(10)]
  1. 用函数/lambda引入新的作用域
b = (lambda a: ((a + i for i in range(10))))(a)

有没有开始怀疑人生怀疑理想?

附一份:访问权限汇总表

Can access class attributes Python 2 Python 3
list comp. iterable Y Y
list comp. expression Y N
gen expr. iterable Y Y
gen expr. expression N N
dict comp. iterable Y Y
dict comp. expression N N

总结

本文介绍了Python中 namespacescope 的区别,
以及复杂作用域的搜索规则( LEGB )。
此外,还介绍了一些常见的会创建scope的情况(函数定义,生成器表达式等),当然包括
了Python 2和Python 3中的不同实现。

Python中对于作用域的定义确实是个大问题,我并没有找到像 C 语言那样,
“代码块 {} 中定义的即是一个局部作用域”这样简洁的规则来清晰地表明
Python中作用域的 创建/销毁 的条件。

这篇文章的内容积压了很久,终于抽了点时间出来整理了下。

写的也有点没章法了,各位看官看得懂就看吧;看不懂多看几遍吧。

看望之后也提点啥建议意见之类的,好让后来人也能更快速简单的理解这个问题。
万一我理解错了呢?

欢迎探讨。

但有一点可以肯定,“这事儿还没完”。

linux free命令输出含义

这篇文章写的

身为一名“兼职”运维人员,需要对linux常用命令熟练应用。比如 netstat / tcpdump / free 等。

查看linux中内存使用有许多命令, top / vmstat / htop / /proc/meminfo / free 等。

free 命令作为一种简单的查看内存的方式,
在linux系的服务器上几乎都有默认安装,它的参数也较少。

直接在命令行执行 free 就可以看到内存使用的情况:

[cipher@Eleanor ~]$ free
             total       used       free     shared    buffers     cached
Mem:       4048052    3609736     438316       1144     224200    1085376
-/+ buffers/cache:    2300160    1747892
Swap:      4194300      82252    4112048

将结果按照 MB 显示:

[cipher@Eleanor ~]$ free -m
             total       used       free     shared    buffers     cached
Mem:          7858       7344        514          0        412       3116
-/+ buffers/cache:       3815       4042
Swap:         5855        511       5344

那么问题就来了,简简单单地一个命令,我只是想查看内存使用量是多少,
为何要输出这么多信息?这些信息分别代表着什么?

这就需要先问一个问题,linux内存使用包括哪些?

  • physical

    RAM的大小

  • swap

    交换分区

  • buffer/Cache

    内核使用

Hint

buffer和Cache有何区别? [1]

buffer是特定程序用来临时存储数据的位置,这些数据存储不可能被其它进程访问。
举个例子,通过网络发送大量数据时,网卡只会以恒定的速率发送部分数据,
而大部分数据都会被存储在buffer中等待发送。

而Cache是为提高某些访问频率较高的数据的访问速度的数据存放地址。

Cache能被重复使用而buffer只会被使用一次。

buffer和Cache相同之处在于两者都是用来 临时存储 数据的。

我们把这个结果看作一个表格:

         0                   1            2          3          4          5          6
0                        total         used       free     shared    buffers     cached
1    Mem:                7858          7344        514          0        412       3116
2    -/+ buffers/cache:                3815       4042
3    Swap:               5855           511       5344
  • F[1][1]:物理内存总量
  • F[1][2]:表示总计分配给缓存(包含buffers 与cache )使用的数量,但其中可能部分缓存并未实际使用。
  • F[1][3]:未被分配的内存。
  • F[1][4]:共享内存,一般系统不会用到,这里也不讨论。
  • F[1][5]:系统分配但未被使用的buffers 数量。
  • F[1][6]:系统分配但未被使用的cache 数量。
  • F[1][1] = F[1][2] + F[1][3]
  • F[2][2] = F[1][2] – F[1][5] – F[1][6]
  • F[2][3] = F[1][3] + F[1][5] + F[1][6]

由此可见,F[2][3]可简单地表明当前系统剩余可用的总内存数。

最后,我画了一张草图来表明这几个项之间的关系:

http://7vijbz.com1.z0.glb.clouddn.com/blog_linux_free_graph_s.jpg

参考文献

[1] Difference between buffers and Cache