Python tutorial 3.4 documentation的阅读笔记

来源:互联网 发布:linux恢复删除文件命令 编辑:程序博客网 时间:2024/05/16 10:23
Edit

python学习笔记

本文是Python tutorial 3.4 documentation的阅读笔记

补充了来自学习手册的读书笔记

1. python如何运行程序

1.1 先编辑成.pyc字节码文件,源代码底层的、平台无关的表现形式
1.2 然后发送到PVM python虚拟机
1.3. 程序开始之前不需要预编译和连接,是python具有更多的动态语言特性:可以运行时构建和运行另一个python程序(如eval,exec)
1.4. cpython;jpython(与java集成);ironpython(与.net等集成)
1.5. 冻结二进制文件,使用py2exe之类的系统,冻结二进制文件中内置了pvm,所以会变大
1.6. 如何制作unix可执行脚本
1.6.1. #! /usr/local/bin/python3 (或者#!/usr/local/bin/env python)
1.6.2. chmod +x
1.7. windows中点击自动运行的python文件需要添加input()
1.8. 属性就是绑定在特定的对象上的变量名;dir可以获取此le
1.9. exec(open(../testunixrun.py).read()) exec运行相当于粘贴了代码运行
1.10 help(dir)打印帮助文档

2. 使用 Python 解释器

2.1. 调用 Python 解释器

2.1.1. 参数传递

调用解释器时,脚本名和附加参数传入一个名为 sys.argv 的字符串列表。

2.2. 解释器及其环境

2.2.2. 执行 Python 脚本

BSD 类的 Unix 系统中,Python 脚本可以像 Shell 脚本那样直接执行。只要在脚本文件开头写一行命令,指定文件和模式

#! /usr/bin/env python3.3

2.2.3. 源程序编码

默认情况下,Python 源文件是 UTF-8 编码。你也可以为源文件指定不同的字符编码。 为此,在 #! 行(首行)后插入至少一行特殊的注释行来定义源文件的编码。

# -*- coding: encoding -*-

2.2.5. 本地化模块

Python 提供了两个钩子(方法)来本地化: sitecustomize 和 usercustomize。
sitecustomize.py 是python中的一个特殊脚本,可以放在目录下的任意位置,不过一般放在home\Lib\site-packages下面,当python开始运行时会先运行该脚本中的代码,因此可以用来设置一些default的东西。

3. Python 简介

3.0. python表达式操作符

Alt text

Alt text

3.1. 将 Python 当做计算器

3.1.1. 数字

  1. 对整数做除法运算并去除小数部分取得整数结果://
  2. 浮点数有完整的支持;与整型混合计算时会自动转为浮点数
  3. 复数也得到支持;带有后缀 j 或 J 就被视为虚数。带有非零实部的复数写为 (real+imagj) ,或者可以用 complex(real, imag) 函数创建。
  4. 转换:数字《=》为(不同进制的)字符串
oct(64),hex(64),bin(64) #('0o100', '0x40', '0b1000000')int('100000',2),int('40',8)# (32,32)eval('0b100000'),eval('0O40')# (32,32)'{0:b},{1:o}'.format(32,32)#'100000,40'

3.1.2. 字符串

Alt text

Alt text

Alt text

  1. 字符串可以标识在一对儿三引号中: """ 或 ''' 。三引号中,不需要行属转义,它们已经包含在字符串中
  2. 生成一个“原始”字符串:不转义.aStr = r"/n"
  3. 字符串可以由 + 操作符连接(粘到一起),可以由 * 重复;相邻的两个字符串文本自动连接在一起(只用于两个字符串文本,不能用于字符串表达式)
    word = 'Help' + 'A' #或word = 'Help' 'A'words = '<' + word*5 + '>'
  4. 字符串也可以被截取/检索/切片word[:2]/word[:-2];内置函数 len() 返回字符串长度
    #切片位置图+---+---+---+---+---+| H | e | l | p | A |+---+---+---+---+---+(-)0   1   2   3   4   5-5  -4  -3  -2  -1
  5. Python 字符串不可变。向字符串文本的某一个索引赋值会引发错误
  6. ASCII<=>str ord() chr()
  7. python3 字符串类型
    7.1 str表示Unicode文本
    7.2 bytes表示二进制数据;
    7.3 bytesarray可变的bytes类型
  8. BOM略 utf-8-sig
>>> B = b'spam'# Make a bytes object (8-bit bytes)>>> S = 'eggs'# Make a str object (Unicode characters, 8-bit or wider)>>> type(B), type(S)#(<class 'bytes'>, <class 'str'>)//转换>>> a = "好">>> a.encode('utf-8') #b'\xe5\xa5\xbd'>>> b = b'\xe5\xa5\xbd' #list(b)[229, 165, 189]>>> b.decode('utf-8') #'好'>>> str(b)#"b'\\xe5\\xa5\\xbd'" #不带编码的一个s t r调用返回b y t e s对象的打印字符串,而不是其 str转换后的形式>>> str(b,encoding="utf-8")#'好'//系统默认编码sys.getdefaultencoding()//区别unicode str和真正的bytes   Unicode只是一个符号集UTF-8是在互联网上使用最广的一种Unicode的实现方式 http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html>>> S = '\u550d'#'唍' 这个unicode编码 是 '唍' >>> '唍'.encode("utf-8")#b'\xe5\x94\x8d' 当这个字 用utf-8编码实现的时候是 '\xe5\x94\x8d'

3.1.3. 关于 Unicode

Python 3.0 开始所有的字符串都支持 Unicode

>>> 'Hello\u0020World !''Hello World !'>>> "Äpfel".encode('utf-8')b'\xc3\x84pfel'

3.1.4. 列表list

  1. 就像字符串索引,列表从 0 开始检索。列表可以被切片和连接
    a[:2] + ['bacon', 2*2]
  2. 所有的切片操作都会返回新的列表,包含求得的元素。这意味着以下的切片操作返回列表 a 的一个浅拷贝的副本:
    >>> a[:]['spam', 'eggs', 100, 1234]
  3. 不像 不可变的 字符串,列表允许修改元素;也可以对切片赋值,此操作可以改变列表的尺寸,或清空它
    a[1:1] = ['bletch', 'xyzzy'] # Insert some:a[0:2] = []                  # Remove some:
  4. 使用append在列表末尾添加内容;len()返回长度

3.1.5. 共享引用

>>> a = '1',b = a, a = 2#b没有改变 字符串和整数都是不可变的,修改的时候改变了引用的对象(a='1',b='1',a is b:true)  sys.getrefcount(1) 1,‘1’会被复用>>> a = [],b= a , a.append(1)#b也改变了,对象还是同一个(可变对象) >>> a= [1],b= a[:], a.append(2)#b不变,两个指向的是不同的对象 >>> c=[a],d=c[:],c[0].append(2)#d也变了 这个copy不是deepcopy 里面都是a>>> c=[a],d=copy.deepcopy(c),c[0].append(2)#d不变了 是deepcopy 会嵌套copy

3.1.6. 赋值、表达式

Alt text

  • 赋值语句建立对象引用值,python变量更像指针而不是数据存储区域
  • 变量名在首次赋值的时候会被创建(一些数据结构也会,比如字典的元素,对象属性)
  • 变量名没有类型,但对象有

3.2. 编程的第一步

第一个例子

>>> a, b = 0, 1 #多重赋值>>> while b < 10:...     print(b)...     a, b = b, a+b #变量赋值前,右边首先完成计算。右边的表达式从左到右计算

4. 深入 Python 流程控制

Alt text

Alt text

4.1. if 语句

if x < 0:

4.2. for 语句

for x in a:

4.3. range() 函数;

for i in range(5):;range(0, 10, 3)for i in range(len(a)):#range 是一个可迭代对象;为了节省空间,它并不真正构造列表>>> list(range(5))[0, 1, 2, 3, 4]

4.4. break 和 continue 语句, 以及循环中的 else 子句

循环可以有一个 else 子句;它在循环迭代完整个列表(对于 for )或执行条件为 false (对于 while )时执行,但循环被 break 中止的情况下不会执行。

>>> for n in range(2, 10):...     for x in range(2, n):...         if n % x == 0:...             print(n, 'equals', x, '*', n//x)...             break...     else:...         # loop fell through without finding a factor...         print(n, 'is a prime number')

4.5. pass 语句

pass 语句什么也不做。它用于那些语法上必须要有什么语句,但程序什么也不做的场合

4.6. 定义函数

Alt text

>>> def fib(n):    # write Fibonacci series up to n...     """Print a Fibonacci series up to n.""" #docstring...     a, b = 0, 1...     while a < n:...         print(a, end=' ')...         a, b = b, a+b...     #return None 默认return None...

4.7. 深入 Python 函数定义

函数的作用:增加代码重用,减少代码冗余;分解流程.函数也是对象.

Alt text

Alt text

  • python引用变量的顺序: 当前作用域局部变量->外层作用域变量->当前模块中的全局变量->python内置变量LEGB

Alt text

  • global关键字用来在函数或其他局部作用域中使用全局变量。但是如果不修改全局变量也可以不使用global关键字。
gcount = 0 #global域 模块顶层文件 gcount和函数global_counter都是global的def global_counter():    global gcount #如果不是要修改 global可以不写 直接引用    gcount +=1    return gcount
  • nonlocal关键字用来在函数或其他作用域中使用外层(非全局)变量。
def make_counter():    count = 0    def counter():        nonlocal count        count += 1        return count    return counter
  • yield 是一个类似 return 的关键字,只是这个函数返回的是个生成器。 第一次迭代中你的函数会执行,从开始到达 yield 关键字,然后返回 yield 后的值作为第一次迭代的返回值. 然后,每次执行这个函数都会继续执行你在函数内部定义的那个循环的下一次,再返回那个值,直到没有可以返回的。
>>> def createGenerator() :...    mylist = range(3)...    for i in mylist :...        yield i*i...>>> mygenerator = createGenerator() # create a generator>>> print(mygenerator) # mygenerator is an object!<generator object createGenerator at 0xb7555c34>>>> for i in mygenerator:...     print(i)
  • def是一个可执行语句,和变量可以出现在任何地方.函数同时允许附加任意的属性记录信息
if a:    def fun():else:    def fun()fun()fun.attr = value
  • 不可变参数通过值传递,可变参数指针传递.
  • python不支持函数重载,定义了几个同名函数,运行的时候是最后一个(赋值)

4.7.1. 默认参数值

def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):

重要警告: 默认值只被赋值一次。这使得当默认值是可变对象时会有所不同,比如列表、字典或者大多数类的实例。

def f(a, L=[]):    L.append(a)    return Lprint(f(1))#[1]print(f(2))#[1, 2]print(f(3))#[1, 2, 3]#如果你不想让默认值在后续调用中累积,你可以像下面一样定义函数:def f(a, L=None):    if L is None:        L = []    L.append(a)    return L

4.7.2. 关键字参数

引入一个形如 **name 的参数时,它接收一个字典,该字典包含了所有未出现在形式参数列表中的关键字参数。这里可能还会组合使用一个形如 *name的形式参数,它接收一个元组,包含了所有没有出现在形式参数列表中的参数值。(*name 必须在 **name 之前出现) 例如,我们这样定义一个函数:

def cheeseshop(kind, *arguments, **keywords):    print("-- Do you have any", kind, "?")    print("-- I'm sorry, we're all out of", kind)    for arg in arguments:        print(arg)    keys = sorted(keywords.keys())    for kw in keys:        print(kw, ":", keywords[kw])#调用      cheeseshop("Limburger", "It's very runny, sir.","It's really very, VERY runny, sir.",           shopkeeper="Michael Palin",client="John Cleese",sketch="Cheese Shop Sketch")

4.7.3. 可变参数列表

Alt text

Alt text

Alt text

def f(a,*pargs,**kargs):print(a,pargs,kargs) #提取参数到a-》元组pargs-》字典kargs>>>f(1,2,3,x = 4, y = 5)1,(2,3),{'x':4,'y':5}

4.7.4. 参数列表的分拆

另有一种相反的情况: 当你要传递的参数已经是一个列表,但要调用的函数却接受分开一个个的参数值。这时候你要把已有的列表拆开来. 你可以在调用函数时加一个 * 操作符来自动把参数列表拆开,以同样的方式,可以使用 ** 操作符分拆关键字参数为字典:

>>> args = [3, 6]>>> list(range(*args))>>> def parrot(voltage, state='a stiff', action='voom'):...     print("-- This parrot wouldn't", action, end=' ')...     print("if you put", voltage, "volts through it.", end=' ')...     print("E's", state, "!")...>>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}>>> parrot(**d)

4.7.5. Lambda 形式

  • lambda是表达式,而不是语句
  • 通过 lambda 关键字,可以创建短小的匿名函数,语义上讲,它们只是普通函数定义中的一个语法技巧。类似于嵌套函数定义,lambda 形式可以从外部作用域引用变量:
>>> def make_incrementor(n):...     return lambda x: x + n...>>> f = make_incrementor(42)>>> f(0)42//默认参数也能在lambda中使用>>>f = (lambda a =1,b=2,c=3:a+b+c)>>>f(2) #7
  • 为什么要用lambda:实现函数速写
L = [lambda x:x**2,lambda x:x**3,lambda x:x**4]for f in L:print(f(2))
  • map 10-->10,filter 10-->5,reduce 10-->1

4.7.6. 文档字符串

参考标准库写法

4.7.7 函数其他1

  • 递归
//一个小例子def sum(L):    return L[0] if len(L) == 1 else return L[0] + sum(L[1:])
  • 函数也是对象,所以可以使用内省工具探索实现细节;也可以定义属性.
>>>def fun():a=1>>>dir(fun.__code__)>>>print(fun.__name__)>>>fun.__code__.co_varnames #('a',)
  • 3.0 注解 def fun(a:float)

    4.7.7 函数其他2:迭代与解析

4.8. 插曲:编码风格

  • 使用 4 空格缩进,而非 TAB。折行以确保其不会超过 79 个字符。可能的话,注释独占一行。
  • 使用文档字符串
  • 把空格放到操作符两边,以及逗号后面,但是括号里侧不加空格: a = f(1, 2) + g(3, 4) 。
  • 统一函数和类命名。推荐类名用 驼峰命名, 函数和方法名用 小写_和_下划线。总是用 self 作为方法的第一个参数(关于类和方法的知识详见 初识类 )。

5. 数据结构

Alt text

type()获得元素,变量类型 返回class(3.0);注意使用type并不是pythonic的思维,应该关注接口,而不是关注类型

//3种方法判断 类型if type(L) == type([])if type(L) == listif isinstance(L,list)

5.1. 关于列表更多的内容

  • list.append(x)把一个元素添加到链表的结尾,相当于 a[len(a):] = [x]
  • list.extend(L)将一个给定列表中的所有元素都添加到另一个列表中,相当于 a[len(a):] = L
  • list.insert(i, x)在指定位置插入一个元素。第一个参数是准备插入到其前面的那个元素的索引,例如 a.insert(0, x) 会插入到整个链表之前,而 a.insert(len(a), x) 相当于 a.append(x) 。
  • list.remove(x)删除链表中值为 x 的第一个元素。如果没有这样的元素,就会返回一个错误。
  • list.pop([i])从链表的指定位置删除元素,并将其返回。如果没有指定索引, a.pop() 返回最后一个元素。元素随即从链表中被删除。(方法中 i 两边的方括号表示这个参数是可选的)
  • list.index(x)返回链表中第一个值为 x 的元素的索引。如果没有匹配的元素就会返回一个错误。
  • list.count(x)返回 x 在链表中出现的次数。
  • list.sort()对链表中的元素就地进行排序。
  • list.reverse()就地倒排链表中的元素。
  • 按照索引删除,可以参考5.2
  • Alt text

    Alt text

5.1.1. 把链表当作堆栈使用

使用list的append()+ pop()

5.1.2. 把链表当作队列使用

可以使用list的insert(0,x) + pop()但是这样效率很低,正确的做法是使用collections.dequeappend()+popleft()

5.1.3. 列表推导式

列表推导式为从序列中创建列表提供了一个简单的方法squares = [x**2 for x in range(10)]相当于squares = map(lambda x: x**2, range(10))

>>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y][(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]# flatten a list using a listcomp with two 'for'>>> vec = [[1,2,3], [4,5,6], [7,8,9]]>>> [num for elem in vec for num in elem][1, 2, 3, 4, 5, 6, 7, 8, 9]

5.1.4. 嵌套的列表推导式

>>> matrix = [...     [1, 2, 3, 4],...     [5, 6, 7, 8],...     [9, 10, 11, 12],... ]>>> [[row[i] for row in matrix] for i in range(4)][[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]>>> list(zip(*matrix))#参数列表的分拆[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]

5.2. del 语句

有个方法可以从列表中按给定的索引而不是值来删除一个子项: del 语句。语句 del 还可以从列表中删除切片或清空整个列表。del a[0] = a[0:1] = []

>>> a = [-1, 1, 66.25, 333, 333, 1234.5]>>> del a[0]>>> del a[2:4]>>> del a[:]>>> a[]>>> del a #再次引用a将发生错误

5.3. 元组和序列

Alt text

Alt text

一个元组由数个逗号分隔的值组成(另参考namedtuple)

>>> t = 12345, 54321, 'hello!' # 一个元素的元组需要后面加, >>> singleton = 'hello',>>> t[0]12345>>> # Tuples may be nested:... u = t, (1, 2, 3, 4, 5)>>> # Tuples are immutable:... t[0] = 88888TypeError: 'tuple' object does not support item assignment>>> # but they can contain mutable objects:... v = ([1, 2, 3], [3, 2, 1])

python 3 后元组还加了 t.index('a');t.count('a')
元组除了使用方便之外,不可变性的约束也是重要的使用原因

5.4. 集合

Python 还包含了一个数据类型—— set (集合) 。集合是一个无序不重复元素的集。基本功能包括关系测试和消除重复元素。集合对象还支持 union(联合),intersection(交),difference(差)和 sysmmetric difference(对称差集)等数学运算。
大括号或 set() 函数可以用来创建集合。 注意:想要创建空集合,你必须使用 set() 而不是 {} 。后者用于创建空字典。

>>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}>>> 'orange' in basket                 # fast membership testingTrue>>> # Demonstrate set operations on unique letters from two words...>>> a = set('abracadabra')>>> b = set('alacazam')>>> a                           # unique letters in a>>> a - b                       # letters in a but not in b  《=》{x for x in a if x not in b}>>> a | b                       # letters in either a or b>>> a & b                       # letters in both a and b>>> a ^ b                       # letters in a or b but not both#集合也可以使用解析构造>>> a = list(range(97,107))>>> b = [chr(x) for x in a]#['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']>>> c = {chr(x) for x in a}#{'a', 'h', 'e', 'f', 'g', 'j', 'b', 'i', 'd', 'c'}>>> d = {chr(x):x for x in a}#{'i':105,'h':104,'a':97,'g':103,'d':100,'e':101,'f':102,'j':106, 'c':99,'b':98}

5.5. 字典

无序的键: 值对 (key:value pairs)集合
因为字典的key可以是任何不可变的对象,比如数字,字典可以模拟list而且比list更灵活(如不用担心越界,稀疏矩阵)

>>> tel = {'jack': 4098, 'sape': 4139}                     # 创建方法1>>> dict(sape=4139, guido=4127, jack=4098)                 # 创建方法2{'sape': 4139, 'jack': 4098, 'guido': 4127}>>> tel['guido'] = 4127        # set value for key>>> del tel['sape']            # del a pair of key-value>>> list(tel.keys())           # ['irv', 'guido', 'jack']>>> 'guido' in tel             # <=> 'guido' in tel.keys()True#dict() 构造函数可以直接从 key-value 对中创建字典:>>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])# 创建方法3#字典推导式可以从任意的键值表达式中创建字典:>>> {x: x**2 for x in (2, 4, 6)}                           # 创建方法4{2: 4, 4: 16, 6: 36}

字典key没有直接访问会出错,使用判断或者使用get语句。

#D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None.value = D.get("key",0)value = D["key"] if key in D else 0

Alt text

Alt text

5.6. 循环技巧

5.6.1在字典中循环时,关键字和对应的值可以使用 iteritems() 方法同时解读出来。

>>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}>>> for k, v in knights.items():...     print(k, v)...

5.6.2在序列中循环时,索引位置和对应值可以使用 enumerate()函数同时得到。

>>> for i, v in enumerate(['tic', 'tac', 'toe']):...     print(i, v)...

5.6.3同时循环两个或更多的序列,可以使用 zip()整体打包。

>>> questions = ['name', 'quest', 'favorite color']>>> answers = ['lancelot', 'the holy grail', 'blue']>>> for q, a in zip(questions, answers):...     print('What is your {0}?  It is {1}.'.format(q, a))...

5.6.4需要逆向循环序列的话,先正向定位序列,然后调用 reversed() 函数。

>>> for i in reversed(range(1, 10, 2)):...     print(i)...

5.6.5排序后的顺序循环序列的话,使用 sorted()函数,它不改动原序列,而是生成一个新的已排序的序列。(不同于list.sort(),后者是原地排序)

>>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']>>> for f in sorted(set(basket)):...     print(f)...

5.7. 深入条件控制

  • while 和 if 语句中使用的条件不仅可以使用比较,而且可以包含任意的操作。
  • 比较操作符 in 和 not in 审核值是否在一个区间之内。操作符 is 和 is not 比较两个对象是否相同;这只和诸如链表这样的可变对象有关。所有的比较操作符具有相同的优先级,低于所有的数值操作。
  • 比较操作可以传递。例如 a < b == c 审核是否 a 小于 b 并且 b 等于 c 。
  • 比较操作可以通过逻辑操作符 and 和 or 组合,比较的结果可以用 not 来取反义。not 具有最高的优先级, or 优先级最低, A and not B or C 等于 (A and (notB)) or C ,这些操作符的优先级低于比较操作符。
  • 逻辑操作符 and 和 or 也称作 短路操作符 :它们的参数从左向右解析,一旦结果可以确定就停止。

5.8. 比较序列和其它类型

序列对象可以与相同类型的其它对象比较。比较操作按 字典序 进行:首先比较前两个元素,如果不同,就决定了比较的结果;如果相同,就比较后两个元素,依此类推,直到所有序列都完成比较。如果两个元素本身就是同样类 型的序列,就递归字典序比较。如果两个序列的所有子项都相等,就认为序列相等。如果一个序列是另一个序列的初始子序列,较短的一个序列就小于另一个。字符 串的字典序按照单字符的 ASCII 顺序。

5.9. 迭代器和解析

  • 迭代器在python中是以C语言的运行速度运行的
  • 手动迭代
X是一个可以迭代的对象比如X = iter([1,2,3]) next(X)<=> X.\_\_next\_\_() (文件的iter就是自己)for x in [1,2,3]:    print(x)<=>I = iter([1,2,3])while True:    try:x = next(I)    except StopIteration:break    print(x)
  • 列表解析比手动循环更快
  • zip,map,filter不支持同一结果上多个活跃的迭代器
  • yield-->生成器函数
(1):next 执行到一个yield。然后程序返回。并且挂起。此时所有的状态被保留。如全局变量(=send(None))(2):send(msg) 从上次挂起的yield那边开始执行。并且把msg赋值给yield的表达式。例如上一次是在 m = yield 5断掉。那么send(10),m将被赋值成10.然后继续执行,直到执行到下一个yield。然后依然挂起。所以不能在第一次调用的时候,就send,因为这个时候,根本就还没有执行到任何的yield。(3)throw(type[, value[, traceback]]),可以抛出指定异常终止程序(4):close(). 其实就是执行throws(GeneratorExit)>>> def gen():    for i in range(10):        x = yield i        print("x:",x)>>> g = gen()>>> next(g)0>>> next(g)x: None1>>> g.send(100)x: 1002
  • 生成器表达式,和列表解析基本一样,只是是在()而不是[]中,注意没有元组解析
>>> (x**2 for x in range(10))//生成器<generator object <genexpr> at 0x10414c090>>>> tuple((x**2 for x in range(10)))//没有元组解析(0, 1, 4, 9, 16, 25, 36, 49, 64, 81)>>> [x**2 for x in range(10)]//列表解析[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]>>> {x**2 for x in range(10)}//集合解析{0, 1, 64, 4, 36, 9, 16, 49, 81, 25}>>> {x:x**2 for x in range(10)}//字典解析{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}

6. 模块

模块是包括 Python 定义和声明的文件。文件名就是模块名加上 .py 后缀。模块的模块名(做为一个字符串)可以由全局变量 __name__ 得到。

>>> import fibo>>> fibo.__name__'fibo'>>> fib = fibo.fib #如果打算频繁使用一个函数,你可以将它赋予一个本地变量>>> fib(500

6.1. 深入模块

每个模块都有自己私有的符号表,被模块内所有的函数定义作为全局符号表使用。 因此,可以在模块内部使用全局变量,而无需担心它与某个用户的全局变量意外冲突。可以使用引用模块函数的表示法访问模块的全局变量, modname.itemname 。

模块可以导入其他的模块。 一个(好的)习惯是将所有的 import 语句放在模块的开始,但非强制。 被导入的模块名会放入当前模块的全局符号表中。

模块的其他导入方法:

#import 语句的一个变体直接从被导入的模块中导入命名到本模块的语义表中。例如:>>> from fibo import fib, fib2#导入模块中的所有定义,这样可以导入所有除了以下划线( _ )开头的命名。>>> from fibo import *#需要注意的是在实践中往往不鼓励从一个模块或包中使用 * 导入所有,因为这样会让代码变得很难读。不过,在交互式会话中这样用很方便省力。# Note 出于性能考虑,每个模块在每个解释器会话中只导入一遍。因此,如果你修改了你的模块,需要重启解释器——或者,如果你就是想交互式的测试这么一个模块,可以用 reload() 重新加载,例如 reload(modulename) 。

6.1.1. 作为脚本来执行模块

当你使用以下方式运行 Python 模块时,模块中的代码便会被执行:

python fibo.py <arguments>

模块中的代码会被执行,就像导入它一样,不过此时 name 被设置为 "main" 。这相当于,如果你在模块后加入如下代码:

if __name__ == "__main__":    import sys    fib(int(sys.argv[1]))

这通常用来为模块提供一个便于测试的用户接口(将模块作为脚本执行测试需求)。

6.1.2. 模块的搜索路径

导入一个叫 spam 的模块时,解释器先在当前目录中搜索名为 spam.py 的文件。如果没有找到的话,接着会到 sys.path 变量中给出的目录列表中查找。 sys.path 变量的初始值来自如下:

  • 输入脚本的目录(当前目录)。
  • 环境变量 PYTHONPATH 表示的目录列表中搜索 (这和 shell 变量 PATH 具有一样的语法,即一系列目录名的列表)。
  • Python 默认安装路径中搜索。

6.1.3. “编译的” Python 文件

对于引用了大量标准模块的短程序,有一个提高启动速度的重要方法,如果在 spam.py 所在的目录下存在一个名为 spam.pyc 的文件,它会被视为 spam 模块的预“编译”( byte-compiled ,二进制编译)版本。用于创建 spam.pyc 的这一版 spam.py 的修改时间记录在 spam.pyc 文件中,如果两者不匹配,.pyc 文件就被忽略。

部分高级技巧:

  • 以 -O 参数调用 Python 解释器时,会生成优化代码并保存在 .pyo 文件中。现在的优化器没有太多帮助;它只是删除了断言( assert )语句。使用 -O 参数, 所有 的字节码( bytecode )都会被优化; .pyc 文件被忽略, .py 文件被编译为优化代码。

  • 向 Python 解释器传递两个 -O 参数( -OO )会执行完全优化的二进制优化编译,这偶尔会生成错误的程序。现在的优化器,只是从字节码中删除了 doc 符串,生成更为紧凑的 .pyo 文件。因为某些程序依赖于这些变量的可用性,你应该只在确定无误的场合使用这一选项。

  • 来自 .pyc 文件或 .pyo 文件中的程序不会比来自 .py 文件的运行更快; .pyc 或 .pyo 文件只是在它们加载的时候更快一些。

  • 通过脚本名在命令行运行脚本时,不会将为该脚本创建的二进制代码写入 .pyc 或 .pyo 文件。当然,把脚本的主要代码移进一个模块里,然后用一个小的启动脚本导入这个模块,就可以提高脚本的启动速度。也可以直接在命令行中指定一个 .pyc 或 .pyo 文件。

  • 对于同一个模块(这里指例程 spam.py),可以只有 spam.pyc 文件(或者 spam.pyc ,在使用 -O 参数时)而没有 spam.py 文件。这样可以打包发布比较难于逆向工程的 Python 代码库。

  • compileall 模块可以为指定目录中的所有模块创建 .pyc 文件(或者使用 -O 参数创建 .pyo 文件)。

6.2. 标准模块

Python 带有一个标准模块库,并发布有独立的文档,名为 Python 库参考手册(此后称其为“库参考手册”)。有一些模块内置于解释器之中,这些操作的访问接口不是语言内核的一部分,但是已经内置于解释器了。这既是为了提高效率,也是为了给系统调用等操作系统原生访问提供接口。

6.3. dir() 函数

内置函数 dir() 用于按模块名搜索模块定义,它返回一个字符串类型的存储列表;无参数调用时, dir() 函数返回当前定义的命名;会列出所有类型的名称:变量,模块,函数,等等。dir() 不会列出内置函数和变量名。如果你想列出这些内容,它们在标准模块__builtin__ 中定义。

6.4. 包

包通常是使用用“圆点模块名”的结构化模块命名空间。正如同用模块来保存不同的模块架构可以避免全局变量之间的相互冲突,使用圆点模块名保存像 NumPy 或 Python Imaging Library 之类的不同类库架构可以避免模块之间的命名冲突。
需要注意的是使用 from package import item 方式导入包时,这个子项(item)既可以是包中的一个子模块(或一个子包),也可以是包中定义的其它命名,像函数、类或变量。import 语句首先核对是否包中有这个子项,如果没有,它假定这是一个模块,并尝试加载它。如果没有找到它,会引发一个 ImportError 异常。相反,使用类似 import item.subitem.subsubitem 这样的语法时,这些子项必须是包,最后的子项可以是包或模块,但不能是前面子项中定义的类、函数或变量。

6.4.1. 从 包 导入 *

执行 from package import 时,如果包中的 __init__.py 代码定义了一个名为 __all__ 的列表,就会按照列表中给出的模块名进行导入。如果没有定义 __all__, from Sound.Effects import 语句 不会 从 sound.effects 包中导入所有的子模块。

__all__ = ["echo", "surround", "reverse"]#这意味着 from Sound.Effects import * 语句会从 sound 包中导入以上三个已命名的子模块。

6.4.2. 包内引用

6.4.3. 多重目录中的包

6.5 文档

Alt text

7. 输入和输出

7.1. 格式化输出

两种输出到终端的方式:直接使用表达式语句 和 print() 函数。(第三种访求是使用文件对象的 write() 方法)

有两种方法可以格式化你的输出: 第一种方法是由你自己处理整个字符串,通过使用字符串切割和连接操作。 第二种方法是使用 str.format()方法。

>>> s = 'The value of x is ' + repr(x) + ', and y is ' + repr(y) + '...'The value of x is 32.5, and y is 40000...
# 有两种方式可以写平方和立方表:>>> for x in range(1, 11):...     print(repr(x).rjust(2), repr(x*x).rjust(3), end=' ')...     # Note use of 'end' on previous line...     print(repr(x*x*x).rjust(4))>>> for x in range(1, 11):...     print('{0:2d} {1:3d} {2:4d}'.format(x, x*x, x*x*x))...#(注意 print 在每列之间加了一个空格,它总是在参数间加入空格。)
  • 以上是一个 str.rjust() 方法的演示,它把字符串输出到一列,并通过向左侧填充空格来使其右对齐。类似的方法还有 str.ljust() 和 str.center() 。这些函数只是输出新的字符串,并不改变什么。如果输出的字符串太长,它们也不会截断它,而是原样输出。(如果你确实需要截断它,可以使用切割操作,例如: x.ljust(n)[:n] 。)还有另一个方法, str.zfill() 它用于向数值的字符串表达左侧填充 0。该函数可以正确理解正负号
  • 方法 str.format() 的基本用法如下:
>>> print('We are the {} who say "{}!"'.format('knights', 'Ni'))>>> print('{0} and {1}'.format('spam', 'eggs'))#spam and eggs>>> print('{1} and {0}'.format('spam', 'eggs'))#eggs and spam>>> print('This {food} is {adjective}.'.format(...       food='spam', adjective='absolutely horrible'))>>> print('The story of {0}, {1}, and {other}.'.format('Bill', 'Manfred',                                                       other='Georg'))# '!a' (应用 ascii()), '!s' (应用 str() ) 和 '!r' (应用 repr() ) 可以在格式化之前转换值:>>> print('The value of PI is approximately {}.'.format(math.pi))>>> print('The value of PI is approximately {!r}.'.format(math.pi))#字段名后允许可选的 ':' 和格式指令。这允许对值的格式化加以更深入的控制。下例将 Pi 转为三位精度。在字段后的 ':' 后面加一个整数会限定该字段的最小宽度,这在美化表格时很有用。>>> print('The value of PI is approximately {0:.3f}.'.format(math.pi))#3.142.>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 7678}>>> for name, phone in table.items():...     print('{0:10} ==> {1:10d}'.format(name, phone))#如果你有个实在是很长的格式化字符串,不想分割它。如果你可以用命名来引用被格式化的变量而不是位置就好了。有个简单的方法,可以传入一个字典,用中括号访问它的键:>>> table = {'Sjoerd': 4127, 'Jack': 4098, 'Dcab': 8637678}>>> print('Jack: {0[Jack]:d}; Sjoerd: {0[Sjoerd]:d}; '          'Dcab: {0[Dcab]:d}'.format(table))#也可以用 ‘**’ 标志将这个字典以关键字参数的方式传入。>>> print('Jack: {Jack:d}; Sjoerd: {Sjoerd:d}; Dcab: {Dcab:d}'.format(**table))#这种方式与新的内置函数 vars() 组合使用非常有效。该函数返回包含所有局部变量的字典。

如何将值转化为字符串:函数 str() 用于将值转化为适于人阅读的形式,而 repr() 转化为供解释器读取的形式(如果没有等价的语法,则会发生 SyntaxError 异常) 某对象没有适于人阅读的解释形式的话, str() 会返回与 repr() 等同的值。

>>> # The repr() of a string adds string quotes and backslashes:... hello = 'hello, world\n'>>> hellos = repr(hello)>>> print(hellos)'hello, world\n'

7.1.1. 旧式的字符串格式化

print('The value of PI is approximately %5.3f.' % math.pi)

7.2. 文件读写

Alt text

>>> f = open('/tmp/workfile', 'w')#Windows 平台上文本文件与二进制文件是有区别的 b

7.2.1. 文件对象方法

  • 要读取文件内容,需要调用 f.read(size), size 是可选的数值,指定字符串长度。如果没有指定 size 或者指定为负数,就会读取并返回整个文件。如果到了文件末尾,f.read() 会返回一个空字符串(”“)。
  • f.readline() 从文件中读取单独一行,字符串结尾会自动加上一个换行符( \n ),只有当文件最后一行没有以换行符结尾时,这一操作才会被忽略。这样返回值就不会有混淆,如果如果 f.readline() 返回一个空字符串,那就表示到达了文件末尾,如果是一个空行,就会描述为 '\n' ,一个只包含换行符的字符串。
  • f.readlines() 返回一个列表,其中包含了文件中所有的数据行。如果给定了 sizehint 参数,就会读入多于一行的比特数,从中返回多行文本。
  • 一种替代的方法是通过遍历文件对象来读取文件行。 这是一种内存高效、快速,并且代码简介的方式:虽然这种替代方法更简单,但并不具备细节控制能力。 因为这两种方法处理行缓存的方式不同,千万不能搞混。
  • 文件迭代器是按照行读取文件的最好工具(同上)
    >>> for line in f:...     print(line, end='')
  • f.write(string) 方法将 string 的内容写入文件,并返回写入字符的长度。
  • f.tell() 返回一个整数,代表文件对象在文件中的指针位置,该数值计量了自文件开头到指针处的比特数。需要改变文件对象指针话话,使用 f.seek(offset,from_what) 。
  • 当你使用完一个文件时,调用 f.close()方法就可以关闭它并释放其占用的所有系统资源。 在调用 f.close() 方法后,试图再次使用文件对象将会自动失败。
  • 用关键字 with 处理文件对象是个好习惯。它的先进之处在于文件用完后会自动关闭,就算发生异常也没关系。它是 try-finally 块的简写:
    >>> with open('/tmp/workfile', 'r') as f:...     read_data = f.read()
  • 文件对象还有一些不太常用的附加方法,比如 isatty() 和 truncate() 在库参考手册中有文件对象的完整指南。

7.2.2. pickle 模块

pickle.dump(x, f)x = pickle.load(f)

8. 错误和异常

Python 中(至少)有两种错误:语法错误和异常(syntax errorsexceptions

8.1. 语法错误

语法错误,也被称作解析错误

8.2. 异常

运行期检测到的错误称为 异常;可以被处理。

8.3. 异常处理

for arg in sys.argv[1:]:    try:        f = open('myfile.txt')        s = f.readline()        i = int(s.strip())    except IOError as err:        print("I/O error: {0}".format(err))    except ValueError:                    #注:用户产生的中断会引发一个KeyboardInterrupt 异常。        print("Could not convert data to an integer.")    else:                                 #当 try 语句没有抛出异常时,执行else        print(arg, 'has', len(f.readlines()), 'lines')        f.close()    finally:                              #不管有没有发生异常,都会执行finally...     print("executing finally clause")

8.4. 抛出异常

>>> try:...     raise NameError('HiThere') #raise一个异常实例或异常类(继承自 Exception 的类)... except NameError:...     print('An exception flew by!')...     raise                     #raise 语句可以让你很简单的重新抛出该异常

8.5. 用户自定义异常

9. 类

用 C++ 术语来讲,所有的类成员(包括数据成员)都是公有public(除了Private Variables))的,所有的成员函数都是虚(virtual)的。有伪私有、但只是约定,理念的区别

9.2. Python 作用域和命名空间

9.2.1. 作用域和命名空间示例

def scope_test():    def do_local():        spam = "local spam"    def do_nonlocal():        nonlocal spam        spam = "nonlocal spam"    def do_global():        global spam        spam = "global spam"    spam = "test spam"    do_local()    print("After local assignment:", spam)    do_nonlocal()    print("After nonlocal assignment:", spam)    do_global()    print("After global assignment:", spam)scope_test()print("In global scope:", spam)#以上示例代码的输出为:After local assignment: test spamAfter nonlocal assignment: nonlocal spamAfter global assignment: nonlocal spamIn global scope: global spam

9.3. 初识类

  • 属性可以是是只读或者可写的;可写的属性可以用del 删除
  • 类是模块内的属性
  • class语句中的赋值语句会自动创建类的属性(就像字典的key)
  • 即使是方法也可以在类外自动创建
//在任何类的外面>>>def upperName(self):    return self.name.upper()>>>upperName(instanceName)>>>className.upperName = upperName>>>instanceName.upperName()
  • 实例会集成所有可读取的类属性
    Alt text
>>> class classx:pass>>> classx.x = 1>>> instancex = classx();instancex2 = classx()>>> instancex.x //只是从类上取的属性1>>> instancex.x = 2//实例也有x属性了 但是和类的x不一样>>> instancex.x2>>> classx.x1>>> classx.__dict__.keys()dict_keys(['__dict__', '__module__', '__doc__', 'x', '__weakref__']>>> instancex.__dict__.keys()dict_keys(['x'])>>> instancex2.__dict__.keys()dict_keys([])
  • 重载运算符,使用__X__这样的特殊钩子,如定义了__add__方法就会支持+;
  • 命名空间是类集成的基础,class只是创建了命名空间的复合语句;所以什么语句都能放进来,比如:
class test:    x = 1    if x:        y = 1    else:        y = 2
  • python中的多态基于对象接口,而不是类型.
  • 伪私有属性,用__x表示,这种功能主要是为了避免实例内命名空间冲突(变量名自动扩展成_classname__x),实际不是私有的
  • 方法也是对象,获取的无绑定方法即函数(3.0)调用的时候要指明对象 无绑定->m = className.methodName;m(O,x) 绑定m = instanceName.methodName;m(x)
  • dict列出实例属性;dir列出包括继承的属性

9.3.1. 类定义语法 class A(B,C):

9.3.2. 类对象

和C++等不同的是,python的class不是声明式的,就像def一样class语句执行的时候产生类对象,和def一样是可执行的代码,知道python运行定义class的语句钱,类都不存在.

  • 一般形式
X = 11                      #globalclass <name>(superclass,..):    data = value            #类的数据    def method(self,..)     #类的方法        x = 33              #类方法的本地数据        self.member = value #实例的数据    @classmethod    def classmd(cls):       #类方法 可以访问类的数据,不能访问实例的数据    @staticmethod           #可以省略    def staticmd():         #静态方法 不能访问类和实例的数据

9.4. 类编写细节

Alt text

  • 这个图里的delegate语句期待子类填空,类似抽象父类,可以在这个父类语句中加assert ,raise,或@abstractmethod
class Super:    @abstractmethod #3.0之后用    def action(self):        #assert False,'action must be implemented'#或者:        #raise NotImplementedError('action must be implemented')

9.4.1. 类-运算符重载 略

9.4.2. OOP和委托:"包装"对象

  • 相当于oc的invocation,包装类,实际工作委托给另一个类处理
class warpper:    def __init__(self, object):        self.warpped = object;    def __getattr__(self, attr):        print(attr)        return getattr(self.warpped, attr)
  • 通用的对象工厂
def factory(aClass, *args, **kwargs):    return aClass(*args, **kwargs)object = factory(list, (1 , 2))

9.5. 类高级主题

9.5.1 所有的类都继承自object

9.5.2 类和类型没有区别,类自身就是类型

9.5.3 钻石继承:略

9.5.4 slots,限制类的实例的合法属性集,节约内存提高效率,只有__slot__列表中的变量名可以赋值为属性;使用slots默认没有dict;内部外部生成属性都要在slot集合里面,除非slot=['a','dict']

>>> class limit:    __slots__ = ['a','b']>>> a = limit()>>> a.a=1>>> a.c=1Traceback (most recent call last):  File "<pyshell#100>", line 1, in <module>    a.c=1AttributeError: 'limit' object has no attribute 'c'>>> a.__dict__Traceback (most recent call last):  File "<pyshell#103>", line 1, in <module>    a.__dict__AttributeError: 'limit' object has no attribute '__dict__'>>> class limit2:    __slots__=['a','__dict__']>>> x = limit2()>>> x.a =1>>> x.c=1>>> x.__dict__{'c': 1}>>>class limit3(limit): #因为__slots__是伪私有?,受类限制所以集成之后没有__slots__了        pass            #默认有__dict__,如果需要,再主动声明__slots__=

9.5.5 类特性:property(和slot一样都是属性描述器);也可以用setattr getattr实现 比较略

>>> class A:    def getAge(self):        return 40    age = property(getAge,None,None,None)#依次是 getter,setter,del,doc>>> a = A()>>> a.age40>>> a.age = 4Traceback (most recent call last):  File "<pyshell#150>", line 1, in <module>    a.age = 4AttributeError: can't set attribute>>>class classic:#使用__setattr__ __getattr__实现    def __getattr__(self,name):        if name =='age':retun 40        else:raise AttribureError    def __setattr__(self,name,value):#没写判断        self.__dict__[name] = value

9.5.6 装饰器和元类

  • 函数装饰器提供了一种方式,替函数明确了特定的运算方式,也就是在函数外面又包裹了一层函数。如:
class C:    @staticmethod    def method():pass     //等价于    method = staticmethod(method)
  • 自定义装饰器
//函数装饰器class testTracer:    def __init__(self,fun):        self.calls = 0        self.fun = fun    def __call__(self, *args, **kwargs):        self.calls += 1        print('call %s to %s'%(self.calls, self.fun.__name__))        self.fun(*args,**kwargs)@testTracerdef fun(a,b): #fun变成了TestTracer实例,执行fun的时候会执行TestTracer实例的__call__函数    print(a,b) # 即fun = testTracer(fun) //类装饰器类似 def decorator(aClass):.. @decorator class C:.. //相当于 class C:.. C = decorator(C)

10.高级话题续

10.1 unicode和字节字符串

已读 未补充

10.2 管理属性

@ 10.2.1 需要灵活定制setter和gtter的方法

  • 1.getattr and setattr把未定义的属性获取和所有的属性赋值指向通用 的处理器方法
  • 2.property内置函数,把特定属性访问定位到g e t和s e t处理器函数,也叫做特性 (Property)
attribute = property(fget, fset, fdel, doc)// 或者使用装饰器来实现class Person:    def __init__(self, name):        self._name = name    @property    def name(self):        print('fetch..')        return self._name    @name.setter    def name(self, name):        print('change..')        self._name = name    @name.deleter    def name(self):        del self._name
  • 3.gerattribute方法把所有属性获取都指向Python 2.6的新式类和Python 3.0的 所有类中的一个泛型处理器方法;

    • getattr针对未定义的属性运行——也就是说,属性没有存储在实例上,或者没 有从其类之一继承;
    • gerattribute针对每个属性,因此,当使用它的时候,必须小心避免通过把属 性访问传递给超类而导致递归循环
    • getattrgerattribute方法也比特性和描述符更加通用——它们可以用来拦截 对任何(几乎所有的)实例属性的获取
      Alt text
  • 4.描述符协议,把特定属性访问定位到具有任意get和set处理器方法的类的实例(上面的特性只是描述符的一种);描述符作为单独的类编写,并且针对想要拦截的属性访问操作提供 特定命名的访问器方法——当以相应的方式访问分配给描述符类实例的属性时,描述符 类中的获取、设置和删除等方法自动运行

class Descriptor:    def __get__(self, instance, owner):pass# Return attr value    def __set__(self, instance, value):pass# Return nothing (None)    def __delete__(self, instance):pass # Return nothing (None)#定义class Subject:    aAttr = Descriptor()#访问X.attr <=>Descriptor.__get__(Subject.attr,X,Subject)#一个例子class Name: #这个定义可以嵌套进Person类    def __get__(self, instance, owner):        print('fetch..')        return instance._name    def __set__(self, instance, value):         print('change..')         instance._name = value    def __delete__(self, instance):        print('remove..')        del instance._nameclass Person1:    def __init__(self, name):        self._name = name    name = Name()#一个描述符类来模拟property内置函数 学习手册p985

10.3 装饰器

10.3.1 什么是装饰器

  • 装饰是为函数和类指定管理代码的一种方式。装饰器本身的形式是处理其他的可调用对 象的可调用的对象(如函数)
    • 函数装饰器在函数定义的时候进行名称重绑定,提供一个逻辑层来管理函数和方法 或随后对它们的调用。
    • 类装饰器在类定义的时候进行名称重绑定,提供一个逻辑层来管理类,或管理随后 调用它们所创建的实例。

10.3.2 为什么要使用装饰器

  • 一种显式的语法,它使得意图明确,可以最小化 扩展代码的冗余,并且有助于确保正确的API使用.

10.3.3 函数装饰器

// 1.表现@decoratordef F(arg):<=>F = decorator(F)//2.实现// 用函数实现def decorator(F):    def wrapper(*args):        ...        F(*args)        ...    return wrapper// 用类实现 这种方法实现的装饰器用于类方法的时候会出问题!!!self不能准确传递class decorator:    def __init__(self,F):        self.F = F    def __call__(self, *args, **kwargs):        ...        self.F()        ...

10.3.4 类装饰器

//用函数实现//一种实现 加工后返回原来的类def decorator(C):    #process C    return C//另一种实现,返回C的包装类def decorator(C):    class Warpper:        def __init__(self,*args):            self.wrapped = C        def __getattr__(self,name):            return getattr(self.warpped, name)    return Warpper// 用类实现 错误版!!多次使用会覆盖class Warpper:    def __init__(self,C):        self.C = C    def __call__(self, *args):        self.wrapped = self.C(*args)        return self;    def __getattr__(self,name):            return getattr(self.warpped, name)//修改:增加下面的函数即可def decorator(C):    def onCall(*args):        return Warpper(C(*args))    return onCall

10.3.5 装饰器嵌套和参数

  • 装饰器参数往往意味着可调用对象的3个层级:接受装饰器参数的一个可调 用对象,它返回一个可调用对象以作为装饰器,该装饰器返回一个可调用对象来处理对 最初的函数或类的调用。这3个层级的每一个都可能是一个函数或类
@A@B@Cdef f()<=>f = A(B(C(f)))@decorator(A,B)def f()<=>f = @decorator(A,B)(f)

10.3.6 装饰器的例子 略

  • 跟踪调用
  • 计时调用
  • singleton
  • 跟踪对象接口
  • 实现私有属性
  • 验证函数参数

10.4 元类

10.4.1 概念

  • 从某种意义上讲,元类只是扩展了装饰器的代码插入模式;和类装饰器不同,它通常是添加实例创建时运行的逻辑,元类在类创建时运行。同样 的,它们都是通常用来管理或扩展类的钩子,而不是管理其实例
  • Python遵从一个标准的协议来使这发生:在一条class语句的末尾,并且在 运行了一个命名控件词典中的所有嵌套代码之后,它调用type对象来创建class对象 class = type(className, superClass, attributeDict):
  • 类是type类的实例
    • type是产生用户定义的类的一个类。
    • 元类是type类的一个子类。
    • 类对象是type类的一个实例,或一个子类。
//由于创建新的类的时候,Python在语句的末尾自动调用元类,因此它可以根据需要 扩展、注册或管理类。此外,客户类唯一的需求是,它们声明元类def extra(self, *arg):passclass Extras(type):    def __init__(Class, className, superClass, attributeDict):        if required():            Class.extra = extra()class Client1(metaclass = Extras):passclass Client2(metaclass = Extras):passclass Client3(metaclass = Extras):passx = Client1()x.extra()//在这种角色中,装 饰器对应到元类的__init__方法,但是,元类还有其他的定制钩子。正如我们还将看到 的,除了类初始化,元类可以执行任意的构建任务,而这些可能对装饰器来说更难
  • 简单的例子
class Meta(type):    def __new__(meta, className, superClass, attributeDict):        print('in meta:',className,superClass,attributeDict,sep='\n....')        return type.__new__(meta, className, superClass, attributeDict)print('making class')class TestMeta(metaclass = Meta):    data = 1print('making instance')    testMeta = TestMeta()
  • 元类也可以接入init协议,由type对象的call调用:通常,new创建并返回了类对象init初始化了已经创建的类。元类也可以用做在创建时管理类的钩子:
  • 实际上任何可调用对象都可以用作一个元 类,只要它接收传递的参数并且返回与目标类兼容的一个对象

• 元类继承自type类。尽管它们有一种特殊的角色元类,但元类是用class语句编写 的,并且遵从Python中有用的OOP模型。
• 元类声明由子类继承。
• 元类属性没有由类实例继承。

//一个工厂函数def MetaFunc(className, superClass, attributeDict):    print('in MetaFunc:',className,superClass,attributeDict,sep='\n....')    return type(className, superClass, attributeDict)print('making class')class TestMetaFunc(metaclass = MetaFunc):    data = 1

10.4.2 例子

• 向类中添加方法;在new中添加

#用元类添加方法def sayHello(obj):    print('hello world')class MetaHello(type):    def __new__(meta, className, superClass, attributeDict):        attributeDict['sayHello'] = sayHello        return type.__new__(meta, className, superClass, attributeDict)class TestMetaHello(metaclass = MetaHello):    pass#用装饰器也可以实现 略
  • 类装饰器和元类
    • 类装饰器可以管理类和实例。
    • 元类可以管理类和实例,但是管理实例需要一些额外工作。如前面的tracer的例子 p1101

  • 用元类和装饰器跟踪:互补使用

附1:工具

  • 内省属性 像classdict这样的特殊属性允许我们查看Python对象的内部实现方面, 以便更广泛地处理它们,列出对象的所有属性、显示一个类名,等等。
  • 运算符重载方法 像stradd这样特殊命名的方法,在类中编写来拦截并提供应用于类实例 的内置操作的行为,例如,打印、表达式运算符等等。它们自动运行作为对内置操 作的响应,并且允许类符合期望的接口。
  • 属性拦截方法 一类特殊的运算符重载方法提供了一种方法在实例上广泛地拦截属性访问: getattrsetattrgetattribute允许包装的类插入自动运行的代 码,这些代码可以验证属性请求并且将它们委托给嵌入的对象。它们允许一个对象 的任意数目的属性——要么是选取的属性,要么是所有的属性——在访问的时候计算。
  • 类特性 内置函数property允许我们把代码和特殊的类属性关联起来,当获取、赋值或删除 该属性的时候就自动运行代码。尽管不像前面一段所介绍的工具那样通用,特性考 虑到了访问特定属性时候的自动代码调用。
  • 类属性描述符 其实,特性只是定义根据访问自动运行函数的属性描述符的一种简洁方式。描述符 允许我们在单独的类中编写getsetdel处理程序方法,当分配 给该类的一个实例的属性被访问的时候自动运行它们。它们提供了一种通用的方 式,来插入当访问一个特定的属性时自动运行的代码,并且在一个属性的常规查找 之后触发它们。
  • 函数和类装饰器 装饰器的特殊的@可调用语法,允许我们添加当调用一 个函数或创建一个类实例的时候自动运行的逻辑。这个包装器逻辑可以跟踪或计时 调用,验证参数,管理类的所有实例,用诸如属性获取验证的额外行为来扩展实 例,等等。装饰器语法插入名称重新绑定逻辑,在函数或类定义语句的末尾自动运 行该逻辑——装饰的函数和类名重新绑定到拦截了随后调用的可调用对象。
  • 元类 和类装饰器不同,它通常是添加实例创建时运行的逻辑,元类在类创建时运行。同样 的,它们都是通常用来管理或扩展类的钩子,而不是管理其实例

附2:结构图

Alt text

0 0
原创粉丝点击