写在前面
每种语言都有各自的特性,适用的场景也有差别,比如C/C++常用于高性能环境中,而Java常用于网络应用程序,Python给人的感觉有点想Shell等脚本语言,能快速开发出程序,关键是在Windows、Linux、Mac上基本上都是有安装Python的。
这篇是学习笔记,主要用来记录在用Python编程时需要注意的地方。下面来看一些非常常用的操作:
isinstance:判断obj是否为对应类型的实例
type:获取obj的类型
dir:获取所有的属性和方法
callable:对象是否可以执行
基础
字符串编码问题
在python 2.x中使用的str实际是字节码,是Unicode经过编码后的字节组成的序列,简单的验证方法是通过print len('我')输出3(utf-8编码中汉字有三个字节)得到验证,str和Unicode都是basestring的子类,而Unicode才是真正意义上的字符串,即printf len(u'我')的结果为1。
另外在有中文的python源文件上有加注释来说明编码:# coding: UTF-8。
在python中打开文件读取到的内容实际上是str,而不是unicode(这样实现更合理),如果要当做字符串处理的话要自己encode。
数据集合
提供了list(可变有序列表)、tuple(不可变的列表)、dict(字典)、set(集合)等能快速使用的数据结构,在根据下标访问时传入负数不会报错,比如list[-1]在实际上得到的是最后一个元素。在初始化时集合中的数据可以是任意的,不需要一致:
s = ['python', 'java', ['asp', 'php'], 'scheme']
在python中有很多截取、生成列表的例子,这也算是一个比java、c灵活快捷的地方:
[x * x for x in range(1, 11)] # 从1到10的平方的数组 [x * x for x in range(1, 11) if x % 2 == 0] # 可以增加条件 [m + n for m in 'ABC' for n in 'XYZ'] # 甚至嵌套循环 list[0:3] # 取list前3个元素 list[:3] # 同上,第一个是0则可以省略不写 list[-3:] # 取list后3个元素 list[::3] # 每3个取一个 list[:] # 复制一个list
这样生成的数据是占用内存的,有时候这样的代价太大,这便有了generator,它只保存了逻辑,不存储数据。generator的初始化方法只是简单的将上面的中括号变成括号即可:
g = (x * x for x in range(10))
而在访问generator是使用g.next()来完成,当然也可以使用for...in...的方式。
函数和逻辑控制
在python中比较蛋疼的是通过缩进来控制逻辑控制语句、函数的范围,写不好就错了,比如if语句你要写成:
if a > 0 : print 1 print 2 # 如果这一行没有缩进的话a < 2也能输出来了
和c的函数指针有点类似的是python中的函数也可以传递,比如在用sorted对列表实现倒序排序时将比较函数当做参数传进去:
def cmp(x, y) : return y - x; sorted([1, 2, 3, 4], cmp);
同样的方法也可以作为返回值。lambda函数使得这种用法更爽(虽然python对lambda的支持有限,只有在简单情况下此案呢过使用),上面三行有一行就可以搞定了:
sorted([1, 2, 3], lambda x,y:y-x);
调用函数的地方传递参数并不一定要跟函数声明时的顺序保持一致,只要你讲清楚具体是哪个传给哪个就可以了,举个例子,申明一个函数为func(a, b),在调用是可以是:
func(b=1,a=2) 等价于 func(2, 1)
在很多的python代码中会看到func(*args, **kwargs)类型的参数,那么可以猜到第一个为正常方式传入的,第二种为key-value方式传入的。在参数比较多的函数调用时比较麻烦,因为大部分参数在大部分场景下是相同的,在Java/C中的方法是重新声明一个函数,而在python中进一步简化(需要functools模块):
int2 = functools.partial(int, base=2)
另外Spring中的AOP很好用吧?这里也有了类似的写法:
def log(func): def wrapper(*args, **kw): print 'call %s():' % func.__name__ return func(*args, **kw) return wrapper @log def now(): print '2013-12-25'
模块
把所有的代码写在一个文件里面很不显示,python通过模块的方式来组织,声明的方法如下:
mycompany(一个模块就是一个目录)
__init__.py(让解释器将该目录当成模块)
xxx.py(模块中的内容)
用import引入之后就可以使用xxx功能了。
面向对象编程
在申明类型时跟函数一样,需要用缩进来标记出来属于该类的代码的范围,最简单的例子:
class Student : def __init__(self, name, score): # 构造函数 self.name = name self.score = score
在使用时s = Student()即可得到一个实例,python中的继承与Java/C++稍有不同:class Student(Person) 。类是属性和方法的封装体,自然并不是所有的属性都能被访问,在python中比较“牛逼”的地方是用两个下划线来将属性保护起来:
s.__name
访问的时候会出错~ 使用时可以“随意”地给类型、实例添加属性和方法,给obj添加属性很简单:s.age=18,给class添加方法之后,所有的实例中都可以访问了:
Student.set_score = MethodType(set_score, None, Student)
很多时候我们并不希望大家随意地添加属性,那么可以通过__slots__ = ('name', 'age')来进行限制,需要注意的是该方法对子类并不起作用。用函数的方式设置和读取看起来比较麻烦,在python又做了一次改进,通过注解将对属性的访问映射到函数上:
class Student(object): @property def score(self): return self._score @score.setter def score(self, value): self._score = value
从次可以用s.score=2来调用score(self,value)方法,这个想法很不错,但是貌似这样写完会把代码搞乱掉的,让调用者看得云里雾里。为了将输出更优化在Java中建议重写toString方法,同样目的,在python中建议重写__str__和__repr__方法,另外可以做出一些额外的定制:
__getattr__:从obj中获取不到属性时,会尝试调用该方法,如果不重写的话会直接抛出AttributeError
__call__:使obj可以像函数一样执行
__getitem__:用来支持obj[0]这种下标的方式获取数据
__iter__:用来支持for .... in这种循环遍历
后面两种有点像C++里面的运算符重载,感觉应用的场景很少。在Java中可以使用cglib等产生新的类型,Python中的type()也提供了类似的功能:
type('Hello', (object,), dict(hello=fn))
其中,第一个参数是class的名称,第二个参数是父类的集合,第三个参数是将方法名称(hello)和函数(fn)绑定。另外一种创建类型的方法是metaclass,来看一个例子,看不懂就算了:
# metaclass是创建类,所以必须从`type`类型派生: class ListMetaclass(type): def __new__(cls, name, bases, attrs): attrs['add'] = lambda self, value: self.append(value) return type.__new__(cls, name, bases, attrs) class MyList(list): __metaclass__ = ListMetaclass # 指示使用ListMetaclass来定制类
在用到对象的地方一般都会涉及到序列化,python通过pickle.dumps和pickle.loads来完成序列化和反序列化,还有问题是对象与JSON格式的字符串进行互相转换,可以使用json.dump来变成JSON,不过它并不认识你自己定义的类型,需要用指定一个函数来完成,简单做法如下:
print(json.dumps(s, default=lambda obj: obj.__dict__))
而在从JSON变成对象的时候需要指定object_hock方法用来根据dict生成对象:
json.loads(json_str, object_hook=dict2student)
进程、线程、协程
创建进程与C一样:os.fork(),如果是父进程的话返回子进程ID,如果是子进程的话返回0。另外还可以通过multiprocessing模块创建进程:
Process(target=run_proc, args=('test',))
如果要创建大量子进程可以用Pool的方式批量创建:
p = Pool()
for i in range(5):
p.apply_async(long_time_task, args=(i,))
进程间的通信可以通过Queue、Pipes来做。Python进程的一个牛逼之处是可以使用managers模块方便地做分布式进程。
Python中的线程是真正的Posix Thread,但不幸的是解释器执行代码时有一个GIL锁,任意Python线程执行前必须获取GIL锁,导致的结果就是多线程不能有效地利用多核优势,不管怎么样看下线程的用法:
t = threading.Thread(target=run_thread, args=(5,)) # 创建线程
t.start() # 开始执行
lock = threading.Lock() # 创建锁
lock.acquire() # 获取锁
// todo
lock.release() # 释放锁
Java中的ThreadLocal很好用吧,Python也有(感觉都是抄来抄去),用法基本上一样:
local_school = threading.local() # 创建ThreadLocal变量
local_school.student = 123 # 绑定属性
其他的基础方面的还有网络编程,但是这部分和Java/C太像了,这里就不写了。
数学计算
毫无疑问在搞机器学习计算的最好工具是Matlab,但是作为一个程序员比较排斥这种东西,更愿意自己写代码来做,但是不用现成的模块的话代码量巨大,所以这里介绍几种常用的Python中数值计算的工具。
NumPy
该模块可以搞定大部分的矩阵中的运算,在官网中的文档看起来要比大部分的翻译轻松很多,它所支持的功能可以在这里查看,如果访问不了可以试试这个。
SciPy
用来支持最优化、线性代数、积分、插值等,和NumPy的有一定的重复,在这里可以用具体用法,如果访问不了可以试试这个。
SymPy
纯python实现的符号运算器,感觉很强大,但是我实际上还没用到,因为都是自己在推公式,感兴趣可以在官网了解。
matplotlib
一个画图工具,为什么需要画图?你运算的结果或者模型在你脑子里面是清楚的,但是你要跟人家展示啊,官网学习中。