python 语言精要
Python 解释器
Python 是一种解释性语言。Python 解释器是通过“一次执行一条语句”的方式运行程序的。Python 科学计算更趋向于使用 IPython(一种加强的交互式 Python 解释器)。
基础知识
- Python 是通过空白符(制表符或空格)来组织代码的,不像其他语言(如 R,C++,Java,Perl 等)用的是大括号。同时,强烈建议用 4 个空格作为默认缩进量,这样,你的编辑器就会将制表符替换为 4 个空格。
- Python 语言的一个重要特点就是其对象模型的一致性。Python 解释器中的任何数值、字符串、数据结构、函数、类、模块等都待在它们自己的“盒子”里,而这个“盒子”也就是 Python 对象。
- Python 使用 # 进行注释,任何前缀为井号 “ # ” 的文本都会被 Python 解释器忽略掉。
- 在 Python 中对变量赋值时,其实是在创建等号右侧对象的一个引用。赋值(assignment)操作也叫做绑定(binding),因为其实是将一个名称与一个对象绑定到了一起。已经赋过值的变量名有时也被称为已绑定变量(bound variable)。
a = [1, 2, 3]
b = a
a.append(4)
b # [1, 2, 3, 4]
- 跟许多编译语言(如 Java 和 C++)相反,Python 中的对象引用没有与之关联的类型信息。变量其实就是对象在特定命名空间中的名称而已。对象的类型信息是保存在它自己内部的。因此,Python 仍然可以被认为是一种强类型语言。可以通过 isinstance() 函数,检车一个对象是否使某个特定类型的实例。
- Python 提供了 getattr(),hasattr(),setattr() 三个函数实现通过名称对函数的调用,尤其是在编写通用的、可复用的代码时,它们就变得非常实用了。
- 鸭子模型:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。” 在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定。
- 引入
import some_module
from some_module import f, g, PI
import some_module as sm
- 比较运算符:要判断两个引用是否指向同一个对象,可以使用 is 关键字;如果想判断两个引用是否不是指向同一个对象,则可以使用 is not。而 == 则是判断对象中可比较的元素的大小是否相同。is 和 is not 常常用于判断变量是否为 None,因为 None 的实例只有一个。
- 严格与懒惰:在 Python 中,只要这些语句被求值,相关计算就会立即(也就是严格)发生。而在另一种编程范式中,值在被使用之前是不会被计算出来的。这种将计算推迟的思想通常称为延迟计算(lazy evaluation)。有一些 Python 技术(尤其是用到迭代器和生成器的那些)可以用于实现延迟计算。在数据密集型应用中,但执行一些负荷非常高的计算时(这种情况不太多),这些技术就能派上用场了。
- 可变和不可变对象:大部分 Python 对象是可变的(muable),比如列表、字典、Numpy数组以及大部分用户自定义类型(类)。也就是说,它们所包含的对象或值是可以被修改的。而其他的(如字符串和元组等)则是不可变的(immutable)。
标量类型
Python 有一些用于处理数值数据、字符串、布尔值(True 或 False)以及日期/时间的内置类型
- None:Python 的 null 值(None 只存在一个实例对象)
- str:字符串类型。Python 2.x 中只有 ASCII 值,而 Python 3 中则是 Unicode
- unicode:Unicode 字符串类型
- float:双精度(64位)浮点数。注意,这里没有专门的 double 类型
- bool:True 或 False
- int:有符号整数,其最大值由平台决定
- long:任意精度的有符号整数。大的 int 值会被自动转换为 long
数值类型:Python 3 中整数除法除不尽时就会产生一个浮点数,如果需要得到 C 风格的整数除法(如果除不尽,就丢弃小数部分),使用除后圆整运算符(//)
字符串:编写字符串变量时,既可以用单引号(‘)也可以用双引号(")。对于带有换行符的多行字符串,可以使用三重引号(即 ''' 或 """)。Python 字符串是不可变的,要修改就只能创建一个新的。由于字符串其实是一串字符序列,因此可以被当做某种序列类型(如列表、元组等)进行处理。反斜杠(\)是转义符,也就是说,它可用于指定特殊字符(比如新号 \n 或 unicode 字符)。不过,如果字符串带有很多反斜杠且没有特殊字符,你就发现这个办法很容易让人抓狂。幸运的是,你可以在字符串最左边引号的前面加上 r,它表示所有字符应该按照原本的样子进行解释。同时,Python 3 给出了一个新的字符串格式化手段
template = "%.2f %s are worth $%d"
template % (4.5560, 'Argentine Pesos', 1)
布尔值:几乎所有内置的 Python 类型以及任何定义了 __nonzero__ 魔术方法的类都能在 if 语句中被解释为 True 或 False。Python 中大部分对象都有真假的概念。比如空序列(列表、字典、元组等)用于控制流就会被当做 False 处理。要想知道某个对象究竟会被强制转换成哪个布尔值,可以使用 bool() 函数。
类型转换:str、bool、int 以及 float 等类型也可用作将值转换为该类型的函数
None:None 是 Python 的空值类型。如果一个函数没有显式地返回值,则隐式返回 None。None 还是函数可选参数的一个常见类型。不过,None 不是一个保留关键字,它只是 NoneType 的一个实例而已。
日期和时间:Python 内置的 datetime 模块提供了 datetime、date 以及 time 等类型。datetime 类型是用得最多的,它合并了保存在 date 和 time 中的信息。
from datetime import datetime, date, time
dt = datetime(2011, 10, 29, 20, 30, 21)
dt.day # 29
dt.minute # 30
给定一个 datetime 实例,你可以通过调用其 date 和 time 方法提取相应的 date 和 time 对象。
dt.date() # datetime.date(2011, 10, 29)
dt.time() # datetime.time(20, 30, 21)
dt.strftime('%m/%d/%Y %H:%M') # '10/29/2011 20:30'
datetime.striptime('20191031', '%Y%m%d') # datetime.datetime(2009, 10, 31, 0, 0)
dt.replace(minute=0, second=0) # datetime.datetime(2011, 10, 29, 20, 0)
dt2 = datetime(2011, 11, 15, 22, 30)
delta = dt2 - dt # delta = datetime.timedelta(17, 7179)
dt + delta # datetime.datetime(2011, 11, 15, 22, 30)
异常处理:
f = open(path, 'w')
try:
write_to_file(f)
except: # or except (IOError, ...):
print 'Failed'
else:
print 'Successed'
finally:
f.close()
range 和 xrange:对于 Python 2,range 产生一组间隔平均的整数,对于非常长的范围,建议使用 xrange,它不会预先产生所有的值并将它们保存到列表中(可能会非常大),而是返回一个用于逐个产生整数的迭代器。不过在 Python 3 中,range 始终返回迭代器,而取消了 xrange。
三元表达式:value = ture-expr if condition else false-expr
数据结构和序列
元组
元组(tuple)是一种一维的、定长的、不可变的 Python 对象序列。调用 tuple 任何序列或迭代器都可以被转换为元组。
nested_tup = (4, 5, 6), (7, 8)
tuple([4, 0, 2]) # (4, 0, 2)
tup = tuple('string') # ('s', 't', ...)
tup[1] # 't'
(4, None, 'foo') + (6, 0) + ('bar',) # (4, None, 'foo', 6, 0, 'bar')
('foo', 'bar') * 2 # ('foo', 'bar', 'foo', 'bar') 这里并没有复制,只是增加了引用而已
tup = 4, 5, (6, 7)
a, b, (c, d) = tup
seq = [(1 ,2, 3), (4, 5, 6)]
for a, b, c in seq:
pass
列表
跟元组相比,列表(list)是变长的,而且其内容也是可以修改的。它可以通过 [] 或 list 函数进行定义。
tup = ('foo', 'bar', 'baz')
b_list = list(tup)
b_list.append('dwarf')
b_list.insert(1, 'red') # 耗时
b_list.pop(2) # 耗时
b_list.remove('foo')
'dwarf' in b_list
b_list + ['baz'] # 耗时
b_list.extend(['baz'])
a = [7, 2, 5, 1, 3]
a.sort # a = [1, 2, 3, 5, 7]
b.sort(key=len)
import bisect
# bisect 模块实现了二分查找以及对有序列表的插入操作
bisect.bisect(c, 2) # 找出新元素应该被插入到哪个位置才能保持原列表的有序性
bisect.insort(c, 6) # 确实地将新元素插入到那个位置上去
a[1:3:2] # start:stop:step
a[-4:]
a[::-1] # 翻转列表
for i, value in enumerate(collection):
pass
mapping = dict((v, i) for i, v in enumerate(some_list))
b = sorted(a)
rlt = sorted(set(a)) # 得到一个序列中的唯一元素组成的有序列表
a = [1, 2, 3]
b = [4, 5, 6]
zip_rlt = zip(a, b) # [(1,4), (2,5), (3,6)]
for i, (a, b) in enumerate(zip(seq1, seq2)):
print('%d,: %s, %s' % (i, a, b))
a, b = unzip(zip_rlt)
list(reversed(range(10))) # [9, 8, 7, ...]
字典
字典(dict)可算是 Python 中最重要的内置数据结构。它更常见的名字是哈希映射(hash map)或相联数组(associative array)。它是一种大小可变的键值对集,其中的键(key)和值(value)都是 Python 对象。
d1 = {'a' : 'some value', 'b' : [1, 2, 3, 4]}
d1[7] = 'an integer' # d1 = {'7' : 'an integer', 'a' : 'some value', 'b' : [1, 2, 3, 4]}
d1['b'] # [1, 2, 3, 4]
'b' in d1 # True
del d1[7] # d1 = {'a' : 'some value', 'b' : [1, 2, 3, 4]}
d1.pop('a') # 'some value'
d1.keys() # Python 2 返回列表
d1.values() # Python 3 返回迭代器
d1.update({'b' : 'foo', 'c' : 12}) # 使用一个 dict 更新另一个 dict
# 字典本质上就是一个二元元组集,所以完全可以用 dict 类型直接处理二元元组列表
mapping = dict(zip(range(5), reversed(range(5))))
字典的默认值操作
value = some_dict.get(key, default_value)
words = ['apple', 'bat', 'bar', 'atom', 'book']
by_letter = {}
for word in words:
letter = word[0]
by_letter.setdefault(letter, []).append(word)
# {a : ['apple', 'atom'], 'b' : ['bat', 'bar', 'book']}
from collections import defaultdict
by_letter = defaultdict(list)
for word in words:
by_letter[word[0]].append(word)
counts = defaultdict(lambda: 4) # 默认值为 4
虽然字典的值可以是任何的 Python 对象,但键必须是不可变对象,如标量类型(整数、浮点数、字符串)或元组(元组中的所有对象也必须是不可变的)。这里的术语是可哈希性(hashability)。通过 hash() 函数,可以判断某个对象是否是可哈希的(即可以用作字典的键)。如果要将列表当做键,最简单的方法就是将其转换为元组。
集合
集合(set)是由唯一元素组成的无序集。你可以将其看成是只有键值而没有值的字典。
# 创建字典
set([1, 2, 3])
{1, 2, 3}
# 集合运算
a | b # 并
a & b # 交
a - b # 差
a ^ b # 异或
a.add(x) # 增加元素
a.remove(x) # 移除元素
a.union(b) # a | b
a.intersection(b) # a & b
a.difference(b) # a - b
a.symmetric_difference(b) # a ^ b
a.issubset(b) # 是否 a 包含于 b
a.issuperset(b) # 是否 a 包含 b
a.isdisjoint(b) # 是否相交
列表、集合以及字典的推导式
list_comp = [expr for val in collection if condition]
dict_comp = {key-expr : value-expr for value in collection if condition}
set_comp = {expr for value in collection if condition}
loc_mapping = {val : index for index, val in enumerate(strings)}
loc_mapping = dict(val : index for index, val in enumerate(strings))
flatted = [x for tup in some_tuples for x in tup]
[[x for x in tup] for tup in some_tuples] # 注意两者的差异
函数
函数可以有一些位置参数和一些关键值参数。关键字参数通过用于指定默认值或可选参数。函数参数的主要限制在于:关键字参数必须位于位置参数(如果有的话)之后。你可以任何顺序指定关键值参数。也就是说你不用死记硬背函数参数的顺序,只要记得它们的名字就可以了。
命名空间、作用域,以及局部函数
函数可以访问两种不同作用域中的变量:全局(global)和局部(local)。Python 有一种更科学的用于描述变量作用域的名称,即命名空间(namespace)。任何在函数中赋值的变量默认都是被分配到局部命名空间(local namespace)中的。局部命名空间是在函数被调用时创建的,函数参数会立即填入该命名空间。在函数执行完毕之后,局部命名空间就会被销毁(会有一些例外的情况,比如闭包)。虽然可以在函数中对全局变量进行赋值操作,但那些变量必须用 global 关键字声明成全局的才行。
可以在任何位置进行函数声明,即使是局部函数(在外层函数被调用之后才会被动态创建出来)也是可以的。各个嵌套的内层函数可以访问其上层函数的局部命名空间,但不能绑定新变量。
严格意义上说,所有函数都是某个作用域的局部函数,这个作用域可能刚好就是模块级的作用域。
返回多个值
Python 支持函数的返回多个值。本质上,函数其实只返回了一个对象,也就是一个元组,最后该元组会被拆包到各个结果变量中。如果直接使用一个变量接受一个返回多个变量的函数结果,那么这个变量将是一个包含所有返回的元组。
函数也是对象
由于 Python 函数都是对象,因此,在其他语言中较难表达的一些设计思想在 Python 中就要简单很多了。
# 实现去除空白符,删除各种标点符号,正确的大写格式等
import re
def remove_punctuation(value):
return re.sub('[!#?]', '', value)
clean_ops = [str.strip, remove_punctuation, str.title]
def clean_strings(strings, ops):
result = []
for value in strings:
for function in ops:
value = function(value)
result.append(value)
return result
# map(remove_punctuation, states)
匿名(lambda)函数
Python 有一种被称为匿名函数或 lambda 函数的东西,这其实是一种非常简单的函数:仅由单条语句组成,该语句的结果就是返回值。它们通过 lambda 关键字定义,这个关键字没有别的含义,仅仅是说“我们正在声明的是一个匿名函数”。
def short_function(x):
return x ** 2
equiv_anon = lambda x: x ** 2
闭包:返回函数的函数
闭包(closure)不是什么很可怕的东西。简而言之,闭包就是由其他函数动态生成并返回的函数。其关键性质是,被返回的函数可以访问其创建者的局部命名空间中的变量。闭包和标准 Python 函数之间的区别在于:即使其创建者已经执行完毕,闭包仍然能继续访问其创建者的局部命名空间。虽然闭包的内部状态一般都是静态的,但也允许使用可变对象(如字典、集合、列表等可以被修改的对象)。但是要注意一个技术限制:虽然可以修改任何内部状态对象(比如向字典添加键值对),但不能绑定外层函数作用域中的变量。一个解决方法是:修改字典和列表,而不是绑定变量。
def format_and_pad(template, space):
def formatter(x):
return (template % x).rjust(space)
return formatter
fmt = format_and_pad("%.4f", 15) # 创建一个始终返回 15 位字符串的浮点数
fmt(1.756)
扩展调用语法和 *args 、**kwargs
Python 函数参数的工作方式其实很简单。位置和关键字参数其实分别是被打包成元组和字典的。函数实际收到的是一个元组 args 和一个字典 kwargs,并在内部完成转换。
func(a, b, c, d=some, e=value)
a, b, c = args
d = kwargs.get('d', some)
e = kwargs.get('e', value)
柯里化:部分参数应用
柯里化(currying)指的是通过“部分参数应用”从现有函数派生出新函数的技术。
def add_numbers(x, y):
return x + y
add_five = lambda y: add_numbers(5, y)
from functools import partial
add_five = partial(add_numbers, 5)
# 计算时间序列 x 的 60 日移动平均
ma60 = lambda x: pandas.rolling_mean(x, 60)
data.apply(ma60)
生成器
能以一种一致的方式对序列进行迭代(比如列表中的对象或文件中的行)是 Python 的一种重要特点。这是通过一种叫做迭代器协议的方式实现的。当调用 for xx in xxx 时,Python 解释器首先会尝试从 xxx 创建一个迭代器。迭代器是一个特殊对象,他可以在诸如 for 循环之类的上下文中向 Python 解释器输送对象 。大部分能接受列表之类的对象的方法也都可以接受任何可迭代对象。生成器(generator)是构造新的可迭代对象的一种简单方式。一般的函数执行之后只会返回单个值,而生成器则是以延迟的方式返回一个值的序列,即每返回一个值之后暂停,直到下一个值被请求时再继续。要创建一个生成器,只需将函数中的 return 替换为 yield 即可。
def squares(n=10):
for i in range(1, n+1):
print 'Generating squares from 1 to %d' % (n ** 2)
yield i ** 2
生成器表达式
gen = (x ** 2 for x in range(100))
itertools 模块
标准库 itertools 模块中有一组用于许多常见数据算法的生成器。
- imap
- ifilter
- combinations
- permutations
- groupby:根据函数返回值对序列中的连续元素进行分组
import itertools
first_letter = lambda x: x[0]
names = ['Alans', 'Adam', 'Wes', 'Will', 'Albert', 'Steven']
for letter, names in itertools.grouby(name, first_letter)
文件和操作系统
为了打开一个文件以便读写,可以使用内置的 open 函数以及一个相对或绝对的文件路径。默认情况下,文件是以只读模式(’r‘)打开的。然后,就可以想处理列表那样来处理这个文件句柄了。从文件中读出的行都带有完整的行结束符(EOF)。
lines = [x.rstrip() for x in open(path)]
文件模式:
- r:只读模式
- w:只写模式。创建新文件(删除同名的任何文件)
- a:附加到现有文件(如果文件不存在则创建一个)
- r+:读写模式
- b:附加说明某模式用于二进制文件,如 'rb' 或 'wb'
- U:通用换行模型。单独使用或附加到其他模式,如 'rU'
常用方法:
- read([size]):以字符串形式返回文件数据,可选的 size 参数用于说明读取的字节数
- readlines([size]):将文件返回为行列表
- write(str):将字符串写入文件
- close():关闭句柄
- flush():清空内部 I/O 缓存区,并将数据强行写回磁盘
- seek(pos):将文件指针移动到指定的文件位置(整数)
- tell():以整数形式返回当前文件指针位置
- closed:如果文件已关闭,则为 True