Python笔记

Python笔记(未完待续)

Python特点

Python特性

Python彩蛋

在交互解释器里输入import this

Python解释器

有很多种,最常用的是CPython。

解释器是输入一行就执行一行

输入exit()来退出解释器。

在终端里输入python3 Filename.py来执行python文件

在Mac和Linux上可以直接运行.py文件,方法是在.py文件的第一行加上一个特殊的注释:

#!/usr/bin/env python3

然后通过命令给hello.py以执行权限:

$ chmod a+x hello.py

以下一行注释是为了告诉Python解释器,按照UTF-8编码读取源代码,否则可能中文会有乱码

# -*- coding: utf-8 -*-

Python语法

输出

print(),隔开多个输出。遇到逗号会输出一个空格

格式化输出

print()方法
占位符 替换内容
%d 整数
%f 浮点数
%s 字符串
%x 十六进制整数
format()方法

另一种格式化字符串的方法是使用字符串的format()方法,它会用传入的参数依次替换字符串内的占位符{0}、{1}……,不过这种方式写起来比%要麻烦得多:

输入

input()。如:name = input()。又如name = input('please enter your name')

input()返回的是str类型,要进行转换,如int()

注释

#开头

数据类型

整数(int)

浮点数(float)

字符串(str)

('"都可以) 可以用r''表示''里的字符串默认不转义

ord()获取字符的整数表示,chr()将编码转换为对应的字符

encode()decode()方法

len()函数计算的是str的字符数,如果换成bytes,则计算字节数

布尔值(bool)

(TrueFalse, andornot运算)

空值

None表示

列表(list)

classmates = ['Michael', 'Bob', 'Tracy']

可以用len()函数获取list的元素个数,

运用索引访问,从0开始,用-1做索引可以直接获取最后一个元素,一次类推,可以获取倒数第2个、倒数第3个···。

list有序可变:append(element)方法追加至末尾,insert(position,element)方法追插入至指定位置,删除末尾用pop(),删除指定位置的用pop(i)

list里的元素可以不同类型,且可嵌套。

list有sort()方法

元组(tuple)

classmates = ('Michael', 'Bob', 'Tracy')

tuple与list类似,但是tuple一旦初始化就不能修改,即tuple没有append(),insert()方法。

定义一个只有1个元素的tuple得这样定义:t=(1,)

tuple里嵌套的list可变

字典(dict)

(d = {'Michael': 95, 'Bob': 75, 'Tracy': 85})

在其他语言中也称为map,使用键-值(key-value)储存。

一个key只能对应一个value,多次放入会把前面的值冲掉。

in判断key是否存在如>>> 'Thomas' in dget()方法,

如果key不存在就返回None,或者自己指定的value,如d.get('Thomas,-1')

要删除一个key,用pop(key),对应的value也会从dict中删除。

dict的key必须是不可变对象,所以list不能作为key

dictitems()可以同时迭代key和value:

for k,v in d.items():
	print(k,'=',v)

list特点:

  1. 查找和插入的时间随着元素的增加而增加;
  2. 占用空间小,浪费内存很少。

dict特点:

  1. 找和插入的速度极快,不会随着key的增加而变慢;
  2. 需要占用大量的内存,内存浪费多。

set

set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key,重复元素在set中自动被过滤。

要创建一个set,需要提供一个list作为输入集合:

>>> s = set([1, 2, 3])
>>> s
{1, 2, 3}

add(key)方法来添加key,remove(key)方法可以删除元素

s1 & s2:集合交, s1 | s2集合并

数据类型转换

int() float() str() bool()

变量本身类型不固定的语言称之为动态语言,与之对应的是静态语言 返回None的时候Python的交互环境不显示结果 常量名常用大写表示,如PI

Python中有两种除法,/的结果是浮点数,'//‘的结果是整数

取余为%

UTF-8为可变长编码,把一个Unicode字符根据不同的数字大小编码成1-6个字节

程序结构

分支语句

if语句
if <条件判断1>:
    <执行1>
elif <条件判断2>:
    <执行2>
elif <条件判断3>:
    <执行3>
else:
    <执行4>

判断条件还可以简写

if x:
    print('True')

只要x是非零数值、非空字符串、非空list等,就判断为True,否则为False

循环语句

for
names = ['Michael', 'Bob', 'Tracy']
for name in names:
    print(name)

range()函数可以生成一个整数序列,比如range(5)生成的序列是从0开始小于5的整数

sum = 0
for x in range(101):
    sum = sum + x
print(sum)

while
sum = 0
n = 99
while n > 0:
    sum = sum + n
    n = n - 2
print(sum)

breakcontinue与C++中类似。

函数

函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:a = abs

定义函数
def my_abs(x):
    if x >= 0:
        return x
    else:
        return -x

return None可以简写为return

空函数

可以用pass语句,如:

def nop():
	pass

pass也可以放在其他语句里,如:

if condition:
	pass
参数检查
def my_abs(x):
    if not isinstance(x, (int, float)):
        raise TypeError('bad operand type')
    if x >= 0:
        return x
    else:
        return -x

函数可以返回多个值(实质上是tuple),并可按位置赋给相应的变量

默认参数

def power(x,n=2):

必选参数在前,默认参数在后。 不按默认顺序提供参数是,需要把参数名写上。 定义默认参数要牢记一点:默认参数必须指向不变对象!不然不同次的调用会出现不同的结果。

可变参数

def calc(*numbers):相当于传入一个tuple

若已有list和tuple 可这样调用calc(*nums)

关键字参数

def person(name, age, **kw)::会组建一个dict

和可变参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去:

命名关键字参数

如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city和job作为关键字参数。这种方式定义的函数如下:

def person(name, age, *, city, job):
    print(name, age, city, job)

和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符**后面的参数被视为命名关键字参数。

如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:

命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:

命名关键字参数可以有缺省值,从而简化调用:

参数组合

在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

函数递归

尾递归可以进行优化,但大多数编译器和解释器没有进行优化

切片

可以对list,tuple,str操作,相当于截取,如:l[1:3]取索引为1和2的元素组成一个list,还可以设置间隔如:l[0:100;5]每五个取一个

迭代

for…in 可以迭代任何可迭代的对象(如dict)

如何判断是否可以迭代:通过collections模块的Iterable类型判断。 isinstance('abc', Iterable)

Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身

for i,value in enumerate(['A','B','C']):
	print(i,value)

列表生成式(List Comprehension)

list(range(1,11))生成[1,2,3,4,5,6,7,8,9,10]

[x * x for x in range(1,11)]生成 [1,4,9,16,25,36,49,64,81,100]

[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']

生成器(generator)

边循环边计算,把[]变为()就可以了

可以通过next()打印generator的下一个返回值

还可以用for循环迭代

g = (x*x for x in range(10))
for n in g:
	print(n)

如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

迭代器

凡是可作用于for循环的对象都是Iterable类型;

凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

可以使用isinstance()判断一个对象是否是Iterator对象:

集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

Python的for循环本质上就是通过不断调用next()函数实现的

函数式编程

高阶函数(Higher-order-function)

函数可以赋值给变量,函数名也是变量

一个函数接受另一个函数作为参数,这种函数被称为高阶函数。

map()reduce()函数

map()函数接受两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回

reduce()把一个函数作用在一个序列[x1,x2,x3,...]上。reduce把结果继续和序列的下一个元素做累积计算

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

filter

Python内建的filter()函数用于过滤序列。

filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list。

sorted

sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,如:

sorted([36, 5, -12, 9, -21], key=abs)

默认情况下,对字符串排序,是按照ASCII的大小比较的,

要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True

返回函数

函数作为返回值

闭包:返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会变化的变量。

匿名函数

当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))

lambda x: x * x实际上就是:

def f(x):
	return x * x

关键字lambda表示匿名函数,冒号前面的x表示函数参数。

匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

装饰器(Decorator)(在代码运行期间动态增加功能的方式)

本质上,decorator就是一个返回函数的高阶函数。

函数也是个对象,可以通过__name__属性拿到函数的名字

def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper
@log
def now():
    print('2018-2-9')

三层嵌套的decorator

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator
@log('execute')
def now():
    print('2018-2-9')

但此时函数名会出现错误,所以完整写法如下:

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper
偏函数(Partial function)

与数学意义上的偏函数不一样

所以,简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

functools.partial就是帮助我们创建一个偏函数的

import functools 
int2 = functools.partial(int, base=2)

创建偏函数时,实际上可以接收函数对象、*args**kw这3个参数

模块(Module)

在Python中,一个.py文件就称之为一个模块(Module)

为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)。

方法是选择一个顶层包名,比如mycompany。引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突。现在,abc.py模块的名字就变成了mycompany.abc,类似的,xyz.py的模块名变成了mycompany.xyz

请注意,每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是mycompany

类似的,可以有多级目录,组成多级层次的包结构。

作用域

正常的函数和变量名是公开的(public),可以被直接引用

类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途

类似_xxx__xxx这样的函数或变量就是非公开的(private),不应该被直接引用

之所以我们说,private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。

安装第三方模块

在Python中,安装第三方模块,是通过包管理工具pip完成的。

面向对象编程

数据封装、继承和多态是面向对象的三大特点

类和实例(Class and Instance)

在Python中,定义类是通过class关键字:class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。

定义好了Student类,就可以根据Student类创建出Student的实例,创建实例是通过类名+()实现的。如:bart = Student()

由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把namescore等属性绑上去:

class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score

和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。

数据封装

和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同:

访问限制

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问

需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name____score__这样的变量名。

双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:

继承和多态

在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。

继承有什么好处?最大的好处是子类获得了父类的全部功能。由于Animial实现了run()方法,因此,DogCat作为它的子类,什么事也没干,就自动拥有了run()方法:

当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。

所以,在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行

静态语言 vs 动态语言

对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。

对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了

这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()方法,返回其内容。但是,许多对象,只要有read()方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()方法的对象。

获取对象信息
使用type()

判断对象类型

使用isinstance()
使用dir()

如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list

实例属性和类属性

疑惑: python的动态类型是否是用指针实现的

参考:

  1. Python教程
  2. 《Python学习手册》 by Mark Lutz(O’REILLY Press,2011)