python 速成指南
作者:doudehou@gmail.com
第一节. 过程式 python
python 的一个特点是不通过大括号 {} 来划定代码块,而是通过缩进。如果和 C/C++ 类比的话,就是在左括号的地方不要换行,然后用一个冒号 (:) 替代, C/C++ 大括号内部的东西,缩进一个 tab 或者几个空格都可以(但需要保持一致),比如:
if (x < 2):
print 'x < 2'
elif (x > 2):
print 'x > 2'
else:
print 'bingo!'
print 'x = 2'
注意两点:一是 python 语句结尾处没有分号(;)作为结束标记。二是和 C/C++ 不同,没有 else if,而是用 elif 替代,相当于可以少打几个字符吧。
(语句的分隔
在C、Java等语言的语法中规定,必须以分号作为语句结束的标识。Python也支持分号,同样用于一条语句的结束标识。但在Python中分号的作用已经不像C、Java中那么重要了,Python中的分号可以省略,主要通过换行来识别语句的结束。
例如,以下两行代码是等价的:
- print "hello world!"
- print "hello world!";
第1行代码的输出结果:
- hello world!
第2行代码的输出结果:
- hello world!
如果要在一行中书写多条句,就必须使用分号分隔每个语句,否则Python无法识别语句之间的间隔:
- # 使用分号分隔语句
- x=1; y=1 ; z=1
第2行代码有3条赋值语句,语句之间需要用分号隔开。如果不隔开语句,Python解释器将不能正确解释,提示语法错误:
- SyntaxError: invalid syntax
注意分号不是Python推荐使用的符号,Python倾向于使用换行符作为每条语句的分隔,简单直白是Python语法的特点。通常一行只写一条语句,这样便于阅读和理解程序。一行写多条语句的方式是不好的习惯。
Python同样支持多行写一条语句,Python使用“\\”作为换行符。在实践中,一条语句写在多行也是非常常见的。
【例2-17】把SQL语句作为参数传递给函数,由于SQL的语句一般非常长,为了阅读方便,因此需要换行书写。
- # 字符串的换行
- # 写法一
- sql = "select id,name \\
- from dept \\
- where name = 'A'"
- print sql
- # 写法二
- sql = "select id,name " \\
- "from dept " \\
- "where name = 'A'"
- print sql
写法一只使用了一对双引号,把SQL语句分为select、from、where等3部分分别书写。
第6行代码输出结果:
- select id,name from dept where name = 'A'
写法二使用了3对双引号,select、from、where分别对应一对双引号。
第11行代码输出结果:
- select id,name from dept where name = 'A'
第二种写法比第一种写法的可读性更强,可以使用空格和制表符对齐语句,使代码显得更工整。对于简短的语句不推荐换行的写法,这种写法只会造成阅读的复杂性。下面这段程序是不合理的换行写法:
- # 一条语句写在多行
- print \\
- "hello world!"
第2行~第3行代码是一个整体,调用print输出“hello world!”,这种情况不适合分行书写。)
类型系统
比如 int,string 等,type() 可以返回数据的类型,如:
>>> type(1)
<type 'int'>
>>> type('123')
<type 'str'>
python 尽管在声明变量的时候不指定类型,但变量其实是有类型的,用 c++0x 的概念来表达的话,实际上 python 的变量好像都是 auto 的,类型自动根据赋值推导出来。所以这样:
name = 'ddh'
verb = ' is '
noun = ' good man'
sentence = name + verb + noun
没有问题,但:
name = 'cyberscorpio'
age = 32
sentence = name + age
就会引发异常,因为字符串和数字不能直接相加。
可以通过 int() 或者 str() 强转类型,如上一句改成: sentence = name + str(age) 就不会有问题了。
容器类型
python 提供好用的两个容器:list 和 dict。插句题外话,其实最好用的容器还是 PHP 提供的关联数组,一个数组就包括了 python 中 list 和 dict 的全部功能,实在是很赞。
list
类似 array 的概念,例如:
lst = list()
lst.append('123')
lst.append('456')
lst.append(1000)
print lst
for x in lst:
print x, ' type is ', type(x)
注意 list 内的数据可以是不同类型的,这一点会很方便。上面的输出是:
>>> print lst
['123', '456', 1000] # 注意这里是 list 的字面表示方法,如 lst = ['123', '456', 1000],lst 自动成为一个 list
>>> for x in lst:
... print x, ' type is ', type(x)
...
123 type is <type 'str'>
456 type is <type 'str'>
1000 type is <type 'int'> # 这个是整型的数据
dict
类似于 std::map 的概念,当然,和 list 类似,dict 的 key 和 value 不要求是同一种类型。如:
dct = dict()
dct['name'] = 'cyberscorpio'
dct['age'] = 32
dct['sex'] = 'male'
print dct
for k in dct:
print k, ' is ', dct[k]
输出为:
>>> print dct
{'age': 32, 'name': 'cyberscorpio', 'sex': 'male'} # 注意这里是 dict 的字面表示方法,如 dct = {'age':32, 'name':'cyberscorpio', 'sex':'male'},则 dct 自动成为一个 dict
>>> for k in dct:
... print k, ' is ', dct[k]
...
age is 32
name is cyberscorpio
sex is male
判断一个 key 是否在 dict 中:
dct = {'name' : 'ddh', 'age' : 32, }
if 'name' in dct:
print 'the dict has a name and we will delete it...'
del dct['name']
可以使用 del 删除这个 key。另外 if key not in dict 可以判断这个 key 是否 “不在” dict 中。
list 和 dict 均为某种对象,它们支持的方法 (method) 可以通过语言内置的 dir() 来获取,比如:
>>> dir(lst)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
比如 append(),insert(),sort() 什么的,都是 list 很常用的方法。而:
>>> dir(dct)
['__class__', '__cmp__', '__contains__', '__delattr__', '__delitem__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'has_key', 'items', 'iteritems', 'iterkeys', 'itervalues', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
has_key(),keys(),values() 等都是 dict 常用的方法。
用 dir('123') 则可以看到 string 支持的所有方法,总之还是比较方便的。
循环
简单的 for 循环,如:
fox x in 容器:
对 x 做操作
这是对容器进行枚举的很方便的做法。
其他的循环方式还有 while,如:
while <测试>:
循环体
注意在循环中我们仍然有 break, continue 等跳出或者继续循环的指令,和 C/C++ 是一致的。
函数
函数定义:
def 函数名 (参数列表):
函数内容
值得注意的是,函数中变量默认是 local 的,如果要访问全局变量,那么需要在函数中声明其为 global 的,如:
bar = 0
def foo ():
global bar
bar = bar + 1
print str(bar)
参数列表这里面颇有玄机,python 具备和 C++ 相同的两种参数列表结构,就是 (var1, var2) 和 (var1, var2=default value) 这两种,但除此之外呢,和 C/C++ 的 … 类似,python 还有两种针对不定参数个数的独门武功,举例说明:
def foo (*params):
if len(params) == 0:
print 'no parameter' # 如果调用方式为 foo() 则进入这里
else:
print params # 如果调用方式为 foo(1, 2, 3, 4),则这里输出为 (1, 2, 3, 4),params 是一个 tuple
def bar (**params):
if len(params) == 0:
print 'no parameter' # bar()
else:
print params # bar(name='ddh', age=32),则这里输出为 {'name' : 'ddh', 'age' : 32},params 是一个 dict。
对于 ** 这种方式,调用函数的时候,必须指定参数的名称和值,名称不需要带引号,但进入函数以后,自然成为同名的字符串,如上可见。
Python 所有的变量(不仅仅是传参)都是基于引用的,但是 python 的对象分为两种,一种叫 immutable,还有一种叫 mutable,区别在哪里呢?后者具有方法来改变自己,比如 list.append() 等,而 str 就是 immutable 的,也即,所有的 str 方法都不能改变自己(比如 str.replace() 是返回一个被替换过的 str,原来的 str 不变)。这样的话,我们这么来看:
def foo (s):
s = '456'
name = '123'
foo(name)
首先,name 本身不是对象,它是对一个 str 且内容为 '123' 的 str 对象的引用。其次,在 foo 函数中, s 是对同一个对象的,引用,而不是对 name 的引用。那么可以断定:
- 对 s 的改变无法影响到 name,因为 name 并未被引用进来。
- s = '456' 只是把 s 换成了对另一个内容为 '456' 的 str 对象的引用,无法改变之前那个 '123' 的 str 对象。
- 因为 str 不提供改变自己的方法,所以,foo 函数无法改变 name 所指向的对象的值。
这么来看的话,immutable 对象创建出来之后就不能变了,比如:
s = '123'
s = '456'
这里第一句,s 指向一个内容为 '123' 的 str 对象,第二句,s 指向一个内容为 '456' 的 str 对象。第一句中的对象,没有被任何人引用了,之后会被 “废料收集” 程序废掉,但我们不能改变它的内容。另:参见附录一 《对 python 中引用问题的再思考》。
除此之外,函数就没什么神奇的地方了,比如可以通过 return 返回一个值等等,这些都是老僧禅痰了。
TRY...EXCEPT
对于出现 traceback 的错误来讲,可以通过 try...except 来挽救。比如:
def safeint(sval):
try:
return int(sval)
except:
return -1
这样,safeint('xxxx') 就不会出现 traceback 了,因为 int('xxx') 会被 except 捕获,正常返回 -1。
第二节. 面向对象的 python
其实前面的 str, list 还有 dict 都是某种意义上的类 (class),只不过是内置的而已。如前所述,dir() 可以列出类和对象的方法 (method)。
class 和 self
和 c++ 一样,python 也用 class 定义一个类:
class 类名称:
"类的文档字串,用来简单说明这个类"
成员变量
def __int__ (self, 参数): # [可选] 类似构造函数,但 __init__ 被(自动)调用时,对象已然被创建出来了
函数体
def __del__ (self): # [可选] 类似析构
函数体
def 成员函数 (self, 参数):
函数体
习惯上,和 c++ 不同的是,python 一般使用 self (是一种习惯而非强制,在 def 的时候可以使用其他的名字比如 this)而不是 this,但两者功能上类似,值得注意的是,self 需要在方法的参数中写定,并且引用成员变量的时候,一定要通过 self。比如:
class Person:
"store information of a person"
name = ''
age = 0
sex = 'male'
def textOut(self): # 这个 self 不可少
print 'name is: ', self.name, ' age is ', self.age, ' and sex is ', self.sex # 对成员变量的引用,亦必须通过 self
@staticmethod
def sayHello ():
print 'hello,world!'
p = Person()
p.textOut()
p.sayHello()
Person.sayHello()
如果成员函数不带 self 参数,则类似 c++ 中静态函数的概念,不能访问成员变量,但是需要在函数前面加 @staticmethod 修饰 (独占一行),静态方法可以通过对象调用,亦可以通过类名调用。如上。
另外需要注意的是很多以两个下划线开头和结尾的函数(专用方法),这些往往都有特殊用途,比如上面提到的 __init__ 和 __del__,还有 __add__, __eq__, __ge__, __gt__, __lt__, __le__, __ne__, __getitem__, __str__ 等等。比如 __getitem__ 用来实现 [] 操作,而 __str__ 用来实现 str() 操作,等等。
继承
python 通过这样的语法形式来实现继承:
class Derived (Base1[, Base2[, …]]]):
类实现
可以有多个逗号分割开的基类,用以实现多继承。有两个细节要注意:
如果定义了 __init__ 函数,那么必须在 __init__ 中调用基类的 __init__,方法为 基类名.__init__(self[, 其他参数])
调用基类的函数,务必手工添加 self 参数
一个例子:
class Namable:
name = ''
def __init__ (self, name):
self.name = name
def output (self):
print 'name is:', self.name
class Agable:
age = 0
def __init__ (self, age):
self.age = age
def output (self):
print 'age is:', self.age
class Person (Namable, Agable):
sex = 'male'
def __init__ (self, name, age, sex = 'male'):
Namable.__init__(self, name) # 显式调用基类的 __init__,带 self 参数
Agable.__init__(self, age)
self.sex = sex
def output (self):
Namable.output(self) # 调用基类的函数,亦需要带上 self 参数
Agable.output(self)
print 'sex is:', self.sex
def do_output (self):
self.output() # 非基类函数,则无需手工加上 self 参数
p = Person('doudehou', 32)
p.do_output()
附录
附录一. 关于 python 中引用问题的再思考
python 中所有的变量都是引用,这一点需要时刻牢记在心。什么意思呢?举个例子, d1 = dict() ,这句话的意思就是,我们首先创建了一个类型为 dict 的 (匿名)对象,接着创建了一个变量 d1,然后让 d1 “引用” 这个 dict 的对象,如果类比于 C 语言的话,可以理解为 dict *d1 = new dict()。d1 它只是一个引用。
这里和 C 语言存在一个巨大的差异。这个差异是什么呢?我们知道 C 语言提供了解引用的操作符 (*),这样程序员可以通过 *d1 来获得这个对象的本体,而作为脚本语言的 python,却并没有(也没有必要)提供这个功能。因此,除了通过 “引用” (换句话说,就是指针)的方式之外,我们无从触摸到这个对象的本体。
想象一下,如果 C 语言中没有了对指针做 * 的运算,我们要如何来修改某个对象呢?比如:
char c = 'A';
char *p = &c;
我们手上只有一个 p,要怎么修改 *p 的内容呢?毫无疑问,我们没有办法。而这就正是 python 里的现实情况。char 就是属于 immutable 的类型,我们再举一个 C++ 的例子:
class foo {
char m_c;
public:
foo () : m_c('A') {}
void set (char c) {
m_c = c;
}
char get () {
return m_c;
}
};
foo *p = new foo();
std::cout << "foo: " << p->get() << std::endl;
p->set('B');
std::cout << "foo: " << p->get() << std::endl;
在上面这个例子里,如果说我们仍然没有对指针的 * 运算,但我们依然可以改变 *p 的内容,这是因为 p 所指向的对象,具有修改自身的能力(提供了相关的方法)。这种类型,在 python 里面,就是 mutable 的。我们常见的 list,dict 等等,就属于此类。而 python 的字符串类,则属于 immutable 的。
这里有两个地方需要注意:
- 变量赋值,实际上并没有产生对象的拷贝,仅仅是 “引用” (指针) 的拷贝。比如:
d1 = {'1' : 1, '2' : 2, }
d2 = d1
d2['3'] = 3
for k in d1:
print k, ' => ', d1[k]
上面的代码,我们会发现,d1['3'] 也存在了,而且值就是 3。所以我们可以把 python 里面的所有变量都想象成指针;所有的变量对变量的 = 操作都想象成指针的赋值,所有的 . 操作都想象成 ->,那么则庶几不会有问题了。
- 通过显式构造函数的方法来拷贝对象,还是上面的例子,如果我们用:
d1 = {'1' : 1, '2' : 2, }
d2 = dict(d1)
d2['3'] = 3
for k in d1:
print k, ' => ', d1[k]
这样,d2 和 d1 就没有一毛钱的关系了,这种情况下,dict(d1) 产生了一个新的(匿名)dict 对象,同时将 d2 变成对它的引用。如果我们把所有 x = Type(var) 的赋值想象成 x = new Type(var) 就相当于抓住了事物的本质。