此文出自知乎灵剑,原文传送门:https://zhuanlan.zhihu.com/p/23926957

  在摸索适合自己的语言学习方法,看到一篇好文章,转之,侵删。

  Python的语法范式相当多、知识点相当细,但是Python是一种内在一致性很好的语言,理解了几条基本的规则,就很容易理解大部分语法与现象。本文会总结一下Python中最根本的几条规则,从这几条规则中,我们可以看出Python与许多其他语言不同,它是非常纯粹、非常和谐的。

Python的基本语法:表达式运算与名称绑定

Python的语句大体上可以分成简单语句和复合语句两类,简单语句当中,最重要的是表达式语句和赋值语句。为了区分C/C++等其他语言当中的赋值,本文中,我们将

  1. a = 1
  2. b = 'abc'
  3. c = (a,b)

  这样的赋值语句称为名称绑定。顾名思义,它代表将右侧表达式运算的结果起了个名字,方便我们将来使用。相应的,变量在本文中称为名称,它主要代表名字本身,而不是对应的值。在官方文档当中叫做identifier(标识符),绑定也就是bound。

  关于表达式、简单语句、复合语句的分类,在官方文档 https://docs.python.org/2.7/reference/index.html 当中已经讲得非常好而全面了(用Python 3的注意左上角切换版本)。简单来说,表达式有很多种,比如常量,比如单个名称,比如运算,比如调用,比如yield等等,这个列表也会不断扩展。表达式的共同特点是它们都有且仅有一个返回值——有些返回值永远是None,有些返回值是元组,但可以认为都是有且仅有一个返回值。

  有些时候我们直接计算表达式就够了,比如有些函数调用。另一些时候,我们会关心这个表达式求出的值,我们希望在后面重新使用这个计算出的值,我们就用到了名称绑定,也就是为这个结果起一个方便我们后面使用的名字,通过名称绑定,它是非常浅显易懂的语法,通常由一个等号组成,等号左侧是要赋值的名称,右侧是表达式,表达式的结果就会绑定到这个名称上:

  1. <target> = <expression>

这种包含等号的语句叫做赋值语句,它还有另外几种变种:

  1. <target1>, <target2> = <sequence-with-length-2>
  2. # 如:
  3. a,b = (1,2)
  4. # 或者
  5. a,b = 1,2

表示解构的赋值

  1. <target1> = <target2> = <target3> = ... = <expr>

可以将同一个值绑定到多个名称。

赋值语句和名称绑定略有区别,它除了用于名称绑定以外,还可以用来修改可变对象如列表(list)和字典(dict):

  1. a[1] = 2
  2. a[2:3] = [4,5,6]
  3. b['a'] = 12

通常需要注意区别。

Python绑定 vs C赋值

我们看下面的代码:

  1. a = 1
  2. a = "abc"

  当我们对已经绑定了的名称重新赋值的时候,旧的值会解除绑定,新的值绑定到名称,这对于旧的值和新的值来说都是不可见的。名称相当于相应值的别名,这个值有多少个名称,都不会影响到值本身;同样,不同的名称也都代表同一个值,因此如果多个名称绑定到了同一个值,而值发生了变化,所有名称对应的值都会变化:

  1. a = [1,2]
  2. b = a
  3. a.append(3)
  4. a # [1,2,3]
  5. b # [1,2,3]

  以往会用“传值/传引用”、“强类型/弱类型”这样的概念,对于Python来说其实都是不必要的,我们记住这样的规则:名称只是对象的别名,不同的名称可以表示同一个对象,同一个对象并没有唯一的名称。

C中的变量就完全不一样,一旦定义就占用固定的内存,赋值实际上是实际内存值的复制。Python的名称是个逻辑概念,而C的变量相当贴近物理实现,这也体现了Python作为编程语言更为纯粹这一特点。

除了重新绑定时候自动解除旧值的绑定,Python中的名称还可以强制解除绑定:

  1. del <identifier>

特殊绑定

  除了赋值语句以外,还有一些特殊语句会实现名称绑定,主要包括import、def(函数定义)、class(类定义)。此外,某些语句的主要功能不是名称绑定,但可以额外附加名称绑定的效果,如for、except、with。

重点说前三个,它们实际上都是赋值语句的直接变形,如import:

  1. import my_module
  2. import my_module as m1
  3. from my_module import my_object
  4. from my_module import my_object as o1

实际上完全等效于:

  1. my_module = __import__('my_module')
  2.  
  3. m1 = __import__('my_module')
  4.  
  5. _ = __import__('my_module')
  6. my_object = _.my_object
  7. del _
  8.  
  9. _ = __import__('my_module')
  10. o1 = _.my_object
  11. del _

  这些语句只是将表达式求值 + 赋值的过程写成了比较统一、简洁、易懂的形式,要理解它们本质上仍然是赋值语句。因此import的结果也可以用del解除绑定,可以重新绑定到新的值。这体现了Python语言的一致性。

定义函数和类也是这样:

  1. def my_func():
  2. pass
  3. #重新绑定
  4. my_func = 123
  5. #解除绑定
  6. del my_func

这与C/C++区别很大,它们都是在定义的时候创建了函数/类的对象,然后将这个对象绑定到了相应的名称上,理解这一点对于理解嵌套函数定义之类的规则大有帮助。

作用域,作用域嵌套与global作用域

在函数中,名称可以独立进行重新绑定,而不影响外部绑定的效果,这种特性叫做名称作用域:

  1. a = 1
  2. def func():
  3. a = 2
  4. return a
  5. func() #
  6. a #

每个函数都有自己独立的作用域,但与C/C++不同,复合语句没有自己独立的作用域。函数中可以嵌套定义其他函数,这些函数也有自己的作用域:

  1. a = 1
  2. def func1():
  3. a = 2
  4. def func2():
  5. a = 3
  6. return a
  7. return (a, func2())
  8. func1() # (2,3)
  9. a #

最外层的作用域叫做global作用域,对于一个最外层的函数来说,这是这个函数定义的模块。

名称可以同时存在在多个不同的作用域中,在Python的函数中使用名称时,名称解析到具体哪一个作用域,服从下面的规则:

  1. 一旦这个函数中存在对某个名称的绑定,则在这个函数中,名称会解析到本函数的作用域
  2. 否则,依次考虑更高一层的作用域,直到这个名称会被解析到这个作用域(或者说,在这一级作用域中,存在对这个名称的绑定) ,或者到达global作用域为止。

“存在对某个名称的绑定”,包括赋值语句,也包括之前提到的特殊绑定,以及之后会提到的参数绑定。注意只要存在这样的绑定的语句,哪怕这个语句从未执行,也会导致作用域解析的变化,换句话来说,名称解析到哪个作用域,是函数定义的时候就决定了的:

  1. a = 2
  2. def func():
  3. if False:
  4. a = 1
  5. return a # UnboundLocalError

然而,具体的解析到值的过程,则是在语句运行时才进行的,这个需要着重注意:

  1. def func():
  2. a = 1
  3. def func2():
  4. return a
  5. a = 2
  6. return func2()
  7.  
  8. func() #

初学者经常犯的错误就是这样的:

  1. def multipliers(n):
  2. funcs = []
  3. for i in range(0,n):
  4. def _func(b):
  5. return i * b
  6. funcs.append(_func)
  7. return funcs
  8.  
  9. ms = multipliers(10)
  10. ms[2](3) # 27, 9 * 3

正确理解了其中的原理之后,这个现象就一点也不难理解了:i在运行时才解析,而运行时,循环已经结束了,所以i是9

每个函数都有自己的global作用域,它是函数定义的Python文件(或者说模块)。它实际上是模块命名空间与__builtins__命名空间两层,其中__builtins__对所有的模块都生效。

参数绑定

对于函数来说,函数的参数的绑定是比较特殊的一个过程,它在调用的时候进行绑定,但是是将传入的参数值,在函数自己的作用域中,绑定到参数名称:

  1. a = 1
  2. def func(a):
  3. return a
  4. func(2) #
  5. a #

它服从一切名称绑定的作用域规则,可以重新绑定,可以del,可以被子作用域解析。

参数可以有默认值,有默认值的情况下,可以理解为实际的参数绑定之前,预先将这些名称绑定到了对应的值,如果调用时指定了新值则重新绑定到新值。

如果既没有默认值、也没有在调用时绑定,就会发生名称未绑定的错误。

小结

  • Python可以运行表达式,也可以将表达式的值绑定到某个名称
  • 绑定的形式有普通绑定,特殊绑定,参数绑定
  • 绑定与作用域的规则:
    • 默认绑定到当前作用域
    • 解析名称时,会解析到最近一层绑定该名称的作用域
    • global/nonlocal可以修改这个特性
    • 定义时决定解析到哪个作用域;运行时决定解析的值

前面这么多内容,整理成规则却非常简洁,有没有感受到Python的确是很协调、很纯粹的语言呢?

后记

本文是从面向初学者的一个演示文稿中整理而来,这是前一部分,后一部分讲面向对象。演示文稿当中的一部分更基础的内容,考虑到受众原因没有在这篇文章中提到。

理解Python的规则的另一种方法是了解对象的内部构造,比如code对象内部有哪些成员,cellvars和freevars各代表什么,__closure__属性以及实现,对象的__dict__与属性的关系,等等,有一定编程基础的也推荐这种学习方式。

(转)Python学习笔记系列——Python是一种纯粹的语言的更多相关文章

  1. python学习笔记系列----(一)python简介

    一个月前,就按下决心要系统的学习下python了,虽然之前有学习过java,学习过c++,也能较为熟练的使用java做自动化测试看懂c++里的业务逻辑,但是实际上有那么多的东西自己还是不清楚,今天下定 ...

  2. Python学习笔记系列

    1.小甲鱼 python 学习系列笔记

  3. python学习笔记系列----(八)python常用的标准库

    终于学到了python手册的最后一部分:常用标准库.这部分内容主要就是介绍了一些基础的常用的基础库,可以大概了解下,在以后真正使用的时候也能想起来再拿出来用. 8.1 操作系统接口模块:OS OS模块 ...

  4. [Python学习笔记1]Python语言基础 数学运算符 字符串 列表

    这个系列是我在学习Python语言的过程中记录的笔记,主要是一些知识点汇总,而非学习教程,可供有一定编程基础者参考.文中偏见和不足难以避免,仅供参考,欢迎批评指正. 本系列笔记主要参考文献是官网文档: ...

  5. python 学习笔记 9 -- Python强大的自省简析

    1. 什么是自省? 自省就是自我评价.自我反省.自我批评.自我调控和自我教育,是孔子提出的一种自我道德修养的方法.他说:“见贤思齐焉,见不贤而内自省也.”(<论语·里仁>)当然,我们今天不 ...

  6. python 学习笔记一——Python安装和IDLE使用

    好吧,一直准备学点啥,前些日子也下好了一些python电子书,但之后又没影了.年龄大了,就是不爱学习了.那就现在开始吧. 安装python 3 Mac OS X会预装python 2,Linux的大多 ...

  7. python学习笔记(一):python简介和入门

    最近重新开始学习python,之前也自学过一段时间python,对python还算有点了解,本次重新认识python,也算当写一个小小的教程.一.什么是python?python是一种面向对象.解释型 ...

  8. python学习笔记(python简史)

    一.python介绍 python的创始人为吉多·范罗苏姆(Guido van Rossum) 目前python主要应用领域: ·云计算 ·WEB开发 ·科学运算.人工智能 ·系统运维 ·金融:量化交 ...

  9. python学习笔记(1)--python特点

    python诞生于复杂的信息系统时代,是计算机时代演进的一种选择. python的特点,通用语言,脚本语言,跨平台语言.这门语言可以用于普适的计算,不局限于某一类应用,通用性是它的最大特点.pytho ...

随机推荐

  1. WPF ListView 使用GridView 带有Header 以及点击header排序 sort

    ListView: <ListView x:Name="lvFiles" VerticalAlignment="Stretch" Background=& ...

  2. oracle lz047中的REGEXP_LIKE(cust_first_name,'[[:digit:]]')) .

    转自http://blog.csdn.net/dream19881003/article/details/6680982 今天在看OCP题库的时候有一道题是考字段约束的,意思是要在表CUSTOMERS ...

  3. apache ftp server的简单入门(properties验证)

    Apache FTPServer:(开源) Apache FTPServer是一个100%纯Java的FTP服务器. 它的设计是基于现有的开放式协议的完整和便携式FTP服务器引擎解决方案.FTPSer ...

  4. 实现自定义Session

    1. 回话状态接口 /// <summary> /// 会话状态策略接口 /// </summary> public partial interface ISessionStr ...

  5. python学习手册中的一些易忘的点(前三部分)

    1.ubuntu下让python脚本可直接运行: test.py文件(后缀可省)#!/usr/bin/pythonprint('wwwww') sudo chmod +x ./test.py (sud ...

  6. 解决升级PHP7.1后,发邮件时提示“fsockopen(): Peer certificate CN=`xxx.xx.com' did not match expected CN=`113.x.xx.98”

    把项目环境升级到PHP7.1后,发现在不使用SSL时可以使用IP发邮件,可设置成SSL时就只能使用hostname发送,PHP提示的错误信息大致意思是说,IP与hostname无法通过SSL验证,修改 ...

  7. 【翻译&转载】shader的导数函数介绍

    原文链接:http://www.aclockworkberry.com/shader-derivative-functions/ 他人的翻译:http://blog.sina.com.cn/s/blo ...

  8. armel和armhf区别

    出于低功耗.封装限制等种种原因,之前的一些ARM架构处理器因为内部资源宝贵,加入浮点运算单元是十分奢侈的,因为需要额外的软件实现.之前的ARM处理器架构是什么样的?(http://www.cnblog ...

  9. CentOS6.4 下安装 MySql5.5.13

    1.卸载系统自带的MySql 1.1.查看该操作系统上是否已经安装了mysql数据库 [root@xhTest-1 ~]# rpm -qa | grep mysql 1.2.删除原mysql数据库 1 ...

  10. 从零开始导入gradle项目

    需要jdk1.8,idea17以上,电脑安装gradle(配置环境变量,与配置java类似),用git的push命令下项目,开始操作 gradle配置本地仓库位置:添加环境变量GRADLE_USER_ ...