作者:cedar(https://www.jianshu.com/writer#/notebooks/28559629/notes/32438329)
1、python交互模式与命令行模式
直接输入 python 进入交互模式,相当于启动了 Python 解释器。可以直接编写python代码输出结果。
命令行模式需要切换到python文件所在路径用python *.py进行运行。路径切换cd+空格+路径。
在Max和Linux条件下还可以像.exe文件那样直接运行.py文件。方法是在.py 文件的第一行加上一个特殊的注释:
#!/usr/bin/env python3
print('hello, world')
然后,通过命令给 hello.py 以执行权限:
$ chmod a+x hello.py
2、输入输出
输入:
Name=input(‘请输入您的姓名:’)
输出:
Print(‘Hello,’name)
3、python基础
python采用缩进方式,当语句以冒号:结尾时,缩进的语句视为代码块。
Python程序大小写敏感。
整数和浮点数在计算机内部存储的方式是不同的,整数运算永远是精确的,而浮点数运算则可能会有四舍五入的误差。
对于字符串里边的‘和“,可以用转义字符\来标识。\n 表示换行, \t 表示制表符,字符\本身也要转义,所以\\表示的字符就是\。Python 还允许用 r''表示''内部的字符串默认不转义。
如果字符串内部有很多换行,用\n 写在一行里不好阅读,Python 允许用'''...'''的格式表示多行内容。
空值是 Python 里一个特殊的值,用 None 表示。 None 不能理解为 0,因为 0 是有意义的,而 None 是一个特殊的空值。
//,称为地板除,两个整数的除法仍然是整数。%可以取余。
Python 的整数和浮点数没有大小限制,但是超出一定范围就直接表示为 inf(无限大)。
4、字符编码
ASCII、 Unicode 和 UTF-8 的关系:
在计算机内存中,统一使用 Unicode 编码,当需要保存到硬盘或者需要传输的时候,就转换为 UTF-8 编码。
在最新的 Python 3 版本中,字符串是以 Unicode 编码的。
Python 提供了 ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符。
由于 Python 的字符串类型是 str,在内存中以 Unicode 表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str 变为以字节为单位的 bytes。
Python 对 bytes 类型的数据用带 b 前缀的单引号或双引号表示。
以 Unicode 表示的 str 通过 encode()方法可以编码为指定的 bytes。
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把 bytes 变为 str,就需要用 decode()方法:
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'
len()函数计算的是 str 的字符数,如果换成 bytes, len()函数就计算字节数。
1 个中文字符经过 UTF-8 编码后通常会占用 3 个字节,而 1 个英文字符只占用 1 个字节。
输出格式化的字符串:%运算符就是用来格式化字符串的。在字符串内部, %s表示用字符串替换, %d 表示用整数替换,有几个%?占位符,后面就跟几个变量或者值,顺序要对应好。
5、使用list和tuple
如果要取最后一个元素,除了计算索引位置外,还可以用-1 做索引,直接获取最后一个元素。
list 是一个可变的有序表,所以,可以往 list 中追加元素到末尾。
>>> classmates.append('Adam')
>>> classmates
['Michael', 'Bob', 'Tracy', 'Adam']
也可以把元素插入到指定的位置,比如索引号为 1 的位置:
>>> classmates.insert(1, 'Jack')
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy', 'Adam']
要删除 list 末尾的元素,用 pop()方法:
>>> classmates.pop()
'Adam'
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy']
要删除指定位置的元素,用 pop(i)方法,其中 i 是索引位置:
>>> classmates.pop(1)
'Jack'
>>> classmates
['Michael', 'Bob', 'Tracy']
tuple 和 list 非常类似,但是 tuple 一旦初始化就不能修改。
只有 1 个元素的 tuple 定义时必须加一个逗号,,来消除歧义。
tuple所谓的“不变”是说, tuple 的每个元素,指向永远不变。
6、条件判断:
if <条件判断 1>:
<执行 1>
elif <条件判断 2>:
<执行 2>
elif <条件判断 3>:
<执行 3>
else:
<执行 4>
input()返回的数据类型是 str, str 不能直接和整数比较。int()函数可以将其转换为整数。
7、循环
Python提供一个 range()函数,可以生成一个整数序列,再通过 list()函数可以转换为 list。比如 range(5)生成的序列是从 0 开始小于 5 的整数。
For循环
While循环
8、使用dict和set
Python 内置了字典: dict 的支持, dict 全称 dictionary,在其他语言中也称为 map,使用键-值(key-value)存储,具有极快的查找速度。
通过 dict 提供的 get 方法,如果 key 不存在,可以返回 None,或者自己指定的 value。
要删除一个 key,用 pop(key)方法,对应的 value 也会从 dict 中删除。
dict 是用空间来换取时间的一种方法。
set 和 dict 类似,也是一组 key 的集合,但不存储 value。重复元素在 set 中自动被过滤。
通过 add(key)方法可以添加元素到 set 中,可以重复添加,但不会有效果。通过 remove(key)方法可以删除元素。
set 可以看成数学意义上的无序和无重复元素的集合,因此,两个 set 可以做数学意义上的交集、并集等操作。
set 和 dict 的唯一区别仅在于没有存储对应的 value,但是, set 的原理和dict 一样,所以,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证 set 内部“不会有重复元素”。
9、函数
在 Python 中,定义一个函数要使用 def 语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用 return 语句返回。
如果想定义一个什么事也不做的空函数,可以用 pass 语句。
Python 的函数返回多值其实就是返回一个 tuple,但写起来更方便。
设置默认参数时,有几点要注意:
一是必选参数在前,默认参数在后,否则 Python 的解释器会报错。
二是如何设置默认参数。当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。
默认参数必须指向不变对象!
定义可变参数和定义一个 list 或 tuple 参数相比,仅仅在参数前面加了一个*号。
可变参数允许你传入 0 个或任意个参数,这些可变参数在函数调用时自动组装为一个 tuple。而关键字参数允许你传入 0 个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个 dict。
和可变参数类似,也可以先组装出一个 dict,然后,把该 dict 转换为关键字参数传进去。
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收 city 和 job 作为关键字参数。
def person(name, age, *, city, job):
print(name, age, city, job)
和关键字参数**kw 不同,命名关键字参数需要一个特殊分隔符*, *\后面的参数被视为命名关键字参数。
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错。
在 Python 中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这 5 种参数都可以组合使用,除了可变参数无法和命名关键字参数混合。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数/命名关键字参数和关键字参数。
10、递归函数
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。尾递归是指,在函数返回的时候,调用自身本身,并且, return 语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
11、切片
对这种经常取指定索引范围的操作,用循环十分繁琐,因此, Python 提供了切片(Slice)操作符,能大大简化这种操作。
L[0:3]
表示,从索引 0 开始取,直到索引 3 为止,但不包括索引 3。即索引 0, 1, 2,正好是 3 个元素。
类似的,既然 Python 支持L[-1]
取倒数第一个元素,那么它同样支持倒数切片。
tuple 也是一种 list,唯一区别是 tuple 不可变。因此, tuple 也可以用切片操作,只是操作的结果仍是 tuple。
12、迭代
如果给定一个 list 或 tuple,我们可以通过 for 循环来遍历这个 list 或tuple,这种遍历我们称为迭代。
默认情况下, dict 迭代的是 key。如果要迭代 value,可以用 for value in d.values(),如果要同时迭代 key 和 value,可以用 for k, v in d.items()。
Python 内置的 enumerate 函数可以把一个 list 变成索引-元素对,这样就可以在 for 循环中同时迭代索引和元素本身:
>>> for i, value in enumerate(['A', 'B', 'C']):
... print(i, value)
...
0 A
1 B
2 C
13、列表生成式
列表生成式即 List Comprehensions,是 Python 内置的非常简单却强大的可以用来创建 list 的生成式。
>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
for 循环后面还可以加上 if 判断,这样我们就可以筛选出仅偶数的平方:
>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]
还可以使用两层循环,可以生成全排列:
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
14、生成器与迭代器
在 Python 中,这种一边循环一边计算的机制,称为生成器: generator。
要创建一个 generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个 generator。
如果要一个一个打印出来,可以通过 next()函数获得 generator 的下一个返回值。我们创建了一个 generator 后,基本上永远不会调用 next(),而是通过 for 循环来迭代它,并且不需要关心 StopIteration 的错误。
如果一个函数定义中包含 yield 关键字,那么这个函数就不再是一个普通函数,而是一个 generator。
注意区分普通函数和 generator 函数,普通函数调用直接返回结果,generator 函数的“调用”实际返回一个 generator 对象。
生成器都是 Iterator 对象,但 list、 dict、 str 虽然是 Iterable,却不是Iterator。把 list、 dict、 str 等 Iterable 变成 Iterator 可以使用 iter()函数:
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
Python 的 Iterator 对象表示的是一个数据流, Iterator 对象可以被 next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration 错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过 next()函数实现按需计算下一个数据,所以 Iterator 的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator 甚至可以表示一个无限大的数据流,例如全体自然数。而使用list 是永远不可能存储全体自然数的。
15、高阶函数
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
def add(x, y, f):
return f(x) + f(y)
16、map和reduce
map()函数接收两个参数,一个是函数,一个是 Iterable,map 将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator 返回。
>>> def f(x):
... return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]
map()传入的第一个参数是 f,即函数对象本身。由于结果 r 是一个Iterator, Iterator 是惰性序列,因此通过 list()函数让它把整个序列都计算出来并返回一个 list。
reduce 把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数, reduce 把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
17、filter与sorted
Python 内建的 filter()函数用于过滤序列。和 map()类似, filter()也接收一个函数和一个序列。和 map()不同的时,filter()把传入的函数依次作用于每个元素,然后根据返回值是 True 还是 False 决定保留还是丢弃该元素。
我们给 sorted 传入 key 函数,即可实现忽略大小写的排序:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']
要进行反向排序,不必改动 key 函数,可以传入第三个参数 reverse=True。
18、返回函数与匿名函数、装饰器
19、偏函数
functools.partial 就是帮助我们创建一个偏函数的,不需要我们自己定义 int2(),可以直接使用下面的代码创建一个新的函数 int2:
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
所以,简单总结 functools.partial 的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
20、模块
为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在 Python 中,一个.py 文件就称之为一个模块(Module)。
使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中,因此,我们自己在编写模块时,不必考虑名字会与其他模块冲突。
为了避免模块名冲突, Python 又引入了按目录来组织模块的方法,称为包(Package)。
引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突。现在, abc.py 模块的名字就变成了 mycompany.abc,类似的, xyz.py的模块名变成了 mycompany.xyz。请注意,每一个包目录下面都会有一个__init__.py 的文件,这个文件是必须存在的,否则, Python 就把这个目录当成普通目录,而不是一个包。__init__.py 可以是空文件,也可以有 Python 代码,因为__init__.py 本身就是一个模块,而它的模块名就是 mycompany。
21、作用域
在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python 中,是通过_前缀来实现的。
类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author__, __name__就是特殊变量, hello 模块定义的文档注释也可以用特殊变量__doc__访问,我们自己的变量一般不要用这种变量名。
类似_xxx 和__xxx 这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc, __abc 等;
22、模块搜索路径
如果我们要添加自己的搜索目录,有两种方法:
一是直接修改 sys.path,添加要搜索的目录:
>>> import sys
>>> sys.path.append('/Users/michael/my_py_scripts')
这种方法是在运行时修改,运行结束后失效。
第二种方法是设置环境变量 PYTHONPATH,该环境变量的内容会被自动添加到模块搜索路径中。设置方式与设置 Path 环境变量类似。注意只需要添加你自己的搜索路径, Python 自己本身的搜索路径不受影响。
23、面向对象编程
在 Python 中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
Class 是一种抽象概念,比如我们定义的 Class——Student,是指学生这个概念,而实例(Instance)则是一个个具体的 Student。面向对象的设计思想是抽象出 Class,根据 Class 创建 Instance。面向对象的抽象程度又比函数要高,因为一个 Class 既包含数据,又包含操作数据的方法。
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量 self,并且,调用时,不用传递该参数。
数据封装:既然 Student 实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在 Student 类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和Student 类本身是关联起来的,我们称之为类的方法。
访问限制:如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在 Python 中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。
24、继承和多态
在 OOP 程序设计中,当我们定义一个 class 的时候,可以从某个现有的class 继承,新的 class 称为子类(Subclass),而被继承的 class 称为基类、父类或超类(Base class、 Super class)。
继承有什么好处?最大的好处是子类获得了父类的全部功能。当子类和父类都存在相同的 run()方法时,我们说,子类的 run()覆盖了
父类的 run(),在代码运行的时候,总是会调用子类的 run()。这样,我们就获得了继承的另一个好处:多态。
判断一个变量是否是某个类型可以用 isinstance()判断:
>>> isinstance(a, list)
True
在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。
对于一个变量,我们只需要知道它是 Animal 类型,无需确切地知道它的
子类型,就可以放心地调用 run()方法,而具体调用的 run()方法是作用在 Animal、 Dog、 Cat 还是 Tortoise 对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种 Animal 的子类时,只要确保 run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:
对扩展开放:允许新增 Animal 子类;
对修改封闭:不需要修改依赖 Animal 类型的 run_twice()等函数。
对于 Python 这样的动态语言来说,则不一定需要传入 Animal 类型。我们只需要保证传入的对象有一个 run()方法就可以了。
25、获取对象信息
基本类型都可以用 type()判断,如果一个变量指向函数或者类,也可以用 type()判断。
判断基本数据类型可以直接写 int, str 等,但如果要判断一个对象是否是函数怎么办?可以使用 types 模块中定义的常量。
能用 type()判断的基本类型也可以用 isinstance()判断。
如果要获得一个对象的所有属性和方法,可以使用 dir()函数,它返回一个包含字符串的 list,比如,获得一个 str 对象的所有属性和方法。
仅仅把属性和方法列出来是不够的,配合 getattr()、 setattr()以及hasattr(),我们可以直接操作一个对象的状态。
可以传入一个 default 参数,如果属性不存在,就返回默认值。
26、面向对象高级编程
为了达到限制的目的, Python 允许在定义 class 的时候,定义一个特殊的__slots__变量,来限制该 class 实例能添加的属性:
class Student(object):
__slots__ = ('name', 'age') # 用 tuple 定义允许绑定的属性名称
使用__slots__要注意, __slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。