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

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

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

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

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

a = 1
b = 'abc'
c = (a,b)

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

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

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

<target> = <expression>

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

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

表示解构的赋值

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

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

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

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

通常需要注意区别。

Python绑定 vs C赋值

我们看下面的代码:

a = 1
a = "abc"

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

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

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

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

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

del <identifier>

特殊绑定

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

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

import my_module
import my_module as m1
from my_module import my_object
from my_module import my_object as o1

实际上完全等效于:

my_module = __import__('my_module')

m1 = __import__('my_module')

_ = __import__('my_module')
my_object = _.my_object
del _ _ = __import__('my_module')
o1 = _.my_object
del _

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

定义函数和类也是这样:

def my_func():
pass
#重新绑定
my_func = 123
#解除绑定
del my_func

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

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

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

a = 1
def func():
a = 2
return a
func() #
a #

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

a = 1
def func1():
a = 2
def func2():
a = 3
return a
return (a, func2())
func1() # (2,3)
a #

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

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

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

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

a = 2
def func():
if False:
a = 1
return a # UnboundLocalError

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

def func():
a = 1
def func2():
return a
a = 2
return func2() func() #

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

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

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

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

参数绑定

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

a = 1
def func(a):
return a
func(2) #
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. --num 与 num-- 的区别

    递增++和递减--操作符都属于一元操作符. 只能操作一个值的运算符是一元操作符,一元操作符是ECMscript中最简单的操作符. 递增.递减操作符介绍 递增.递减操作符有两个版本:前置型和后置型.顾名 ...

  2. Laravel 支付宝异步通知 419报错

    支付宝在支付是有服务器通知和网页通知,一个在前端展示,一个在后台操作, laravel框架自带csrf_token验证. 所以我们需要把支付的路由跳过验证 可以在中间键的csrf配置中更改

  3. asp.net mvc +easyui 实现权限管理(二)

    一写完后,好久没有继续写了.最近公司又在重新开发权限系统了,但是由于我人微言轻,无法阻止他们设计一个太监版的权限系统.想想确实是官大一级压死人啊, 没办法我只好不参与了 让他们去折腾. 我就大概说一下 ...

  4. the interconversion of String and StringBuilder

    package com.itheima_03; /* * StringBuilder和String的相互转换 * * StringBuilder -- String * public String t ...

  5. webpack必知必会

    细节 url-loader和file-loader是什么关系? file-loader用于将文件路径打包为另一个url,url-loader封装了file-loader.使用url-loader时,只 ...

  6. springmvc源码解析-初始化

    1.      概述 对于Web开发者,MVC模型是大家再熟悉不过的了,SpringMVC中,满足条件的请求进入到负责请求分发的DispatcherServlet,DispatcherServlet根 ...

  7. JavaScript运行机制的学习

    今天在偶然在网上看到一个JavaScript的面试题,尝试着看了一下,很正常的就做错了,然后给我们前端做,哈哈,他居然也顺理成章做的错了,代码大概是这样的 /*1 下面代码会怎样执行?执行结果是什么* ...

  8. 计算机网络通信、线程、tcp、udp通信及信号量等读书笔记

    一.计算机网络 1.什么是计算机网络:把分布在不同地理位置的计算机与专门的网络设备用通信线路互相连成一个规模大.功能强的系统,从而使众多计算机可以方便地互相传递信息.共享软件.硬件.数据信息等.简单来 ...

  9. 给UIScrollView添加category实现UIScrollView的轮播效果

    给UIScrollView添加category实现UIScrollView的轮播效果 大家都知道,要给category添加属性是必须通过runtime来实现的,本教程中给UIScrollView添加c ...

  10. 基于NSString处理文件的高级类

    基于NSString处理文件的高级类 我已经把处理文件的类简化到了变态的程度,如果你还有更简洁的方法,请告知我,谢谢! 使用详情: 源码: // // NSString+File.h // Maste ...