Python 基础1—基本概念

1 基本概念

要想较好的理解 Python,做到知其然知其所以然,而非生搬硬套,就需要对一些基本概念有理解,所以不会一上来就介绍变量、函数、循环,而是先对程序语言的的一些基础概念进行介绍。不感兴趣的可以直接跳过。内容主要来源于 python 帮助文档的学习,有余力的同学建议能对python 官网上的帮助文档做下学习,个人认为比很多python 书籍都写的好很多。

词法分析与解析

不管是人言还是兽语都是在遵循预先定义/形成的规则,交流过程基本上可以分成两部分:

  • 标记、组织标记、识别标记:词语,发音
  • 理解标记:对其中的语义进行理解

计算机在处理一段代码时也遵循这个过程:

  • 词法分析(lexical analyzer):对代码中的符号进行识别,得到一堆标记(token)。
  • 语义理解(parser):对标记进行处理、解析。

词法分析过程中如果出现错误,就会报 SyntaxError 一类的错误,这是比较基础的错误,通常在编译阶段就能发现,也相对比较好修改,当然也很有可能会阻挡初学者很长一段时间,为了能更好地消除这类错误,有必要对 Python 词法分析的一些基本概念进行介绍。

行结构

代码都是由一条条语句构成,我们先看看“猪”是怎么跑的:

from wordcloud import WordCloud
import matplotlib.pyplot as plt

def print_hi(name):
    # Use a breakpoint in the code line below to debug your script.
    print(f'Hi, {name}')  # Press ⌘F8 to toggle the breakpoint.


# Press the green button in the gutter to run the script.
if __name__ == '__main__':
    print_hi('PyCharm')
    filename = "word_cloud.txt"
    with open(filename, encoding="UTF8") as f:
        mytext = f.read()
    word_cloud = WordCloud().generate(mytext)
    plt.imshow(word_cloud, interpolation='bilinear')

    plt.axis("off")
    print_hi('End')

这里先不需要理解代码的含义,对 Python 脚本有一个大概印象即可。 在面对一段代码时,Python 词法分析会先把代码分成一系列逻辑行,这里引出了物理行逻辑行以及一些相关概念:

  • 物理行:由字符加换行符构成。有一点需要注意,换行符不同平台可能不一样,Windows 换行符是 CR LF(ASCII,Python 中表示\r \n),Linux 是 LF;

  • 逻辑行:由一个或者多个物理行构成,多个物理行之间使用连接符号连接(显式的或者隐式的),Python 中显式的连接符“\”,隐式连接符如{}、[]等。一个逻辑行也就是一条语句。

    #显示连接符进行行连接
    if 1900 < year < 2100 and 1 <= month <= 12 \
      and 1 <= day <= 31 and 0 <= hour < 24 \
      and 0 <= minute < 60 and 0<= second <60:        #看起来就是一个合法的日期
          return 1
    #隐式行连接
    month_names = ['January','Februari','Maart',        #These are the 
                  'April', 'Mri', 'Juni',             #Dutch names
                  'Juli','Augustus','September'       #for the months
                  'Oktober','November','December']    #of the year
  • 空行:空格、tab、制表符等构成。

  • 缩进:Python 中使用空白字符进行物理行缩进。缩进目的不仅是为了美观易读,而且是代码分组的方式。Python 从语法上对缩进进行了限制。

  • 空白字符:除了物理行行首表示缩进的场景外,程序其他地方的空格、tab、制表符可以混用,用于分隔各类标记(类似于标点符号,主要是为了方便人阅读)。

其他标记

  • 标识符(identifier)与关键字:通俗讲就是名字,是代码的最小组成单元之一。程序中不可能在每次数据处理时都把数据或者处理方法重复上一遍,所以不管是变量、还是函数、方法等都需要一个名称。标识符可以由字符、数字或者下划线组成,但是第一个字符不能是数字。关键字是保留的标识符,供 Python 程序自身使用。

  • 下划线标识符:除了关键字外,还有一类标识符在 python 中有特别意义,他们就是下划线标识符

    • _* : 单下划线开头,该命名的变量、方法等不会被 from moudle import * 导入
    • __* :双下划线开头,该种方式命名的变量或者方法如果出现在类里面,则会被认为是该类的私有属性;
    • __*__ : 前后双下划线(行内也成为 dunder),这个命名方式也一般是在类中使用,实现运算符重载等特别目的;

    更详细介绍可以参考 Python 帮助文档(以后均简称为帮助文档)“reference/2.3.3 Reserved class of identifiers”

  • 字面量(literals): Literals are notations for constant values of some built-in types,用来表示一个明确类型的明确、固定的值。通俗讲,计算机系统中有一些固有常量,如 Tab、回车、换行或者一些特别符号如转义字符(反斜线\)、数字、8进制表示法\o、16进制表示法\0x 等

  • 运算符(operators):如下标记为运算符

    + - * ** / // % @ << >> & | ^ ~ : = < > <= >= == !=

    运算符可以分为算数运算符、比较运算符、逻辑运算符、位运算符几类:

    • 算数运算符:加减乘除,取整//,取余%,指数等**;
    • 比较运算符:大于>,小于<,等于==,不等于!=,大于等于>=,小于等于<=,是is、不是is not等;
    • 逻辑运算符:与&,或|,非^
    • 位运算符:左移<<,右移>>
  • 分隔符(delemiters):如下标记被用作语法中的分隔符

    ( ) [ ] { } , : . ; @ = -> += -= *= /= //= %= @= &= |= ^= >>= <<= **=

赋值操作符从词法上分析属于分隔符,但是会同样执行运算。单引号、双引号、#、转义字符有特别含义,不属于分隔符,美元符号$与问号?在 Python 中没有定义。

  • 表达式(expression):可以被求值的语法片段。如函数调用、属性访问、运算符操作等。
  • 语句(statement):由表达式或者关键字构建的代码块(如 if、for、def 等)。赋值操作是语句。

对象与数据

Python 是一个号称完全的面向对象的程序语言,在 Python 中“万物皆对象”,python 中所有的元素都可以被看做是对象(或者对象之间的关系),对象是对数据的抽象,由三部分构成:

  • id: 是对象的“身份证号”(可以认为就是对象在内存中的地址),对象一旦创建后其 id 就不会再改变,可以使用 id(x)这个内建函数来查看对象的 id值,也可以使用‘is’运算符来判断两个变量所代表的对象的 id 是否一致;
  • type: 对象类型,决定了对象的行为,比如可以存取的值,支持的操作等(types affect almost all aspects of object behavior)。我们可以通过内置函数 type(x)来查看对象的类型。与 id 类似,对象一旦“出生”(被创建)之后,其类型就固定不会再变化(所以Python 是强类型语言)。一个类型对象只所以支持某种操作,是因为对象定义了对应的方法,如果使用了对象不支持的操作,就会抛出Attribute Error 或者 Type Error 等类型错误。对象的类型决定了对象支持的操作或者可能的取值。对象有不同类型的主要原因是处理方便。使用内置函数 dir(x)可以查看对象支持的操作。
  • value: 对象值。对象创建之后,如果值可以更改,就是可变对象(mutable),反之就是不可变对象(immutable)。对象是否可变是在其创建时由其类型决定的。数字、字符串、元组等是Python 中最常见的不可变对象,列表、字典是常见的可变对象。

Python 中内建数据类型主要有: 

可以看到 Python 中数据的范围要远超出普通意义上的范围:代码、函数、方法的功能都是数据类型(而不仅是字符串、数字等这些传统意义所指的类型)。

数据按照是否能被“分割”可以分为元数据容器数据

  • 元数据:本身就是最小单位,不能继续分隔了,如数字类型;
  • 容器数据:如果一个对象包含有到其他对象的引用,那么这个对象就属于容器数据(container),可继续分割,因为其本身是通过组合其他类型的数据构成的。元组、列表、字典是一些典型的容器数据类型。对于容器数据类型来说,这些引用也是容器值的一部分,但在大多数情况下,当我们提到容器的值,一般指被包含的对象的值,而不包含被包含对象的 id,但是当我们在谈论容器的可变/不可变时,这个时候我们就仅关注被包含对象的 id。所以虽然元组可以包含列表元素,这个列表的值可以被修改,但是元组仍然是不可变的。容器对象通常都可以通过 for 循环进行内容遍历。

对象创建方法

Python 中有5种数据创建方法:

  1. 使用字面量方式。如100,True 等;
  2. 使用分隔符或者引号。如’abc’,“xyz”,[1,2,3],{'a':1,'b':2,'c':3}
  3. 使用表达式,如列表生成式、字典生成式、生成器表达式等。
  4. 使用内置函数。如 str()、tuple()、list()、dict()、map()、filter()、repr()
  5. 使用类方法。如 str.join()等

对于某种具体数据类型,后2种方法基本上都是有的,前3种需要看具体情况了。另外所有数据类型都会支持如下3种操作:

  • 真值判断
  • 比较
  • 转换为字符串(通过 repr()或者 str())

可哈希

可哈希是指通过 hash 函数可以得到唯一的值:

value = hash(key)

如果 key 发生改变,值也会发生改变。 Python 中不可变的数据类型都是可哈希的,如数字、字符串、元组、自定义类的对象;而可变数据类型都是不可哈希的,如字典、列表、集合等。另外需要注意的是,虽然集合是可变的,但是集合中的元素必需是不可变的(不可哈希),这个是为啥呢?因为可哈希的数据一旦建立就不可修改,但是集合是可修改的(添加、删除等)。

执行模型

程序结构与代码块

Python 程序是由代码块构成,所谓的代码块(code block)是指被做为一个单元运行的代码片段(A block is a piece of Python program text that is executed as a unit )。常见代码块有:

  • 模块、脚本文件
  • 函数
  • 交互模式下的每一条命令
  • 做为参数传递给 eval()、exec()的字符串

代码块是在 excution frame 中执行,excution frame 还包括一些管理信息(调试用等)以及决定代码块执行完成后如何继续(where and how)(A code block is excuted in an excution frame.A frame contais some administrative infomation(used for debugging)and determins where and how exctuiton continues after the code block's excution has completed)

命名与绑定(naming and binding)

名称绑定 我们通过名字引用对象(张三、李四),哪个名字引用哪个对象是通过 name binding 操作来实现的(命名绑定,或者称为名称绑定)。一些典型的名称绑定场景有:

  • import 语句
  • 函数定义、类定义
  • 函数实参传递
  • 标识符赋值
  • for 循环头
  • as 语句(with...as,except...as)
  • del 语句(语义上是 unbind 命名与对象)

除非特别生命为 nonlocal 或者 global,否则命名绑定只在本代码块内有效。如果名称绑定发生在模块级别,那么它就是一个全局变量(严格讲即是全局也是局部)

名称解析 Name resolution,通俗讲就是看到一个名字后确定它是谁。与名称解析相关联的有一个“作用域”(scope)的概念,所谓作用域就是名称生效的范围,个人认为其与名字空间(namespace)是一回事。When a name is used in a code block,it is resolved using the nearest enclosing scope.The set of all such scopes visible to a code block is called the block's environment.如果一个名字解析失败(在namespace 中找不到)就会抛出 NameError 异常。如果当前作用域是在函数内部,当尝试访问一个未被绑定的名字时会抛出 UnboundLocalError 异常(UnboundLocalError 是 NameError 子类)。 A scope defines the visibilty of a name within a block.When a name is used in a code block,it is resolved using the nearest endosing scope.The set of all such scopes visible to a code block is called the block's environment(作用域)

模块的名字空间是在模块首次被导入时创建,主模块的名字是__main__(The namespace for a module is automatically created the first time a module is imported.The main module for a script is always called __main__).

类定义的命名空间 类中定义的名字都会被做为类属性字典,对于 unbound local variables会直接在全局名字空间中查找,在类的代码块中定义的名字,值在类代码块中生效,不能扩展到类方法中,包括列表生成式与迭代器生成式(The namespace of the class definition becomes the attribute dictionary of the class.The scope of names defined in a class block is limited to the class block,it does not extented to the code blocks of methods-this includes comprehnsions and generator expressions since they are implemented using a function scope).因此如下代码会报错: 

class A:
  a = 42
  b = list(a+i for i in range(10))

a = A()

名称绑定发生在定义时,名称解析发生在使用时。

eval 与 exec

The eval() and exec() funcitions do not have access to the full environment for resolving names.Names may be resolved in the local and global namespaces of the caller. The exec() and exec() functions have optional arguments to override the global and local namespaces. if only one namespace is specified,it is used for both


版权声明:本文为super_goalkeeper原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
THE END
< <上一篇
下一篇>>