Contents
由于项目需要,最近开始学Python。Python是一种面向对象、解释型语言。也是最近非常火的一种语言。Python为我们提供了非常完善的基础代码库,涵盖了网络、文件、GUI、数据库、文本等大量内容。Python非常简单,有一定面向对象知识的同学都可以很快地入门并深入。但是Python也有缺点:一个是运行速度慢;一个是代码不能加密。因为Python是解释型语言,所以代码在执行时会一行一行地翻译成CPU能理解地机器码。这个翻译过程很耗时。我学习Python的目的主要是为了编写爬虫代码和学习机器学习知识。所以对Python的学习程度并不需要达到深层次。我为了快速入门,选择廖雪峰的官方网站中的Python3教程进行学习。本文主要是我学习过程记下的笔记,着重于与java不太一样的地方。个人笔记仅供参考。
Python基础
数据类型和变量
- 整数:十六进制用0x前缀和0-9,a-f表示
- 浮点数:小数,科学计数法:10用e代替;整数和浮点数在计算机内部存储的方式是不同的,整数运算永远是精确的(包括除法),浮点数运算则可能会有四舍五入的误差
- 字符串和编码
(1) 以单引号’或双引号”括起来的任意文本。
(2) 可以用转义字符\来标识’和”。(\n:换行(Python允许使用’’’…’’’格式表示多行内容,eg。’’’a…b…c’’’),\t:制表符,\:表示\,)
(3) r’’表示’’内部的字符默认不转义
(4) 编码:
ASCII编码:1个字节(包括大小写英文字母,数字和一些符号)
Unicode编码:把所有语言都统一到一套编码里,清除乱码问题(多为2字节,把ASCII编码的字符用Unicode编码,在前面补8个0)
UTF-8:把Unicode编码转化为“可变长编码“,因为Unicode编码比ASCII编码需要多一倍的存储空间,若文本全为英文的话,及其不节约。
~~ASCII编码实际上可以被看成是UTF-8编码的一部分。
~~在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就装换为UTF-8编码
(5) Python3版本中,字符串是以Unicode编码,即支持多语言。ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符。
(6) 如果要在网络上传输,或者保存到磁盘上,就需要把字符串变为以字节为单位的bytes,用带b前缀的单引号或双引号表示:x=b’ABC’。encode()将字符变为bytes,decode()将bytes变为字符。
(7) 在操作字符串时,我们经常遇到str和bytes的互相转换。为了避免乱码问题,应当始终坚持使用UTF-8编码对str和bytes进行转换。
(8) 由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码。当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上这两行:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
- 第一行注释是为了告诉Linux/OS X系统,这是一个Python可执行程序,Windows系统会忽略这个注释;
- 第二行注释是为了告诉Python解释器,按照UTF-8编码读取源代码,否则,你在源代码中写的中文输出可能会有乱码。
- 申明了UTF-8编码并不意味着你的.py文件就是UTF-8编码的,必须并且要确保文本编辑器正在使用UTF-8 without BOM编码:
(9) 格式化:%
%d:整数
%f:浮点数
%s:字符串
%x:十六进制整数
%%:%
- 布尔值:True和False;and、or和not运算
在python中,and和or执行布尔逻辑运算时,并不返回布尔值,而是返回他们实际进行比较的值之一。在布尔上下文中从左到右演算表达式的值。对于and,如果布尔上下文中的所有值为真,那么and返回最后一个值;如果布尔上下文中的某个值为假,则and返回第一个假值。对于or,如果有值为真,则返回第一个真值,若所有值都为假,则返回最后一个假值。 - 空值:None,不能理解为0。
- 变量:变量名必须是大小写英文、数字和_的组合。
动态语言:
静态语言:
b:ABC - 常量:通常用全部大写的变量名表示常量
- 1.1+1.1+1.1=3.3000000000003:因为精度损失
条件判断
循环
- for x in ..
- while
列表
- list是一种有序的集合,可以随时添加和删除其中的元素
classmates = [‘Michael’, ‘Bob’, ‘Tracy’] - 用len()函数可以获得list元素的个数,用索引来访问list中每一个位置的元素,记得索引是从0开始的当索引超出了范围时,Python会报一个IndexError错误,所以,要确保索引不要越界,记得最后一个元素的索引是len(classmates) - 1。
- 如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素。
- list元素也可以是另一个list
元祖tuple
- 不可变的列表(指向永远不变);除改变列表内容的方法外,其他方法均适用于元组;因此:索引、切片、len、print可用;append、extend、del等不可用
classmates = (‘Michael’, ‘Bob’, ‘Tracy’) - 因为tuple不可变,所以代码更安全。如果可能,能用tuple代替list就尽量用tuple。
- 定义一个空的tuple:t = ()
- t=(1):定义的不是tuple,而是1这个数,只有1个元素的tuple定义时必须加一个逗号,。t=(1,)
字典
- Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度
my_dict={‘john’:1234,’Mike’:5678,’Bob’:8765}
my_dict[‘Bob’] - 给定一个名字,比如’Michael’:95,dict在内部就可以直接计算出Michael对应的存放成绩的“页码”,也就是95这个数字存放的内存地址,直接取出来,所以速度非常快
- dict内部存放的顺序和key放入的顺序是没有关系的
- 和list比较,查找和插入的速度极快,不会随着key的增加而增加;需要占用大量的内存,内存浪费多。
- 要保证hash的正确性,作为key的对象就不能变。在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key
字典预算符合方法
len(my_dict)
key in my_dict 快速判断key是否为字典中的键:O(1);==my_dict.has_key(key)
for key in my_dict 枚举字典中的键:键是无序的
my_dict.items() 全部的键-值对;返回的键值对是以列表的形式
my_dict.keys() 全部的键
my_dict.values() 全部的值
my_dict.clear() 清空字典
集合
无序不重复元素(键)集;和字典类似,但是无“值”
创建: x=set()
x={key1,key2,...}
添加和删除: x.add(‘body’)
x.remove('body')
集合的运算符: - & | !=
- set的原理和dict一样,所以,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证set内部“不会有重复元素”
中文分词————算法:正向最大匹配(从左到右取尽可能长的词)
(1) 加载词典:lexicon.txtdef load_dic(filename):
f= open(filename) word_dic=set() max_length=1 for line in f: word=unicode(line.strip(),'utf-8') word_dict.add(word) if len(word)>max_len: max_len=len(word) return max_len,word_dict
(2) 正向最大匹配分词
def fmm_word_seg(sent,max_len,word_dict):
begin=0
words=[]
sent=unicode(sent,'utf-8')
while begin<len(sent):
for end in range(begin+max_len,begin,-1):
if sent[begin:end] in word_dict:
words.append(sent[begin:end])
break
begin=end
return words
(3)应用
max_len,word_dict=load_dict('lexcion.txt')
sent=raw_input('Input a sententce:')
words =fmm_word_seg(sent,max_len,word_dict)
for word in words:
print word
函数
函数参数
函数参数:位置参数、默认参数、可变参数、关键字参数(命名关键字参数)
- 位置参数:普通的传参方式
- 默认参数
1
2
3
4
5
6def power(x, n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
1 | 5) power( |
!定义默认参数必须指向不可变对象1
2
3
4
5def add_end(L=None):
if L is None:
L = []
L.append('END')
return L
- 可变参数:允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。
1
2
3
4
5def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
1 | 1, 2, 3) calc( |
- 关键字参数:允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict
作用:扩展函数的功能。用途:注册时,除必填项外,利用关键字参数还可以传入可选项。1
2def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
1 | 'Michael', 30) person( |
extra表示把extra的一份拷贝传给kw,kw获得一个dict,对kw的改动不会影响到函数外的extra
命名关键字:限制关键字参数的名字,需要一个特殊分隔符,后面的参数被视为命名关键字参数1
2
3
4def person(name, age, *, city, job):
print(name, age, city, job)
'Jack', 24, city='Beijing', job='Engineer') person(
Jack 24 Beijing Enginee
- 如果函数定义中已经有一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符
- 命名关键字参数必须传入参数名,这和位置参数不同,如果没有传入参数名,视为位置参数,调用将报错
1 | 'Jack', 24, 'Beijing', 'Engineer') person( |
- 参数组合
在python中定义函数,可以组合使用上述5种参数,但参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数1
2
3
4
5
6
7
8
9
10def f1(a,b,c=0,*args,**kw):
print('a=',a,'b=',b,'c=',c,'args=',args,'kw=',kw)
def f2(a,b,c=0,*,d,**kw):
print('a=',a,'b=',b,'c=',c,'d=',d,'kw=',kw)
f1(1,2)
f1(1,2,c=3)
f1(1,2,3,'a','b')
f1(1,2,3,'a','b',x=99)
f2(1,2,d=99,ext=None)
f2(1,2,dd=11,d=99,ext=None)
1 | a= 1 b= 2 c= 0 args= () kw= {} |
最神奇的是通过一个tuple和dict,你也可以调用上述函数:1
2
3
4
5
6
7
81, 2, 3, 4) args = (
'd': 99, 'x': '#'} kw = {
f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
1, 2, 3) args = (
'd': 88, 'x': '#'} kw = {
f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}
所以,对于任意函数,都可以通过类似func(args, *kw)的形式调用它,无论它的参数是如何定义的。
递归函数
函数调用是通过栈实现,所以使用递归函数需要注意防止栈溢出。
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,可以把循环看成是一种特殊的尾递归函数。
尾递归:在函数返回的时候,调用自身本身,并且return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。1
2
3
4def fact(n):
if n==1:
return 1
return n * fact(n - 1)
1 | def fact(n): |
尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。
遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。
函数式编程
高阶函数
高阶函数: 把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
- map/reduce
(1) map()
map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。 Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。1
2
3
4
5def normalize(name):
name=name.title()
return name
L=['adam','LISA','barT']
print(list(map(normalize,L)))
(2)reduce()
reduce把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
- filter:过滤序列
和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。
生成素数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19def _odd_iter():
n=1
while True:
n=n+2
yield n
def _not_divisible(n):
return lambda x:x%n>0
def primes():
yield 2
it=_odd_iter()
while True:
n=next(it)
yield n
it = filter(_not_divisible(n),it)
for n in primes():
if n<1000:
print(n)
else:
break
yield:yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator, 调用primes() 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 primes() 函数内部的代码,执行到 yield n 时,primes函数就返回一个迭代值,下次迭代时,代码从 yield n 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。
- sorted
sorted()也是一个高阶函数,用sorted排序的关键在于实现一个映射函数, 它可以接收一个key函数来实现自定义的排序1
2
3
436, 5, -12, 9, -21]) sorted([
[-21, -12, 5, 9, 36]
36, 5, -12, 9, -21], key=abs) sorted([
[5, 9, -12, -21, 36]
可以传入第三个参数reverse=True, 进行反向排序1
2'bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True) sorted([
['Zoo', 'Credit', 'bob', 'about']
返回函数
- 高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
- 内部函数可以引用外部函数的参数和局部变量,当外部函数返回内部函数时,相关参数和变量都保存在返回的函数中,这种程序结构称为闭包。
- 每次调用都会返回一个新的函数,即使传入相同的参数。
- 返回一个函数时,牢记该函数并未执行,直到调用了f()才执行。所以返回函数中不要引用任何可能会变化的变量。
匿名函数:不需要显示地定义函数
lambda x: x * x
- 关键字lambda表示匿名函数,冒号前面的x表示函数参数。
- 匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。
- 匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数
装饰器
- 函数是一个对象,可以赋值给一个变量,也可以通过变量来调用该函数。
- 若要增强某个函数的功能,比如在函数调用前后自动打印日志,但又不希望修改函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”
1
2
3
4
5
6
7
8
9
10
11
12
13import functools
def log(text):
def decorator(func):
def wrapper(*args,**kw):
print('%s %s()'%(text,func.__name__))
return func(*args,**kw)
return wrapper
return decorator
def now():
print("2015-3-1")
now() #相当于 now=log(‘execute’)(now)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import functools
def log(textOrFunc):
text = textOrFunc if isinstance(textOrFunc, str) else ''
def decorator(func):
def wrapper(*args, **kw):
print(text)
print('bengin call %s()' % ( func.__name__))
func(*args, **kw)
print('end call %s()' % ( func.__name__))
return wrapper
return decorator if isinstance(textOrFunc, str) else decorator(textOrFunc)
def now():
print("2015-3-1")
now()
print(now.__name__)
偏函数
当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。
functools.partial就是帮助我们创建一个偏函数。1
2
3
4import functools
2) int2 = functools.partial(int, base=
'1000000') int2(
64
面向对象
继承与多态
1 | class Animal(object): |
因为python是动态语言(鸭子类型),所以不一定需要传入Animal类型或其子类型,只需要保证传入的对象有一个run()方法就可以了。
对象信息
- type():返回对象类型
~使用types模块中定义的常量来判断一个对象是否是函数。 - isinstance():判断对象是否是同一类型;对于class的继承关系,type()很不方便,可以使用isinstance()函数
- dir():获得一个对象的所有属性和方法,返回一个包含字符串的list
实例属性和类属性
因为python是动态语言,所以根据累创建的实例可以任意绑定属性1
2
3
4
5
6
7
8
9
10
11
12class Student(object):... name = 'Student'
...
# 创建实例s>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性 s = Student()
Student
# 打印类的name属性 print(Student.name)
Student
'Michael' # 给实例绑定name属性>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性 s.name =
Michael
# 但是类属性并未消失,用Student.name仍然可以访问 print(Student.name)
Student
del s.name # 如果删除实例的name属性>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student
编写程序的时候,千万不要把实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
Python允许在定义class的时候,定义一个特殊的slots变量,来限制该class实例能添加的属性:1
2
3
4
5
6
7class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
# 创建新的实例>>> s.name = 'Michael' # 绑定属性'name'>>> s.age = 25 # 绑定属性'age'>>> s.score = 99 # 绑定属性'score' s = Student()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
- @property
Python内置的@property装饰器就是负责把一个方法变成属性调用的: 既能检查参数,又可以用类似属性这样简单的方式1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Student(object):
def score(self):
return self._score
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
s = Student()
60 # OK,实际转化为s.set_score(60)>>> s.score # OK,实际转化为s.get_score()60>>> s.score = 9999 s.score =
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值, 还可以定义只读属性。
多重继承
1 | class Dog(Mammal, Runnable): |
MixIn:一种常见的设计。MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。
定制类
1. str()与repr()
str()返回用户看到的字符串,而repr()返回程序开发者看到的字符串,也就是说,repr()是为调试服务的。1
2
3
4
5
6
7
8class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__
'Michael') print(Student(
'Michael') Student(
2. iter
如果一个类想被用于for … in循环,类似list或tuple那样,就必须实现一个iter()方法,该方法返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的next()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。1
2
3
4
5
6
7
8
9
10
11
12class Fib(object):
def __init__(self):
self.a,self.b=0,1
def __iter__(self):
return self
def __next__(self):
self.a,self.b=self.b,self.a+self.b
if self.a>100:
raise StopIteration()
return self.a
for n in Fib():
print(n)
3. getitem
实现getitem()可以像list那样按照下标取出元素。
要正确实现一个getitem()还是有很多工作要做的,即切片和step参数
4. getattr :动态返回一个属性。
只有在没有找到属性的情况下,才调用getattr,已有的属性,不会在getattr中查找。
5. call :只需要定义一个call()方法,就可以直接对实例进行调用。1
2
3
4
5
6
7
8
9class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
'Michael') s = Student(
# self参数不要传入 s()
My name is Michael.
通过callable()函数,我们就可以判断一个对象是否是“可调用”对象。1
2
3
4 callable(Student())
True
'str') callable(
False
使用枚举类
为这样的枚举类型定义一个class类型: Enum类1
2
3
4
5
6
7
8
9
10
11from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
from enum import Enum, unique
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
@unique装饰器可以帮助我们检查保证没有重复值。 既可以用成员名称引用枚举常量,又可以直接根据value的值获得枚举常量。
元类
class的定义是运行时动态创建的,而创建class的方法就是使用type()函数。
type()函数既可以返回一个对象的类型,又可以创建出新的类型,
要创建一个class对象,type()函数依次传入3个参数:
- class的名称;
- 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
- class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。
metaclass
除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass。
先定义metaclass,就可以创建类,最后创建实例。