此知识要点,是根据学习廖雪峰phthon3.0教程总结的,所以结构基本和这个教程的结构相同。
背景知识
- python是什么?(1)python是一门编程语言,意味着可以用python编写程序,完成一定的功能;(2)python是一种脚本语言,这就是说,python程序需要在一个解释器中运行,这个解释器把程序翻译成计算机可执行的二进制代码,python的官方解释器叫做CPython.
- 安装python.所谓安装python,实际上主要是安装一个python解释器(CPython,以便使用该解释器执行python程序)和内置类库;除此之外,同时还会安装一个集成开发环境,这个集成开发环境叫做IDLE。
- python的代码库。python的代码库可以分为两类,一类是python内置的代码库,提供了包括网络/文件/GUI/数据库/文本处理等大量的功能,内置代码库在安装python的时候会同时安装;另一类是第三方代码库,在python上有大量的第三方代码库可供使用;
- 启动python.所谓启动python,实际上是启动python解释器(CPython),通过在命令行中输入python启动解释器。python解释器有两种模式,一种是交互式模式,在这种模式下,输入的代码在回车后会立即执行,并显示代码执行结果,在命令行中通过输入python进入交互式模式,输入exit()退出交互式模式;另一种是命令行模式,即在命令行中输入python [文件名.py],直接执行python程序。这里有一个问题,就是python可以用来编写web应用程序,web应用程序的基本功能是处理http请求,python程序是如何运行起来(或者说在上面的哪种模式下)处理http请求的呢?这是由web服务器和框架来启动python程序处理http请求的。所有用脚本语言编写的网站后台都是这种由框架启动的模式;
- python语言的输入和输出。python用variable = input()函数将用户输入保存在变量variable中,用print('hello', variable)函数产生输出;
- python程序需要保存为utf-8编码,在notepad++的Encoding菜单中选择Encode in UTF-8-BOM;
数据类型/变量/语句/运算符
- 强制缩进。(缩进的最佳实践是使用4个空格,而不使用Tab)这个理解起来比较容易,但是却说不上来;以#表示注释;Python区分大小写;当语句以:结束时,其后缩进的代码被视为代码块;
- python中的objects及其idnetity/type/value。objects是python对data的抽象(不懂),在python程序中,所有的data都是由objects表示的(好像懂了一点)。每个objects都有一个identity,一个type和一个value。object的type决定了该object可以支持的操作operations以及该object's value的取值范围(对比C#来讲,我理解object就相当于是一个变量,这个变量由所属的类型(就是class)以及value),变量也有一个identity,可以理解就是该变量在内存中的地址。python中有内置的(build-in)的type(python的build-in是语言本身提供的还是library提供的?),下面说的python的数据类型,就是这些内置的type。
- python根据object的value是否可以改变,将变量分为mutable和inmutable两类。怎么理解“object的value是否可以改变”?当我们在python中给一个object赋值时,实际上python做了两件事:(1)python首先会分配一块内存(第一块内存),用来存储这个object的value;(2)然后会再分配一块内存(第二块内存),这块内存的内容是第一块内存的地址,这块内存代表这个object本身的名字(应该就是这个objects的identity)。这样在“改变”object时,就有两种情况发生:(1)第一块内存存储的内容发生变化;(2)第二块内存的内容发生变化(也就是存储的地址变了,指向另一块内存了)。如果是发生第一种情况,那么这个object的value就是可以改变的,其type就是mutable(可变的),如果发生第二种情况,那么这个object的value就是不能改变的,其type就是inmutable(不可改变的)。
- python的简单数据类型:object的type决定了这个object可以支持的操作以及这个object的可能的取值。python中的数据类型都是class(这个怎么理解?)。
- 整型:class int;0x前缀表示十六进制,其他没有什么特别的;
- 浮点型:class float;也可以用科学技术法表示,其他没有什么特别的;
- 字符型(字符串):class str;(1)单引号或双引号扩起来的任意文本;(2)转义字符\,r''表示其中的字符均不转义;(3)'''...'''(3个引号)用来表示多行内容;以上3点是定义字符串字面量的方式;(4)也可以使用构造函数str()来创建字符串;(5)字符串方法:
- 字节序列:class bytes;python中数据类型是字节序列,不是单个字节。bytes可以通过字面量b'A'(这实际上是A字符ASCII的二进制表示形式,所以b'中'这样是不能定义bytes的,因为'中'字符不能用ASCII表示)、函数bytes()、str.encode('ascii/utf-8等')(同样,如果使用ascii编码,这里的str不能是ascii不能表示的字符,汉字的utf-8编码一般是3个字符,所以'中'.encode('utf-8)得到的是一个长度为3的字节序列)来定义。
- 布尔型:class bool;用True、False表示布尔值(注意大小写);运算符:and( a and b); or(a or b); not(not a);None/空字符串/0/空的字典或List等都会被认为是False;
- 一个特殊的空值None,空值在什么场合下有什么用(类似C#的null)
- 运算符:
- +、-、*、**、/(除法运算符,结果为浮点数,即使能整除也是)、//(地板除,结果为小于除法计算结果的最小整数)、%(取余运算符:得到两个整数相除的余数)
- 变量。有两点注意即可:
- (1)python是一种动态语言,这就是说python中的变量没有固定的数据类型,可以给同一个变量赋任意数据类型的值;
- python中的特殊变量:
- __name__:当在命令行中运行python模块文件时,变量__name__被赋值为字符串'__main__',而在其他地方导入python模块文件时,__name__不会被赋值,所以我们常用 if __name__ == '__main__'来运行只想在命令行中执行时才运行的代码(如测试代码等);
- __author__:
- __doc__:
- 用_或者__作为前缀修饰变量、函数来表示private的含义;
- palceholder
变量的作用域:Python没有提供类似private、public的机制,以下只是习惯用法:
- 常量。事实上,python中没有常量,也就是说python没有任何机制保证一个变量的值不被修改,但约定俗成的,用全部大写的变量名表示常量。
- python字符串和字符编码:
- 对于字符编码规则,我是这样理解的:字符编码规则是定义某种bit序列表示某个字符的规则,也就是使用一个特定的编码规则,可以将一个bit序列解释为一个特定的字符(即解码decode),同样一个特定的字符也会转变为一个特定的bit序列(即编码encode),如果一个bit序列是根据A规则形成的(即编码),然后用另一种规则B解码,那么其结果就是乱码。假如一个存储在磁盘上的文件,在磁盘上就是一个bit序列,这个bit序列表示的是什么含义,要结合该文件使用的字符编码来确定。一个工具软件要想知道这个bit序列的含义,需要1.知道这个文件所使用的字符编码规则;2.根据字符编码规则对这个bit序列进行解释,得到最终的含义。那么工具软件是怎样知道一个文件所使用的字符编码规则呢?好像是操作系统有一个默认的字符编码规则。另外一种可能是:这个bit序列其实是可以自解释的,也就是只能用某种字符编码规则对这个bit进行解释,才能得到有意义的内容(这个好像不太可能);
- python解释器默认好像是用UTF-8对源文件(即存储在磁盘上的bit序列)进行解释的,在遇到源文件中无法用UTF-8解释的内容时(比如在保存文件时用gb2312编码规则,那么源文件中的中文内容就不能用UTF-8解释),就会报错。但是可以在文件开头通过使用# coding=<encoding name>(如 # coding=gb2312)声明源文件使用的字符编码,这样也不会出错。但最好的方法还是把源文件以UTF-8保存,这样python解释器也按照UTF-8解释,这样简单不出错。
- 【Python中的字符串在内存中都是以Unicode(每个字符用两个字节表示)表示的,这样在将字符串内容通过网络传输或保存到磁盘中的时候,需要将Unicode的内容按照某种规则(如UTF-8)编码,得到一个字节序列,这个字节序列可用于传输或保存,这个通过encode('utf-8')转换。同理,python程序接受的字节序列,通过decode('uft-8')可以转化为python中的字符串。】这个其实我没有弄明白。
- 输出格式化的字符串。字符串的格式化是通过%运算符实现的,基本格式是:'hello, 占位符' % (逗号分隔的实际内容)。这里“占位符”的格式比较重要,要记住,很多程序(像log4net等)都使用这个格式。%d:整数;%f:浮点数;%s:字符串;%x:十六机制整数。还可以在%后面用数字表示输出内容所占的长度(内容本身长度不足时用空格(默认)或0(需指定)补足),用%.数字f定义浮点数中小数点后的位数。
- list、tuple、range(它们都是python中的sequence type):
- list(和tuple)是什么?是python内置的type,它们都是sequence type,实际上str、bytes、bytearray都是sequence type。sequence type有一些通用的操作,如:x in s/ x not in s(判断x是否在或不在s中),s + t/s * n/n * s(将s和t连接起来,把s重复n次加入到s中),切片操作s[i:j:k],len(s)/min(s)/max(s),s.index(x[, i[, j]])/s.count(x)(求x在s中的index,出现的次数)等。
- list和tuple都表示元素的一种有序集合。区别在于tuple(叫做元组)一旦初始化后,其元素是不可修改的,“不可修改”的含义是:tuple类型的变量所指向的内存地址的内容是不可改变的,但是这个变量可以指向不同的内存地址(参看上面3的说明),也就是说list是mutable, tuple是inmutable。
- 初始化list的语法是中括号[],如list1 = [ 1, '2', [ 3, '4' ] ],初始化tuple的语法是小括号(),如tuple1 = ( 1, '2', [ 3, '4' ] ),但使用索引值访问其中元素的方法都是中括号[],如list[0], tuple[0];向list中添加元素:list.append();list.insert(i,x);从list中移除元素:list.pop([i]);list.remove(x):移除list中所有等于x的元素;del list[i:j:k];list.clear();llist[i]=x;list.reverse()。这些操作只对mutable的sequence有效。
- 对list进行排序,其方法是:list.sort(key, reverse),或sorted(list, key, reverse),前者是就地排序,也就是改变list的内容,后者是不改变list内容,而是返回一个排序后的新list。其中的参数key是一个function,该function的参数是list的元素,返回值是排序的规则。key可以使用lambda表达式,语法举例如下:list.sort(key = lambda x: x.lower(), reverse = True)等。
- python一个特殊的地方是,可以用负数作为索引值访问其中的元素,-1表示最后一个元素,依次类推;
- 定义只有一个元素的tuple,其语法是tuple = ( 1, ),而非tuple = (1),这是因为小括号()同时是运算符,后一种方式会使得变量tuple等于(1)的运算结果1,而非元组(1);
- range表示一个数字序列:其语法有两种:range(stop);range(start, stop[, step]),给range方法的参数必须是整数,如果省略start,则默认从0开始,如果省略step,则默认步进为1。
- 条件判断的语法:
-
if <条件判断1>:
<执行1>
elif <条件判断2>:
<执行2>
elif <条件判断3>:
<执行3>
else:
<执行4>需要注意的几点:(1)else if的语法是elif;(2)每个条件判断后面必须有个冒号:;(3)缩进的部分就是一个代码块;(4)上面的<条件判断>两边可以有括号,也可以没有括号;
- 循环的语法:
- for ... in ...循环:对于循环n次,则使用range(),如:for i in range(n);
- while循环:while expression: statement block else: statement block;
- break;continue;
- try语句:形式1:try: statement block finally: statement block;形式2:try: statement block (except [expression [ as identifier]]: statement block) else: statement block finally: statement block。except子句用来指定一个或多个异常处理句柄,当try中发生异常时,如果某个exception子句中expression的结果(是个object)和发生的异常兼容(compatible,如果前面表达式结果的object是所发生异常的class或者base class,或者是个tuple,其中包含了与所发生异常兼容的对象)时,则执行该exception子句中的代码。
- dict:几点:(1)dict中的元素是一个key-value对,而不只是一个值,其中的key必须是不可变对象,字符串可以作为key,整数也是可以作为key的;(2)dict的查找和插入速度比list快,这是因为value的内存位置是通过key值来计算的(这也是要求key必须是不可变对象的原因,否则计算出的位置不同,就不能找到value的正确位置);(3)dict的语法是使用花括号{key1:value1, key2:value2},元素用,分隔,key和value之间为:分割,以key为所以存取value,即dict[key]的语法。存入元素d[key] = value,如果该变量中不含此key,则直接加入,否则会覆盖原有值;读取dict中的元素时,如果key不存在,则会报错,所以还可以使用dict.get(key[, default] )的方法获取元素的值,此时如果key不存在,则返回None或指定的默认值;(4)删除dict中的元素使用dict.pop(key)方法。
- set:几点:(1)set是一个不包含重复值的集合;(2)因为要判断其中的值是否相同,所以其中的值必须是不可变对象,除此之外,set和list更像;(3)创建set变量的语法特别在i.有一个set关键字;ii.需要将一个list作为输入集合,如下:s = set([1, 2, 3]);(4)set的add(element)和remove(element)方法;
函数
- 函数的定义:def 函数名( 逗号分隔的参数列表 ): 函数体
- 函数名:
- 参数列表:参数无数据类型,用逗号分隔。
- 返回值:在函数定义中,并不能定义返回值。但在函数体内部用return语句结束函数执行,return 语句可带有(多个)返回值,如果没有明确指定返回值,则函数的返回值为None,有多个返回值的,实际上是返回一个tuple。
- 调用函数:
- 在命令行模式下调用函数:
- 在交互式模式下调用函数:
- 再谈函数参数:
- 位置参数:就是常见的参数形式,没有特别的;
- 默认参数:(1)就是参数有一个默认值,在调用函数时,如果没有为有默认值的参数指定值,则该参数使用此默认值,如:函数power( x, n = 2 ),n即为有默认值的参数;(2)如果有多个有默认值的参数,那么调用此函数的时候,要么全部省略实际参数,要么依顺序提供实际参数,如果需要省略位于较前位置的实际参数值,那么为较后位置的参数提供实际参数时,要使用命名参数的形式,即 参数名 = 值 的形式;(3)默认参数的“坑”:
- 可变参数:是说函数参数的个数是不固定、可变的。定义可变参数的语法是在参数名前添加一个*,如:def calc( *numbers );实际上可变参数和类型为tuple的参数,效果是一样的,只是可变参数使得代码更简洁一些(可以认为是个语法糖),在调用函数的时候可以提供任意个参数;
- 关键字参数:也是函数参数的个数不规定、可变的,只是在调用函数时使用“参数名 = 值”的方式提供多个参数,这些“参数名 = 值”在函数内部将作为形式参数的元素加入到形式参数中(这个形式参数将转变为dict),定义关键字参数的语法是在形式参数名前添加两个*,如:def calc( **numbers )。
- 命名关键字参数:
- 在python中,调用函数的时候,不能传入未经定义的实参(这一点和javascript不一样,后者可以传入任意多的实参,如果在函数定义中无对应的形参,则多余参数将被忽略);
- python的函数参数整这么复杂,必要性是什么?
高级特性
- 切片(slice):切片是指获取list或tuple的一部分(或一个子集),不能对dict或set进行切片操作(因为其中的元素不是按顺序存储的)。切片的语法是:list[n1:n2:n3],其中n1为切片的起始索引,如果省略则为从0开始;n2为结束索引,若省略则为最后一个;n3则表示在起始索引和结束索引的范围内每n3个取一个元素,若省略,则每个元素都取。这个切片就像字符串的substring方法一样。
- 可迭代对象及迭代:迭代就是使用for循环依次访问可迭代对象中的每个元素。
- 什么是可迭代对象呢?
- 如何迭代list/dict/tuple/set/字符串:
- python的for循环非常特别,在for循环中可以引用多个变量,形如:for i, j, k in ...,这是要求in后的可迭代对象中也要有分别对应i,j,k多个变量的内容。
- 列表生成式list Comprehensions:专门用来生成一个(复杂的)list的机制。语法为:[ 对for中变量进行计算的表达式 for 变量列表 in 可迭代对象 if 对for中变量筛选的表达式],如:[ k + ' = ' + v for k, v in dict.items() if v % 2 == 0],这个语法真是足够灵活。
- 生成器generator:生成器可以认为是表示生成一个list的算法(而不像列表生成式那样直接生成了一个列表),list中的元素在需要时(比如访问这个元素时)才会生成(不需要时不生成,减少了内存占用,可表示无限集)。
- 创建生成器:方法一:把列表生成式语法中的[]替换为(),即创建了一个生成器;方法二:使用函数来创建生成器,但是用来创建生成器的函数,除了其中要有yield语句外,还是与一般的函数不同。不同之处在于在这个函数中,yield还需要放在一个循环语句中,这样,这个生成器才能生成多个列表元素。
- 访问生成器generator中的元素:因为generator也是可迭代对象,所以可以使用for来访问generator中的元素: for n in g: print(n)
函数式编程
所谓函数式编程,就是语言中的“函数”也是数据,可以被赋值给变量、作为另一个函数的参数等,总之是在使用数据的场合都可以使用“函数”。
- 高阶函数:就是可以将其他函数作为参数的函数。python内置的几个高阶函数有(这些高阶函数和C#中在集合上定义的扩展方法非常类似,它们的参数都是一个函数和一个可迭代对象,然后将函数作用于可迭代对象中的每个元素,产生结果):
- map(f, list):传入map的函数仅有一个参数,将此函数单独作用在可迭代对象的每个元素上(也就是依次用可迭代对象中的每个元素作为此函数的参数调用,需要注意的是,在函数中没有有关此元素在list中的位置信息,所以如果遇到需在函数中使用元素的位置信息的时候,不要使用高阶函数),其返回值是一个迭代器(可以转变为list而不直接返回list);
- reduce(f, list):传入reduce的函数有两个参数,达到的效果是:reduce(f, [x1, x2, x3, x4]) = f( f( f( x1, x2 ), x3 ), x4 ),reduce的返回结果,是函数的计算结果,而不再是一个迭代器或list;
- filter():传入filter的函数仅有一个参数,此函数依次作用于可迭代对象的每个元素时返回一个bool结果。整个filter的运算结果是一个迭代器,但排除了上面运算结果为false的元素;
- sorted():
- 函数作为返回值和闭包:大概了解是怎么回事,但是还是有些说不清楚,也很难应用起来。
- 匿名函数(在python中就是lambda表达式):语法是:lambda 逗号分隔的参数列表:表达式。匿名函数的返回值就是lambda表达式的值。python中,这里的“表达式”只是一个简单的表达式,不支持复杂的语句块;
- 装饰器:类似于OOP中的装饰器模式,Python直接从语言层面上支持此模式。python中的装饰器可以为一个函数临时增加一些功能。Python是通过下面的步骤实现此模式的:
- 定义一个参数为函数、返回值也是函数的函数,即装饰器函数,在返回的函数中调用传入的参数函数及添加其他功能,也就是返回的函数成为了参数函数的一个包装器;
- 在定义需要临时增加一些功能的函数时,在函数前使用“@装饰器函数名”语法修饰该函数,则在调用此函数时,会转为调用在装饰器函数中定义的包装函数,从而达到临时增加功能的目的;
- 实际上包装器函数和原函数的一些属性还是不同的,如__name__属性等,但python通过functools模块中的wraps函数可以将原函数的属性复制给包装器函数,所以在装饰器函数中要求这么一句@functools.wrap(原函数名);
- 总结起来,装饰器函数有这么几个特征(1)其参数为一个函数;(2)返回值也是一个函数;(3)在返回值函数中调用参数函数并添加其他功能,达到为参数函数临时增加功能的目的;(4)通过“@装饰器函数名”的方式修饰其他函数,从而为该函数增加装饰器中增加的临时功能;(5)装饰器函数中要有这样一个语句:@functools.wrap(原函数名),用于将原函数属性复制给包装器函数;这也太笨拙了吧?
- 偏函数:就是通过为一个函数中的某些参数指定默认值,从而形成一个新函数,这个新函数成为原函数的偏函数。python是通过functools模块中的partial方法做到这一点的,这个作用不太大,等到用到的时候在详细查看其语法吧。
模块
模块是Python中组织源代码的一种机制,一个.py文件就是一个模块,模块名是该.py文件所在的文件夹名与文件的组合,用.分隔,即“文件夹名.文件名”;
用文件夹来组织模块的方式,称为“包(Package)”。可以作为包的文件夹中必须包含一个__init__.py文件(这个__init__.py文件本身可以为空,也可以包含代码,它本身也是一个模块,但它的模块名不是__init__,而是它所在文件夹的名称),否则该文件夹只是一个普通的文件夹而非“包”(“包”类似C#中命名空间的机制)。
使用内置模块
只要安装了Python,内置模块就可以使用。使用模块的第一步是导入模块,语法为import 模块名,如:
import sys
导入模块后,将相当于定义了一个与模块同名的变量sys,使用该变量来引用该模块,如sys.argv,就是引用模块sys中定义的变量argv。
使用第三方模块
使用第三方模块之前,需要首先进行安装。在Python中,是通过包管理工具pip完成第三方模块管理的。
pip命令:
pip search param
pip install param
pip uninstall param
pip list
等
面向对象编程
面向对象编程的内容:类、对象、类和对象的成员、封装、继承、多态;类及其成员的可访问性public/private/protected;构造函数
class Student(object): def __init__(self, name, score):
self.name = name
self.score = score 创建类的实例:
student = Student()
- 定义类的语法:class 类名( 基类 ):,object是所有类的基类,如果上面的定义中省略了基类,则默认从object继承,如:class Student( object ):;
- 构造函数及创建对象:如果定义类时,未显式定义类的构造函数,则类默认拥有一个无参数的构造函数,如果显示定义了构造函数,则此默认构造函数就不再存在。类的构造函数名必须是__init__。创建对象时,传入的参数必须与类的构造函数的参数匹配(self参数除外);
- 类和对象的属性:
- 类属性:(1);(2)可以为类动态添加属性,如:Student.score = 90,这样的属性相当于C#的静态成员,为类动态添加的属性会立即体现在类的对象中;(3)以__开头的属性是private,不能直接访问(这不是约定,而是python的限制),如果需要访问,则需要定义对应的get/set方法;(4)类似__xxx__的属性都是类的特殊变量,但不是private的;
- 对象属性:可以为对象动态添加属性,如student1.score = 80;如果对象的属性与类的属性重名,则对象的属性覆盖类的属性;这个可能和javascript的属性访问机制一样,即从底层开始,找到后即不在继续向上层查找;
- 类的方法:(1)与普通函数的区别在于,类的方法,其第一个参数永远是self(语法是:def methodname(self)),但在调用时不必传入该参数;(2)也可以为类动态添加方法,先定义一个第一个参数是self的方法,动态添加方法的语法是:类名.方法名 = MethodType( 方法名, 类名 )
- 继承和多态:继承和多态总体上来讲和C#一样,区别在于:python中在子类中定义的与父类同名的方法,自动全部是多态的(以__开头的方法除外,C#有覆盖和复写的区别,python没有?),python中有无类似abstract机制?python中有没有protected/protected internal的机制?
- 获取对象信息,使用以下函数:
- type():用来判断对象的类型。python的设计不严谨,这个type()函数就是个很好的例子。type()函数返回值的类型是什么呢?是类。那是什么类呢?他可以和int/str/bool/bytes(对于基本数据类型)、类名(对于一般类)、types模块中定义的常量(如:FunctionType等)比较。是不是太灵活了?
- isinstance():判断某个变量是否为某种或某几种类型,返回值为True或者False;
- dir():获取一个对象所有的属性和方法,返回值是一个包含对象所有属性和方法名的list;
- hasattr()/getattr()/setattr():用于判断一个属性/方法名(字符串表示)是否为一个对象的属性/方法。
- 再谈动态添加属性、方法和类的__slots__属性:
- @property:
多进程和多线程
“多进程/多线程”和“异步”、“并行”的概念有什么区别?
1.重要的是要知道,哪些功能在哪些模块中;
2.Python的文档很难看懂,也许是还没有找到看python文档的正确方法;