语言要点

特性简介

Python对程序格式有较强的限定,如下所示:

  • 注释符为#
  • 无需分号表示语句结束,长语句必要时需加\续行
  • 用相同缩进来表达同一语句块,建议用4个空格作为缩进
  • 默认只接受ASCII字符集,故通常需编码声明:# -*- coding: utf-8 -*-

数据与操作

数据类型

  • 数字类型:整型、浮点型和复数型
  • 序列类型:字符串、元组、列表
  • 映射类型:集合、字典
  • 空类型:None对象
  • 可调用类型:实现了__call__()方法的类
  • 布尔类型:语义比较广泛,通常非空为True,空集为False

基本操作

  • del解除变量或序列
  • .取对象中成员
  • []取集合中元素
  • ()函数调用
  • 比较操作允许链式表达a < b < c < d
  • 没有三目算符,但有三元表达:returntrue if condition else returnfalse
  • 所有序列都支持元素存在测试:if item in seqs

列表操作

x = [1, 2, 3, 4, 5]
del x[1]; print x
del x[::2]; print x
[1, 3, 4, 5]
[3, 5]
l.count(x)
x出现次数
l.index(x)
x出现下标
l.append(x)
在尾部加入x
l.extend(x)
插入x中的所有元素,append则是将x当作一个元素插入
l.insert(i, x)
在下标i处插入x
l.remove(x)
删除x第一次出现,如果未出现则引发异常
l.pop(i=-1)
删除下标为i的元素,返回删除的元素,如果下标越界,引发异常
l.reverse()
反转链表,无返回值
l.sort(cmp=None, key=None, reverse=False)
如指定key则以key(x)替代x参与运算

集合操作

s.copy()
影子复制
s.difference(s1)
s出现s1未出现
s.intersection(s1)
s和s1同时出现
s.issubset(s1)
s是s1的子集
s.issuperset(s1)
s1是s的子集
s.symmetric_difference(s1)
只在s和s1其一出现
s.union(s1)
在s或s1中出现
s.add(x)
添加x到集合
s.clear()
清除集合
s.discard(x)
删除x,如果不存在也没关系
s.pop()
随机弹出一个元素并返回
s.remove(x)
如果x不存在会产生异常

字典操作

d.copy()
影子复制
d.has_key(x)
测试是否有键x
d.items()/d.iteritems()
返回(key, value)对列表/迭代器
d.keys()/d.iterkeys()
键列表/迭代器
d.values()/d.itervalues()
值列表/迭代器
d.get(k, x=None)
这个函数非常有用,如果k存在就返回d[k],否则返回x
d.clear()
清除字典
d.update(d1)
等价于:for k in d1: d[k] = d1[k]
d.setdefault(k, x=None)
如果k存在就返回d[k],否则将d[k]设置为x并返回
d.pop(k, x=None)
弹出d[k],没有找到就返回x,如果未指定x又没找到d[k]会引发异常
d.popitem()
随机弹出一个元素

流程控制

分支语句

if condiiton:
    statements
elif condition:
    statements
else:
    statements

要判断一个变量是否为真,直接的方式是if x,请不要使用如下的一些形式:

if x is True
if x == True
if bool(x)

循环语句

while condition:
    statements
for item in iterable:
    statements
while condition:
    statements
else:
    statements                          # 只要while没有被break就会执行
[expression for item in iterable clauses]
[x + 1 for x in range(10)]
[x + 1 for x in range(10) if x % 2]
[x + y for x in range(10) for y in range(10)]

跳出循环有两种方式:

break
退出循环
continue
进入下一轮循环

函数

def functionname(parameters):           # 命名函数
    statements

lambda parameters: expression           # 匿名函数

函数默认将变量绑定到局部名称空间,要使用全局的名字,就需要在函数中作如下声明。

global indentifiers

属性

def sum_args(*nums):
    '''accept arbitrary numerical arguments and return their sum'''
    return sum(nums)

print sum_args.__name__
print sum_args.__doc__
print sum_args(1, 2, 3)
sum_args
accept arbitrary numerical arguments and return their sum
6

生成器

yield expression
def updown(n):
    for x in range(n):
        yield x
    for x in range(n - 1, -1, -1):
        yield x
for i in updown(10):
    print i,
0 1 2 3 4 5 6 7 8 9 9 8 7 6 5 4 3 2 1 0

闭包与装饰器

函数装饰器是通过嵌套函数来实现的,或者说是闭包来实现的。闭包的主要特点是即使外部函数已经返回,内部函数依然可以访问外部函数的局部变量。

def func_log(func):
    n = [0]
    class last: pass
    last.arg = None
    last.val = None
    def log(arg):
        n[0] += 1
        line = 'call {}th {}({}) = {}, last call {}({}) = {}'
        print line.format(n[0], func.__name__, arg, func(arg),
                          func.__name__, last.arg, last.val)
        last.arg = arg
        last.val = func(arg)
    return log

@func_log
def power(n):
    return n ** 2

for i in range(3):
    power(i)
call 1th power(0) = 0, last call power(None) = None
call 2th power(1) = 1, last call power(0) = 0
call 3th power(2) = 4, last call power(1) = 1

Python2不允许修改外部作用域的局部变量,上面的例子分别通过修改可变类型和定义局部类型来实现修改外部作用域的值。

闭包本身可以看作是函数加上引用环境,当内部函数引用外部变量时,编译器/解释器会把函数和涉及到的引用环境打包为一个整体,所以称作闭包。

注意事项:函数只有在执行时才会去查找变量的值:

flist = []
for i in range(3):
    def foo(x): print x + i
    flist.append(foo)
for f in flist:
    f(2)
4
4
4

比包的用途不仅在于提供装饰器,也可用于保存运行环境、根据外部变量来生成不同结果等。

类与继承

基本概念

Python2支持经典模型和新风格,Python3只支持新风格,所以最好是只使用单根继承。类型存储静态字段和方法,实例只存储数据成员,也称为attribute。访问对象成员时,按照如下顺序查找: instance.__dict__class.__dict__baseclass.__dict__

注意不要和名字查找混淆,名字查找采用LEGB顺序在不同的作用域查找:

locals
函数内部名字空间,形参和局部变量
enclosing
嵌套函数的外部函数
globals
函数所在模块名字空间
__builtins__
内置模块名称空间

私有字段建议单下划线开头,提示即可。

属性propertygetter()setter()deleter()几个方法构成。从字面上attributeproperty都是属性,但前者是比较直接的数据成员,后者在行为上和数据成员相同,但是在实现上来看是将成员函数伪装成数据成员。

class Example(object):
    @property
    def name(self): return self.__name

    @name.setter
    def name(self, val): self.__name = val

    @name.deleter
    def name(self): del self.__name

e = Example()
e.name = "example"
print e.__dict__, e.name
del e.name
print e.__dict__
{'_Example__name': 'example'} example
{}

从实现上看,属性property是特殊处理过的attribute,所以访问优先级高于同名数据成员。

特殊方法

要实现类的方法,需要添加装饰器修饰:

class Example(obejct):
    @staticmethod
    def static_print(os):
        print os

特殊方法:

__new__()
创建对象实例
__init__()
初始化对象状态
__del__()
对象回收前调用

修改字段要用指定函数:

setattr(obj, "name", value)
u.name = value
hasattr(obj, "name")
obj.__dict__["name"]
getattr(obj, "name", default=None)
如果未找到返回default
delattr(obj, "name")
del obj.__dict__["name"]

索引操作符[]也可以重载,自定义__setitem____getitem____delitem__即可。函数对象/仿函数通过定义__call__()来重载。

__getattr__()
访问不存在的成员
__setattr__()
对成员赋值
__delattr__()
删除成员
__getattribute__()
访问任何存在或不存在的成员,包括__dict__

在定义这些函数的时候,不能调用setattrgetattr等函数,因为会造成死循环,只能直接访问__dict__。而__getattribute__更狠,会拦截__dict__,所以它唯一能调用的就是基类的__getattribute__

继承

Python提供了两个函数来判定类型关系:

issubclass(a, base)
判断a是否是base的继承
isinstance(a, class)
判断a是否是class的实例

多重继承MRO(method resolution order):从下到上,从左到右。

class A(object):
    def print_a(self):
        print "a:a"
class B(object):
    def print_a(self):
        print "b:a"
    def print_b(self):
        print "b:b"
class C(A, B):
    def print_c(self):
        base = super(C, self)
        base.print_a()
        base.print_b()
C().print_c()
a:a
b:b

Python也支持抽象类,只需要添加修饰即可,派生类必须实现所有抽象函数/属性才能实例化:

from abc import ABCMeta, abstractmethod, abstractproperty
class AbsExample(object):
    __metaclass__ = ABCMeta
    @abstractmethod
    def print_id(self):
        pass

    name = abstractproperty()

class Example(AbsExample):
    def print_id(self):
        print "id"
    name = property(lambda s: s._name,
                    lambda s, v: setattr(s, "_name", v))

e = Example()

Python还有一个特性叫开放类,可以在运行期增加/删除对象成员。

def print_id(self):
    print self.id

def print_class_id(cls):
    print "class: ", cls

def print_static():
    print "static"

class User(object):
    def __init__(self):
        self.id = "self id"

User.print_id = print_id
User.print_class_id = classmethod(print_class_id)
User.print_static = staticmethod(print_static)
u = User()
u.print_id()
User.print_class_id()
User.print_static()
self id
class:  <class '__main__.User'>
static

注意事项

兼容性问题

输入和输出

在Python2中print是一个表达式,而Python3中print是一个函数,因此在写print时,建议用函数式写法。

在Python2中input函数会计算用户的输入,要防止计算就需要用raw_input, Python3中将raw_input删除了,而input的含义等同于python2中的raw_input。

字典

在python2中字典有两个迭代函数,一个返回列表,一个返回迭代器。

d.items()
返回(key, value)对列表
d.iteritems()
返回(key, value)对迭代器

但在Python3中,直接将迭代器形式删除了,而原来的列表形式变成了迭代器形式。所以为了兼容性,还是推荐Python3的写法。

d.items()
返回(key, value)对迭代器

注意事项

慎用__del__()方法

文章 慎用python的__del__方法 比较详细分析了这个缺陷。从语义上看,__del__相当于C语言中的析构函数,但是问题是它不一定被调用。也就是说如果你实现了__del__方法就不允许出现循环引用,否则垃圾回收器将因为不知道释放顺序而放弃释放。

class A(object):
    def __init__(self, parent):
        print "A init"
        self.parent = parent

    def __del__(self):
        print "A del"

class B(object):
    def __init__(self):
        print "B init"
        self.child = A(self)

    def __del__(self):
        print "B del"

b = B()
B init
A init

内存泄漏

Python中的内存泄漏主要有两方面的原因:一种情况是对于长期运行程序而言,实例没有及时释放所有的引用导致内存泄漏。另一种情况是出现交叉引用,垃圾回收器不知道释放顺序而放弃释放。