基于字典的字符串格式化
'%(n)d %(x)s' % {'n':1,'x':'spam'}
>>>'1 spam '
文件字符集编码声明
在脚本的第一行或第二行声明以下注释可以指明想要的编码,从而将默认编码修改为支持任意的字符集
# -*- coding: latin-1 -*-
字符串类型
python 2.x
- str表示8位文本和二进制数据
- unicode用来表示宽字符Unicode文本
python 3
- str表示Unicode文本(8位的和更宽的)
- bytes表示二进制数据
- bytearray,是一种可变的bytes类型
转换(python 3)
- str.encode()和bytes(s, encoding)把一个字符串转换为其raw bytes形式
bytes.decode()和str(b, encoding)把raw bytes转换为其字符串形式。
s='eggs' s.encode() >>>b'eggs' bytes(s, encoding='ascii') >>>b'eggs' b=b'spam' b.decode() >>>'spam' str(b, encoding='ascii') >>>'spam'
如果str函数省略了编码参数,返回的是打印字符串而不是转换后的字符串,e.g.
b=b'spam' str(b) >>>"b'spam'" len(str(b)) >>>7
字节常量要求字符要么是ascii字符,如果它们的值大于127就进行转义(只能以十六进制转义)。
字符串常量可以包含字符集中的任何字符(十六进制或Unicode转义)。
文本文件和二进制文件(python3)
- 以文本模式打开(e.g. w,r,w+,r+),读取数据会自动将其内容解码(默认编码或提供一个编码名称),并且将其返回为一个str,写入会接受一个str。
- 以二进制模型打开(e.g. wb, rb),读取其数据不会以任何方式编码它,直接返回其内容raw并且未经修改作为一个bytes对象,写入也类似地接受一个bytes对象。
作用域
LEGB法则
- 本地作用域(L)
- 上一层的def或lambda的本地作用域(E)
- 全局作用域(G)
- 内置作用域(B)
在函数内引用全局变量不需要global声明,赋值则需要。
nonlocal的作用与global类似,不过是用于嵌套的函数作用域。
在某个函数内部调用一个以后才定义的函数是可行的,只要第二个函数定义的运行是在第一个函数调用前就行。
nonlocal使得对变量的查找从嵌套的函数作用域中开始,而不是从声明函数的本地作用域开始,并且不会继续到全局或内置作用域中。
nonlocal声明的变量必须在一个嵌套的函数中提前定义过,否则会产生一个错误。
encode和decode
unicode是python字符串的内部编码方式,因此encode和decode都是围绕着unicode编码来进行编码转化的。decode是将其他编码的字符串解码为unicode编码,而encode则是将unicode编码的字符串编码为另一种编码。
例如:str.decode(‘utf8’),这个语句将已知是utf8编码的字符串str解码为unicode,而str.encode(‘utf8’)则是将unicode编码的字符串str编码为utf-8。
一般情况下,字符串的编码跟代码文件的编码保持一致,通过在字符串面量值前面加上字母u(例如:str = u’test’)可以强制指定为unicode编码而忽略文件的编码。
nonlocal (python3)
- nonlocal使得对名称的查找从嵌套的def的作用域中开始,而不是从声明的函数的本地作用域开始
- nonlocal使得可以对变量进行修改而不只是引用
- nonlocal中列出的名称必须在一个嵌套的def中提前定义过,否则会产生错误,global则不需要
- nonlocal限制作用域查找仅为嵌套的def,不会在模块的全局作用域或内置作用域中查找
函数参数
参数顺序:
- 函数调用:位置参数,关键字参数(name=value)和*sequence形式的组合,**dict形式
- 函数声明:一般参数(name),默认参数(name=value),*name形式(如果有),keyword-only参数(name或name=value)(python3才可以),**name形式
参数匹配顺序:
- 通过位置分配非关键字参数
- 通过匹配变量名分配关键字参数
- 其他额外的非关键字参数分配到*name元组中
- 其他额外的关键字参数分配到**name字典中
- 用默认值分配给未得到分配的参数
注意:
- 在函数调用和声明中*name和**name的形式都只能出现一次,并且**name形式只能出现在最后
位置参数的匹配优先于关键字参数,e.g.
def f(a,b,c): print a,b,c f(a=1,*(1,2))
这样的调用会出错,参数a会被匹配两次,因为位置参数先匹配,所以参数a会赋值为1,参数b会赋值为
2,接着再进行关键字参数的匹配,这时再次对参数a进行匹配,所以就出错了 再看个例子:def f(a,b,c,d): print a,b,c,d f(1,c=3,*(2,),d=4)
位置参数会先匹配,所以参数b被赋值为2,在* sequence形式的后面只能是关键字参数或* * dict形式,如果是这样调用则会出错:f(1,c=3,*(2,),4)
在函数调用时,*sequence形式接受任意可迭代对象,e.g.
def f(a,b,c,d): print a,b,c,d l = [1,2,3,4] it = iter(l) f(*it) f(*open('XXX'))
python3支持keyword-only参数,在函数声明中,keyword-only参数只能出现*arg或单独的*字符(不需要可变数量位置参数)后面,在函数调用时,这些参数只能使用关键字语法来传递。e.g.
def f(a,*b,c): print a,b,c f(1,2,3) #出错,c只能以关键字形式传递 def f(a,*,b,c): print a,b,c f(1,2,3) #出错,b,c都只能以关键字形式传递 keyword-only参数也支持默认参数
函数注解(python3)
def f(a:'test',b:1,c:(1,2)=100)->'hello':
print(a,b,c)
>>> f.__annotations__
{'return': 'hello', 'c': (1, 2), 'a': 'test', 'b': 1}
函数注解只在def语句中有效,在lambda表达式中无效
生成器函数之send
send函数传入的参数会作为yield表达式的返回值,e.g.
def f():
for i in range(10):
x = yield i
print 'send ' + str(x)
>>> g = f()
>>> next(g)
0
>>> g.send(10)
send 10
1
生成器表达式
e.g.
g = (x for x in range(10)
>>> next(g)
0
>>> next(g)
1
实际上,列表解析基本等同于在list内置调用中包含一个生成器表达式以迫使其一起生成列表中所有的结果。e.g.
[x ** 2 for x in range(8)]
list(x ** 2 for x in range(8))
如果生成器表达式作为函数调用中的唯一参数,括号是可以省略的,其他情况则不行。e.g.
sum(x for x in range(10))
sorted(x for x in range(10))
sorted((x for x in range(10)), reverse=True)
函数内的本地变量是静态检测的
x = 99
def f():
print x
x = 1
>>> f()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in f
UnboundLocalError: local variable 'x' referenced before assignment
在编译时,python看到对x的赋值语句(也包括import,嵌套def,嵌套类等)决定了x将在函数中的任何地方都将是本地变量名,即使运行时赋值语句是在print语句之后运行的
因为上述的原因,所以在函数中,不可能同时使用同一变量名的本地变量和全局变量,如果希望在函数内打印全局变量,之后使用同一变量名的本地变量,可以通过以下方式实现:
#test.py
x=99
def f():
import test
print test.x #打印全局变量x的值
x = 88
print x #打印本地变量x的值
if __name__ == '__main__':
f()
print x #打印全局变量x的值
$python test.py
99
88
99
函数默认参数陷阱
默认参数是在def语句运行时评估并保存的,而不是在这个函数被调用的时候,从内部来讲,python会为每个默认参数保存成为一个对象,附加在这个函数本身。e.g.
>>> def f(x=[]):
... x.append(1)
... print x
...
>>> f()
[1]
>>> f()
[1, 1]
>>> f()
[1, 1, 1]
import
python会把载入的模块存储到一个名为sys.modules的表中,只有在模块第一次导入时才会进行加载。
import的三个步骤:
- 找到模块文件
- 编译成位码(需要时)
- 执行模块的代码来创建其所定义的对象
只有被导入的(.py)文件才会生成字节码文件(.pyc)
模块搜索路径
按照搜索的顺序排列如下:
- 程序的主目录,顶层脚本文件的目录而非当前工作目录。
- PYTHONPATH目录(如果已经设置该环境变量)
- (当前工作目录)
- 标准链接库目录
- 任何.pth文件的内容(如果存在的话)[路径文件作为第三方库经常使用,它通常在python的site-packages目录安装一个路径文件,从而不需要用户设置]
其中第一和第三是自动定义的,第二和第四用于拓展路径
可以通过打印sys.path列表查看搜索路径,导入模块时,python会由左至右搜索这个列表中的每个目录
模块文件选择
import b形式的可能会加载:
- 源代码文件b.py
- 字节码文件b.pyc
- 目录b,包导入
- 编译扩展模块(通常用c或c++编写),导入时使用动态连接(例如,linux的b.so以及cygwin和windows的b.dll或b.pyd)
- 用c编写的编译好的内置模块,并通过静态连接至python
- zip文件组件,导入时会自动解压缩
- 内存内映像,对于frozen可执行文件
- java类,在jyhon版本的python中
- net组件,在IronPython版本的python中
重载模块
- 导入(无论是是通过import或from语句)只会在模块在流程中第一次导入时,加载和执行该模块的代码,之后的导入只会只用已加载的模块对象,而不会加载或重新执行文件的代码
- reload函数会强制已加载的模块的代码重新载入并重新执行。此文件中新的代码的赋值语句会在适当的地方修改现有的模块对象
- reload会在模块当前命名空间内执行模块文件的新代码 。重新执行模块文件的代码会覆盖其现有的命名空间,并非进行删除而进行重建
- 文件中顶层的赋值语句会使得变量名换成新值。例如,重新执行的def语句会因为重新赋值函数变量名而取代模块命名空间内该函数之前的版本
- 重载会影响所有使用import读取了模块的客户端。因为使用import的客户端需要通过点号运算取出属性,在重载后,他们会发现模块对象中变成了新的值
- 重载只会对以后使用from的客户端造成影响。之前使用from来读取属性的客户端并不会受到重载的影响,那些客户端引用的依然是重载前所取出的旧对象
包导入
- 包导入语句的路径中每个目录内都必须有_ init_.py这个文件,否则导入包会失败
- python首次导入某个目录的时候,会自动执行该目录下_ init_.py文件中的所有程序代码(可以为空)
- _ init_.py为目录创建了命名空间,该空间包含了在_ init_.py中赋值的所有变量名
- 可以在_ init_.py中使用_ all_列表来定义目录以from*语句形式导入时,需要导出什么。如果没有指定_ all_,from*语句不会自动加载嵌套于该目录内的子模块,只会加载该目录_ init_.py文件中赋值语句定义的变量名,包括该文件中明确导入的任何子模块
包相对导入
相对导入只适用在包内的import
不带点号的import,e.g.
import A
from A import B
在python2.6中先相对(包目录)再绝对(sys.path)的搜索路径顺序
在python3中则是绝对的(sys.path),而不会在包目录下搜索
带点号的import,e.g.
from . import A
from .A import B
from .. import A
from ..A import B
在python2.6和python3中都是只会在包目录下搜索
在模块中隐藏数据
- 在变量名前面加一个下划线(e.g. _X)可以防止使用from *语句导入模块时,把这些变量名复制出去
- 在模块顶层把变量名的字符串列表赋值给变量_ all_,from *语句只会把这些变量名复制出去
- python会先寻找模块内的_ all_列表,如果没有定义,from *就会复制出开头没有单下划线的所有变量名
使用字符串导入模块
>>> sys=__import__('sys')
>>> sys
<module 'sys' (built-in)>
类属性与实例属性
- 在方法内对self属性做赋值运算会产生每个实例自己的属性
- 在class语句内的顶层的赋值语句(不是在def之内)会产生类对象中属性,该属性被所有实例共享
- 对实例属性的搜索顺序:先在实例对象中寻找,然后是创建实例的类,之后是所有较高的超类,由对象树底端到顶端,并且从左到右
- 继承树的搜索只发生在属性引用时,而不是属性的赋值运算时
调用类方法的两种方法
- instance.method(args…)
- class.method(instance, args…)
class Child(Base):
def method(self,arg):
Base.method(self,arg)
运算符重载(python3)
- _ init_ 构造函数
- _ del_ 析构函数
- _ add_,_ radd_,_ iadd_ 运算符+,+=
- _ or_ 运算符|(位OR)
_ repr_,_ str_ 打印,转换
- _ str_用于str内置函数和print,它通常返回一个用户友好的显示
- _ repr_用于所有其他环境中:用于交互模式下提示回应和repr函数
- 交互模式下,只使用_ repr_,并不尝试_ str_
- _ repr_和_ str_都必须返回字符串,其他的结果类型不会转换并会引发错误
_ str_只对打印操作顶层才有用,嵌套到其他对象中的对象用_ repr_或默认方式打印
class Printer: def __init__(self, val): self.val = val def __str__(self): return str(self.val) >>> objs = [Printer(2), Printer(3)] >>> for x in objs: print(x) 2 3 >>> print(objs) [<__main__.Printer object at 0x025D06f0>, <__main__.Printer object at ...
为了确保一个定制显示在所有的环境下都显示而不管容器是什么,请编写_ repr_,而不是_ str_
class Printer: def __init__(self, val): self.val = val def __repr__(self): return str(self.val) >>> objs = [Printer(2), Printer(3)] >>> for x in objs: print(x) 2 3 >>> print(objs) [2, 3]
_ call_ 函数调用
_ getattr_ 点号运算 X.undefined
当通过对未定义(不存在)属性名称和实例进行点号运算时,就会用属性名称作为字符串调用这个方法,如果通过其继承树搜索流程找到这个属性,该方法就不会被调用。class X: def __init__(self): self.attr1 = 1 def __getattr__(self, attrname): print attrname >>> x = X() >>> x.attr1 1 >>> x.attr2 'attr2'
_ setattr_ 属性赋值语句 e.g. X.any = value
该方法会拦截所有属性的赋值语句,如果在该函数中对任何self属性做赋值,就会再调用该函数,导致了无穷递归循环,所以得通过对属性字典做索引运算来赋值任何实力属性。- _ delattr_ 删除属性 e.g. del X.any
- _ getattribute_ 属性获取 X.any,访问任何属性(数据和函数)都会调用该函数,_ getattribute_的默认实现会在在属性不存在时会抛出AttributeError,导致_ getattr_被调用
_ getitem_ 索引或分片运算,e.g. X[key],X[i:j]
class X: def __getitem__(self, index): print x >>> x = X() >>> x[2] 2 >>> x[0:10:2] slice(0, 10, 2)
_ setitem_ 索引或分片赋值语句,e.g. X[key]=value,X[i:j]=value
class X: def __setitem__(self, index, value): print index, value >>> x = X() >>> x[0] = 1 0 1
- _ delitem_ 索引和分片删除,e.g. del X[key],del X[i:j]
- _ len_ 长度,e.g. len(X)
- _ bool_(_ nonzero_) 布尔测试,e.g. bool(X)
- _ lt_,_ gt_ ,_ le_,_ ge_,_ eq_,_ ne_比较,e.g. X< Y,X>Y
- _ radd_ 右侧加法,e.g. other+X
- _ iadd_ 原地加法,e.g. X+=Y
- _ iter_,_ next_ 迭代,e.g. I=iter(X),next(I)
_ getitem_和_ iter_都支持迭代,在所有迭代环境中,Python首先尝试使用_ iter_(它返回支持迭代协议的一个对象,该对象带有一个_ next_方法,直到发生StopIteration异常),如果在继承搜索中找不到_ iter_,Python就会使用_ getitem_索引方法,直到发生IndexError异常。 - _ contains_ 成员关系测试,e.g. item in X
_ index_ 整数值,e.g. hex(X),bin(X),oct(X)
class X: def __index__(self): return 255 >>> x = X() >>> hex(x) '0xff' >>> bin(x) '0b11111111' >>> oct(x) '0o377' >>> ('c'*256)[x] 'c'
_ enter_,_ exit_ 环境管理器,e.g. with obj as var:
- _ get_,_ set_ 描述符属性
- _ delete_
- _ new_ 在_ init_之前创建对象
抽象超类
python 3.0
from abc import ABCMeta, abstractmethod
class Super(metaclass=ABCMeta):
@abstractmethod
def method(self, ...):
pass
python 2.6
class Super:
__metaclass__ = ABCMeta
@abstractmethod
def method(self, ...):
pass
新式类
- 在python3中所有类都是新式类,所有类都继承自object,不管它们是否显示地继承object
- 在python2.6中,类必须继承object(或者其他的内置类型)才是新式类
新式类的类模式变化
#对于经典类来说,对用户自定义类型和内置类型的处理是不一样的
Class C:pass
>>> I = C()
>>> type(I)
<type 'instance'>
>>> I.__class__
<class __main__.C at 0x025085A0>
>>> type(C)
<type 'classobj'>
>>> C.__class__
AttributeError: class C has no attribute '__class__'
>> type([1,2,3])
<type 'list'>
>>> type(list)
<type 'type'>
>>> list.__class__
<type 'type'>
对新式类来说,用户自定义类型和内置类型是一样的
class C(object): pass
>>> I = C()
>>> type(I)
<class '__main__.C'>
>>> I.__class__
<class '__main__.C'>
>>> type(C)
<type 'type'>
>>> C.__class__
<type 'type'>
>> type([1,2,3])
<type 'list'>
>>> type(list)
<type 'type'>
>>> list.__class__
<type 'type'>
类的伪私有属性
class语句内开头有两个下划线,但结尾没有两个下划线的变量名(无论是类还是实例属性,无论是方法还是数据),会自动扩张,从而包含了所在类的名称:原始的变量名会在头部加入一个下划线,然后是所在类名称
class X:
def __init(self):
self.__a = 1
>>> x = X()
>>> x.__dict__
{'_X__a':1}
新式类的继承搜索顺序
属性搜索处理沿着树层级,以更加广度优先的方式进行
slots(新式类)
在class语句顶层内将字符串名称顺序赋值给变量_ slots_:只有_ slots_列表内的这些变量名可以赋值为实例属性,使用了slots,实例通常没有一个属性字典。通过在_ slots_中包含_ dict_仍然可以容纳额外的属性
class X(object):
__slots__ = ['a','b','__dict__']
c = 1
def __init__(self):
self.a = 1
self.d = 1
>>> x = X()
>>> x.__slots__
['a','b','__dict__']
>>> x.__dict__
{'d':1}
类特性(新式类)
(新式类的)特性是一种可以替代_ getattr_和_ setattr_的方法
class X(object):
def getage(self):
return 40
def setage(self, value):
print 'set age:', value
self._age = value
age = property(getage, setage, None, None)
>>> x = X()
>>> x.age
40
>>> x.age = 42
set age: 42
>>> x._age
42
>>> x.job = 'trainer'
>>> x.job
'trainer'
无self“静态”方法
- python2.6类方法总是要求传入一个实例,不管是通过一个实例还是类都无法调用无self方法
- python3只能通过类调用而无法通过实例调用
静态方法和类方法
class X:
def sf():
print 'static method'
def cf(cls):
print 'class method', cls
sf = staticmethod(sf)
cf = classmethod(cf)
>>> x = X()
>>> x.sf()
'static method'
>>> x.cf()
'class method' <class '__main__.X'>
>>> X.sf()
'static method'
>>> X.cf()
'class method' <class '__main__.X'>
函数装饰器
函数装饰器把一个函数当作参数并且返回一个可调用对象
class tracer:
def __init__(self, func):
self.func = func
def __call__(self, *args):
print 'call tracer'
self.func(*args)
@tracer
def f(a,b,c):
print a,b,c
>>> f(1,2,3)
'call tracer'
1 2 3
类装饰器
def addcount(cls):
cls.count = 0
return cls
@addcount
class A:pass
@addcount
class B:pass
>>> A.count
0
>>> B.count
0
元类
class Meta(type):
def __new__(meta, classname, supers, classdict):...
class C(metaclass=Meta):...
元类通过重新定义type类的_new_或_init_方法,以实现对一个新的类对象的创建和初始化的控制。
管理属性
- _getattr_和_setattr_方法,把未定义和属性获取和所有的属性赋值指向通用的处理器方法。
- _getattribute_方法,把所有属性获取都指向新式类的该泛型处理器方法
property内置函数,把特定属性访问定位到get和set处理器函数,也叫做特性
常规写法
attribute = property(fget, fset, fdel, doc)
装饰器写法
class Person: def __init__(self, name): self._name=name @property def name(self): "name property docs" print('fetch...') return self._name @name.setter def name(self, value): print('change...') self._name=value @name.deleter def name(self): print('remove...') del self._name
描述符协议,把特定属性访问定位到具有任意get和set处理器方法的类的实例
class Descriptor: "docstring goes here" def __get__(self, instanc, ower): ... def __set__(self, instance, value): ... def __delete(self, instance): ... class Subject(): attr=Descriptor() e.g. class Descriptor(object): def ____get____(self, instance, owner): print(self, instance, owner, sep='\n') class Subject: attr=Descriptor() x=Subject() x.attr >>><____main____.Descriptor object at xxx> >>><____main____.Subject object at xxx> >>><class '____main____'.Subject> Subject.attr >>><____main____.Descriptor object at xxx> >>>None >>><class '____main____.Subject'>
装饰器
- 函数装饰器
def decorator(F):
#process function F
return F
@decorator
def func():...
def decorator(F):
#save or use function F
return G
@decorator
def func():...
class decorator:
def __init__(self, func):
self.func = func
def __call__(self, *args):
#use self.func and args
@decorator
def func(x, y):...
基于类的装饰器无法用于类方法
e.g.
class decorator:
def __init__(self, func):
self.func = func
def __call__(self, *args):
#self.func(*args) fails! C instance not in args!
class C:
@decorator
def method(self, x, y): ...
- 类装饰器
异常
try语句分句形式
- except: 捕获所有异常
- except name: 只捕获特定的异常
- except name,value: 捕获所列的异常和其额外的数据(或实例)
except name as value: 在python3中改成这种写法 - except (name1, name2): 捕获任何列出的异常
- except (name1, name2), value: 捕获任何列出的异常,并取得其额外的数据
- else: 如果没有引发异常,就运行
- finally: 总是会运行此代码块
- 即使except和else块内引发了新的异常,finally块的代码仍然会执行
- 如果出现一个else的话,必须有至少一个except
- raise
- rasie < instance >
- raise < class >
- raise 重新抛出最近的异常
- raise exception from otherexception(python3)第二个异常会附加到第一个异常的_ cause_属性
- 在except内部引发一个新异常的时候,前一个异常附加新异常的_ context_属性
- assert < test >, < data >,如果使用-O命令行标志位就会关闭assert
环境管理器
- 必须有_ enter_和_ exit_方法
- 如果as子句存在,其_ enter_返回值会赋值给as子句的变量,否则,直接丢弃
- 如果with代码块引发异常,_ exit_(type, value, traceback)方法会被调用,如果此方法返回值为假,则异常会重新引发,否则异常会终止
- 如果with代码块没有引发异常,_ exit_方法仍然会被调用,其type,value和traceback都会以None传递
参考自《Python学习手册》