python基础
数据类型
- 整数:在程序中的表示方法和数学上一样。
- 浮点数:小数,数字很大时10用e表示,例如1.23×109要写成1.23e9。
- 字符串:用单引号’ 或双引号" 括起来的任意文本,如果字符串内部含有引号用转义字符即可(例如\’,转义字符\可以表示很多,\n表示换行,\t表示制表符,\本身也要转义,\\表示的字符是\)。
- 布尔值:True和False,可以用and,or,not计算。
- 空值:None,不可以理解为0,0是有意义的,而None就是空值。
变量
- 变量名必须是大小写英文、数字和_的组合,且不能用数字开头。
- 变量不仅可以是数字,还可以是任意数据类型。
- 常量就是不能变的变量,通常用全部大写的变量名表示常量。
字符串
- 在最新的Python3版本中,字符串是以Unicode编码的,即Python的字符串支持多语言。
- 字符串str,字节byte
- 格式化字符串,%s表示用字符串替换,%d表示用整数替换。如果不知道用什么,%s永远起作用,它会把任何数据类型转换为字符串。
- 编码内容太难了[哭]
list
- list是可变的有序集合,可以随时添加和删除其中的元素,可以往list中追加元素到末尾。
- len()函数可以获得list元素的个数,元素位置索引从0开始,最后一个是-1。
- append()函数可以追加元素到末尾。
- insert(i,p)函数可以插入元素p到任意位置i。
- pop(i)可以删除i位置的元素。
- list的元素也可以是另一个list。
tuple
- tuple一旦初始化就不能修改,也没有append(),insert()这样的方法,因为其不可变所以更安全,能使用tuple就不用list。
- 如果tuple里面包含一个list,虽然list不能指向到别的元素只能是list,但是list本身内容可变。
- 定义一个空的tuple,写成()。
- 定义一个只有1个元素的tuple,必须加一个逗号,例如t=(2,)只有一个元素2的tuple。
条件判断和循环
- 条件判断用if语句实现,要接冒号:,后面可以加elif做更细致的判断。
- 一种循环是for…in…循环,依次吧list或tuple里面的元素迭代出来。
- break语句可以在循环过程中直接退出循环,而continue语句可以提前结束本轮循环。
dict和set
- dict字典,使用键-值(key-value)存储,查找速度非常快,一个key只能对应一个value,如果key不存在dict就会报错。
- 删除key用pop()的方式,其value也会一起删除。
- set和dict类似,也是一组key的集合,但不存储value,所以set中没有重复的key。
- add(key)方法可以添加元素到set中。
- remove(key)方法可以删除元素。
函数
- 调用函数:python中内置很多函数,知道函数的名称和参数就可以直接调用。
- 定义函数:定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回。
- 空函数:如果想定义一个什么事也不做的空函数,可以用pass语句,虽然pass语句什么都不做,但是可以用来当做占位符,
def nop():
pass
- 参数检查:参数个数不对,系统可以自己检查,但是类型不对无法检查。
- 函数可以返回多个值,也就是tuple。
- 函数的参数除了正常的必选参数以外,还有默认参数、可变参数和关键字参数。默认参数就是把参数设置一个默认值,如果不输入其他值就是默认值。可变参数就是传入的参数个数可变,定义可变参数在参数前面加一个*即可,调用的时候不需要组装出一个list或者tuple。关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数,命名关键字参数必须传入参数名。
- 递归函数就是一个函数在内部调用自身本身,优点是定义简单逻辑清晰,缺点是存在栈溢出的问题(听不懂什么意思= =),可以通过尾递归防止栈溢出。
def person(name, age, *, city='Beijing', job):
print(name, age, city, job)
>>> person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer
以上,*后面的city和job是关键字参数,其中city具有默认值,调用时可以不传入city的参数,输入engineer时必须同时输入参数名job。
高级特性
切片
- L[1:3]表示从索引1取到3(共两个),如果从索引0开始0可以省略L[:3],python支持倒数切片L[-2:]表示从倒数第二个取到最后,L[-2:-1]表示从倒数第二个取到倒数第一个(只有一个)。
>>> L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
>>> L[0:3]
['Michael', 'Sarah', 'Tracy']
>>> L[:3]
['Michael', 'Sarah', 'Tracy']
>>> L[-2:]
['Bob', 'Jack']
>>> L[-2:-1]
['Bob']
- 如果L[:10:2]表示前10个每2个取一个,L[::5]表示所有的每5个取一个。
迭代
- 如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)
- 一般情况下dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items()。
列表生成式
- python内置的可以创建list的生成式
- 写列表生成式时,把要生成的元素放到前面,后面跟for循环,就可以把list创建出来,还可以使用两层循环(三层以上很少用)。
生成器generator
- 要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator。
- 可以通过next()函数获得generator的下一个返回值,但一般不会用next()而是用for循环进行迭代。
- 比较下面两个斐波拉契数列代码,第二个是generator,也就是说如果函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return 'done'
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return 'done'
- 生成器部分有点难…
函数式编程
高阶函数
- map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
- reduce把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,效果就是reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
- filter()函数用于过滤序列
- sorted()函数可以对list进行排序,还可以接收一个key函数来实现自定义的排序
返回函数
匿名函数
- 关键字lambda表示匿名函数,冒号前面的x表示函数参数
- 用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突
- 匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数
装饰器
- 看不太懂
偏函数
- functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义
模块
使用模块
- python内置了很多模块,安装以后就可以使用很方便,导入模块用import语句。
- 正常的函数和变量名是公开的(public),可以被直接引用,有的函数我们希望只在模块内部使用而不给被人使用,通过下划线_前缀来实现,类似_xxx和__xxx这样的函数或变量就是非公开的(private),不应该被直接引用。
安装第三方模块
- 在Python中,安装第三方模块,是通过包管理工具pip完成的
- 由于我装了pycharm这块就没仔细看 [尴尬]
面向对象编程(这部分好像有点难)
类和实例
- 类是抽象的模板,比如Student类
- 定义类是通过class关键字,class后面紧接着是类名,类名通常是大写开头的单词,如下定义了Student类
class Student(object):
pass
- 定义好类以后,就可以创建实例,创建实例是通过类名+()实现的,下面bart就是student的一个实例
bart = Student()
- 由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把name,score等属性绑上去(init前后有两个下划线)
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
访问限制
- 如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__
- 接上,name,score属性,加上下划线以后就无法从外部访问bart.__name和bart.__score了
- 如果外部代码要获取name和score,可以给Student类增加get_name和get_score这样的方法
- 又要允许外部代码修改score,可以再给Student类增加set_score方法
class Student(object):
...
def get_name(self):
return self.__name
def get_score(self):
return self.__score
def set_score(self, score):
self.__score = score
继承和多态
- 当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)
- 继承有什么好处?最大的好处是子类获得了父类的全部功能,第二个好处需要我们对代码做一点改进
- 可以画一下继承树
获取对象信息
- 判断对象类型,使用type()函数
- isinstance()可以判断一个对象是否是该类型本身,或者位于该类型的父继承链上,还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
- 如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list
实例属性&类属性
- 给实例绑定属性的方法是通过实例变量,或者通过self变量
- 当我们定义了一个类属性后,这个属性虽然归类所有,但类的所有实例都可以访问到
- 在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性
>>> class Student(object):
... name = 'Student'
...
>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student
面向对象高级编程
使用__slots__
- 正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性
- 如果我们想要限制实例的属性,可以在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性
- __slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的
使用@property
- 可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性
- 具体怎么用没太看懂 [捂脸]
多重继承
通过多重继承,一个子类就可以同时获得多个父类的所有功能
定制方法
Python的class允许定义许多定制方法,可以让我们非常方便地生成特定的类,详情参见python官网文件定制方法
枚举类和元类
- 枚举类
- metaclass直译为元类,先定义metaclass,就可以创建类,最后创建实例,因此metaclass允许你创建类或者修改类
- 这块要再看看==
错误、调试和测试
错误处理
- 在程序运行的过程中,如果发生了错误,可以事先约定返回一个错误代码,这样,就可以知道是否有错,以及出错的原因
- python内置了一套try…except…finally…的错误处理机制
- 当我们认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。
- 如下给出一个例子:
try:
print('try...')
r = 10 / 0
print('result:', r)
except ZeroDivisionError as e:
print('except:', e)
finally:
print('finally...')
print('END')
5. 出错的时候,一定要分析错误的调用栈信息,才能定位错误的位置。
- 记录错误
- 抛出错误
调试
- 出现错误后,第1种方法就是用print()来检查错误,但坏处是以后还要删除
- 第2种方法是用断言assert来替代,例如下面assert的意思是,表达式n != 0应该是True,否则,根据程序运行的逻辑,后面的代码肯定会出错。assert的好处后期可以启动Python解释器,用-O(大写字母O)参数来关闭assert。
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n
def main():
foo('0')
- 第3种方式是把print()替换为logging,logging不会抛出错误而且可以输出到文件,logging.info()就可以输出一段文本(logging是终极武器,比较好用)
import logging
s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
- 第4种方式是启动Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态,但是略麻烦。
- 第5种方法是在可能出错的地方放一个pdb.set_trace(),就可以设置一个断点,
- 最后一个是IDE功能,可以比较爽地设置断点、单步执行
单元测试
- 编写单元测试需要引入Python自带的unittest模块