• 􀁺 语句和语法
  • 􀁺 变量赋值
  • 􀁺 标识符和关键字
  • 􀁺 基本风格指南
  • 􀁺 内存管理
  • 􀁺 第一个 Python 程序

3.1语句和语法

Python 语句中有一些基本规则和特殊字符:

  • 􀁺 井号(#)表示之后的字符为 Python 注释
  • 􀁺 换行 (\n) 是标准的行分隔符(通常一个语句一行)
  • 􀁺 反斜线 ( \ ) 继续上一行
  • 􀁺 分号 ( ; )将两个语句连接在一行中
  • 􀁺 冒号 ( : ) 将代码块的头和体分开
  • 􀁺 语句(代码块)用缩进块的方式体现 (缩进四个空格宽度,避免使用制表符)
  • 􀁺 不同的缩进深度分隔不同的代码块
  • 􀁺 Python 文件以模块的形式组织 (当一个模块变得过大,并且驱动了太多功能的话,就应该考虑拆一些代码出来另外建一个模块.)

  • 注释( # )

    和很多 Unix 脚本类似,Python 注释语句从 # 字符开始,注释可以在一行的任何地方开始,解释器会忽略掉该行 # 之后的所有内容。要正确的使用注释。

  • 继续( \ )

    Python 语句,一般使用换行分隔,也就是说一行一个语句。一行过长的语句可以使用反斜 杠( \ ) 分解成几行,如下例:

      # check conditions
      if (weather_is_hot == 1) and \
      (shark_warnings == 0):
          send_goto_beach_mesg_to_pager()
    
    • 有两种例外情况一个语句不使用反斜线也可以跨行。

      在使用闭合操作符时,单一语句可以 跨多行,例如:在含有小括号、中括号、花括号时可以多行书写。

      另外就是三引号包括下的字符串也可以跨行书写。

    如果要在使用反斜线换行和使用括号元素换行作一个选择,我们推荐使用括号,这样可读性会更好。

  • 多个语句构成代码组(:):

    缩进相同的一组语句构成一个代码块,我们称之代码组。

    像if、while、def 和class 这样 的复合语句,首行以关键字开始,以冒号( : )结束,该行之后的一行或多行代码构成代码组。我们将首行及后面的代码组称为一个子句(clause)。

    代码组由不同的缩进分隔

    代码的层次关系是通过同样 深度的空格或制表符缩进体现的。

    • 核心风格:缩进四个空格宽度,避免使用制表符

      对一个初次使用空白字符作为代码块分界的人来说,遇到的第一个问题是:缩进多大宽度 才合适?两个太少,六到八个又太多,因此我们推荐使用四个空格宽度。

      需要说明一点,不同的文本编辑器中制表符代表的空白宽度不一,如果你的代码要跨平台应用,或者会被不同的编辑器读写,建议你不要使用制表符。

      使用空格或制表符这两种风格都得到了Python 创始人Guido van Rossum 的支持,并被收录到Python 代码风格指南文档。

  • 同一行书写多个语句(;)

    分号( ; )允许你将多个语句写在同一行上,语句之间用分号隔开,而这些语句也不能在这行开始一个新的代码块。

    必须指出一点,同一行上书写多个语句会大大降低代码的可读性,Python 虽然允许但不提倡你这么做。

  • 模块

    每一个Python 脚本文件都可以被当成是一个模块。

    模块以磁盘文件的形式存在。

    当一个模块变得过大,并且驱动了太多功能的话,就应该考虑拆一些代码出来另外建一个模块。

    模块里的代码可以是一段直接执行的脚本,也可以是一堆类似库函数的代码,从而可以被别的模块导入(import)调用。

    模块可以包含直接运行的代码块、类定义、函数定义或这几者的组合。

3.2 变量赋值

  • 赋值运算符

    Python 语言中, 等号(=)是主要的赋值运算符。

    注意,赋值并不是直接将一个值赋给一个变量,尽管你可能根据其它语言编程经验认为应该如此。在Python 语言中,对象是通过引用传递的。在赋值时,不管这个对象是新创建的,还是一个已经存在的,都是将该对象的引用(并不是值)赋值给变量。同样的,如果你比较熟悉C,你会知道赋值语句其实是被当成一个表达式(可以返回值)。不过这条并不适合于 Python, Python 的赋值语句不会返回值。

  • 链式赋值

      >>> y = x = x + 1
      >>> x, y
      (2, 2)
    
  • 增量赋值

      >>> x = 1
      >>> x += 1
      >>> x
      2
    
  • 多元赋值 (采用这种方式赋值时, 等号两边的对象都是元组)

      >>> x, y, z = 1, 2, 'a string'
      >>> x
      1
      >>> y
      2
      >>> z
      'a string'
    

    Python 的多元赋值方式可以实现无需中间变量交换两个变量的值。

3.3 标识符

  • 合法的Python 标识符

    • Python 标识符字符串规则和其他大部分用C 编写的高级语言相似:

      􀁺 第一个字符必须是字母或下划线(_)

      􀁺 剩下的字符可以是字母和数字或下划线

      􀁺 大小写敏感

  • 关键字

  • 内建

    除了关键字之外,Python 还有可以在任何一级代码使用的“内建”的名字集合,这些名可以由解释器设置或使用。

    虽然built-in 不是关键字,但是应该把它当作“系统保留字”,不做他用。

    然而,有些情况要求覆盖(也就是:重定义,替换)它们。

    Python 不支持重载标识符,所以任何时刻都只有一个名字绑定。

    我们还可以告诉高级读者built-in 是__builtins__模块的成员,在你的程序开始或在交互 解释器中给出»>提示之前,由解释器自动导入的。把它们看成适用在任何一级Python 代码的全局变量。

  • 专用下划线标识符

    Python 用下划线作为变量前缀和后缀指定特殊变量。

    总结:

    • 􀁺 _xxx 不用’from module import *‘导入
    • 􀁺 __xxx__系统定义名字
    • 􀁺 __xxx 类中的私有变量名

    • 核心风格:避免用下划线作为变量名的开始

      因为下划线对解释器有特殊的意义,而且是内建标识符所使用的符号,我们建议程序员避免用下划线作为变量名的开始。

      一般来讲,变量名_xxx 被看作是“私有的”,在模块或类外不可以使用

      当变量是私有的时候,用_xxx 来表示变量是很好的习惯。因为变量名__xxx__对Python 来说有特殊含义,对于普通的变量应当避免这种命名风格。

3.4 基本风格指南

注释,文档,缩进,标识符

  • 模块结构和布局

    # (1) 起始行(Unix)

    # (2) 模块文档

    # (3) 模块导入

    # (4) 变量定义

    # (5) 类定义

    # (6) 函数定义

    # (7) 主程序

    • (1) 起始行

      通常只有在类Unix 环境下才使用起始行,有起始行就能够仅输入脚本名字来执行脚本,无需直接调用解释器。

    • (2)模块文档

      简要介绍模块的功能及重要全局变量的含义,模块外可通过 module.doc 访问这些内 容。

    • (3)模块导入

      导入当前模块的代码需要的所有模块;每个模块仅导入一次(当前模块被加载时);函数内部的模块导入代码不会被执行, 除非该函数正在执行。

    • (4)变量定义

      这里定义的变量为全局变量,本模块中的所有函数都可直接使用。

      从好的编程风格角度说, 除非必须,否则就要尽量使用局部变量代替全局变量,如果坚持这样做,你的代码就不但容易维护,而且还可以提高性能并节省内存。

    • (5)类定义语句

      所有的类都需要在这里定义。当模块被导入时class 语句会被执行, 类也就会被定义。类的文档变量是class.doc

    • (6)函数定义语句

      此处定义的函数可以通过module.function()在外部被访问到,当模块被导入时 def 语句会被执行,函数也就都会定义好,函数的文档变量是function.doc

    • (7) 主程序

      无论这个模块是被别的模块导入还是作为脚本直接执行,都会执行这部分代码。通常这里 不会有太多功能性代码,而是根据执行的模式调用不同的函数。

    Figure 3–1 Typical Python file structure

    • 推荐代码风格:主程序调用main()函数

      主程序代码通常都和你前面看到的代码相似,检查 name 变量的值然后再执行相应的 调用(参阅下一页的核心笔记)。

      主程序中的代码通常包括变量赋值,类定义和函数定义,随后检查__name__来决定是否调用另一个函数(通常调用main()函数)来完成该模块的功能。

      主程序通常都是做这些事。(我们上面的例子中使用test()而不是main()是为了避免你在读到核心笔记前感到迷惑。)

      不管用什么名字,我们想强调一点那就是:这儿是放置测试代码的好地方。大部分的Python 模块都是用于导入调用的,直接运行模块应该调用该模块的回归测试代码。

      请记住,绝大部分的模块创建的目的是为了被别人调用而不是作为独立执行的脚本。

      只有一个模块,也就是包含主程序的模块会被直接执行,或由用户通过命令行执行,或作为批处理执行,或由Unix cron 任务定时执行,或通过Web 服务器调用,或通过GUI 执行。

    • 核心笔记:name 指示模块应如何被加载

      如果模块是被导入, name 的值为模块名字

      如果模块是被直接执行, name 的值为 ‘main

3.5 内存管理

  • 􀁺变量无须事先声明
  • 􀁺变量无须指定类型
  • 􀁺 程序员不用关心内存管理
  • 􀁺 变量名会被“回收”
  • 􀁺 del 语句能够直接释放资源

  • 变量定义

    在Python 中,无需显式变量声明语句,变量在第一次被赋值时自动声明。

    和其他大多数语言一样,变量只有被创建和赋值后才能被使用。

  • 动态类型

    Python 中不但变量名无需事先声明,而且也无需类型声明。

    Python 语言中, 对象的类型和内存占用都是运行时确定的。

    尽管代码被编译成字节码,Python 仍然是一种解释型语言。

    在创建--也就是赋值时,解释器会根据语法和右侧的操作数来决定新对象的类型。

    在对象创建后,一个该对象的引用会被赋值给左侧的变量。

  • 内存分配

    作为一个负责任的程序员,我们知道在为变量分配内存时,是在借用系统资源,在用完之 后, 应该释放借用的系统资源。

    Python 解释器承担了内存管理的复杂任务, 这大大简化了应用程序的编写。

    你只需要关心你要解决的问题,至于底层的事情放心交给Python 解释器去做就行了。

  • 引用计数

    每个对象各有多少个引用, 简称引用计数。

    一个引用计数内部跟踪变量,称为一个引用计数器。

    当这个对象不再需要时, 也就是说, 这个对象的引用计数变为0 时, 它被垃圾回收。

  • 增加引用计数

    当对象被创建并(将其引用)赋值给变量时,该对象的引用计数就被设置为1。

    当同一个对象(的引用)又被赋值给其它变量时,或作为参数传递给函数, 方法或类实例 时,或者被赋值为一个窗口对象的成员时,该对象的一个新的引用,或者称作别名,就被创建(则该对象的引用计数自动加1)。

  • 对象的引用计数在

    􀁺 对象被创建

          x = 3.14
    

    􀁺 或另外的别名被创建

          y = x
    

    􀁺 或被作为参数传递给函数(新的本地引用)

          foobar(x)
    

    􀁺 或成为容器对象的一个元素

          myList = [123, x, 'xyz']
    
  • 减少引用计数

    • 当对象的引用被销毁时,引用计数会减小。

      最明显的例子就是当引用离开其作用范围时,这种情况最经常出现在函数运行结束时,所有局部变量都被自动销毁,对象的引用计数也就随之减少。

    • 当变量被赋值给另外一个对象时,原对象的引用计数也会自动减1:

        foo = 'xyz'   #'xyz'  1
        bar = foo     #'xyz'  2
        foo = 123    #'xyz'  1
      
    • 其它造成对象的引用计数减少的方式包括使用 del 语句删除一个变量(参阅下一节), 或者当一个对象被移出一个窗口对象时(或该容器对象本身的引用计数变成了0 时)。

    • 一个对象的引用计数在以下情况会减少:

      • 􀁺 一个本地引用离开了其作用范围。比如 foobar()(参见上一下例子)函数结束时。

      • 􀁺 对象的别名被显式的销毁。

          del y # or del x
        
        1. 􀁺 从现在的名字空间中删除 y
        2. 􀁺 x 的引用计数减一
      • 􀁺 对象的一个别名被赋值给其它的对象

          x = 123
        
      • 􀁺 对象被从一个窗口对象中移除

          myList.remove(x)
        
      • 􀁺 窗口对象本身被销毁

          del myList # or goes out-of-scope
        
  • 垃圾收集

    不再被使用的内存会被一种称为垃圾收集的机制释放。

    虽然解释器跟踪对象的引用计数,但垃圾收集器负责释放内存。

    • 垃垃圾收集器

      圾收集器是一块独立代码,它用来寻找引用计数为0 的对象。

      它也负责检查那些虽然引用计数大于0 但也应该被销毁的对象。

      特定情形会导致循环引用。

      一个循环引用发生在当你有至少两个对象互相引用时,也就是说所有的引用都消失时,这些引用仍然存在,这说明只靠引用计数是不够的。

      Python 的垃圾收集器实际上是一个引用计数器和一个循环垃圾收集器。

      当一个对象的引用计数变为0,解释器会暂停,释放掉这个对象和仅有这个对象可访问(可到达)的其它对象。

      作为引用计数的补充,垃圾收集器也会留心被分配的总量很大(及未通过引用计数销毁的那些)的对象。

      在这种情况下,解释器会暂停下来,试图清理所有未引用的循环。

  • 核心技巧:使用局部变量替换模块变量

    类似 os.linesep 这样的名字需要解释器做两次查询:

    • (1)查找os 以确认它是一个模块,

    • (2)在这个模块中查找 linesep 变量。因为模块也是全局变量,我们多消耗了系统资源。

    如果你在一个函数中类似这样频繁使用一个属性,我们建议你为该属性取一个本地变量别名。变量查找速度将会快很多--在查找全局变量之前,总是先查找本地变量。这也是一个让你的程序跑的更快的技巧: 将经常用到的模块属性替换为一个本地引用。代码跑得更快,而也不用老是敲那么长的变量名了。

3.6 相关模块和开发工具

Python 代码风格指南(PEP8), Python 快速参考和Python 常见问答都是开发者很重要的“工具”。

另外, 还有一些模块会帮助你成为一个优秀的Python 程序员。

  • 􀁺 Debugger: pdb
  • 􀁺 Logger: logging
  • 􀁺 Profilers: profile, hotshot, cProfile