python进阶

Python进阶

标签(空格分隔): python


说明: 本文只是按照廖雪峰的教程写的笔记, 没有任何原创 (/ω\)
目录

.0附录

0.1 python命名规范:

1.文件名: 全小写,可使用下划线
2.包: 简短的,小写的名字,可使用下划线,mypackage,my_pacakage
3.模块: 和包相同规范, mymodule,my_module
4.类: 每个单词首字母大写,MyClass
5.函数&方法: 函数名小写,可加下划线myfunction,my_function
6.全局变量: 避免使用全局变量,实在要使用在前面加 下划线
7.变量: 全小写,用下划线连接

注意:
7.1.无论是类成员变量还是全局变量,都不要使用m或g前缀
7.2.私有类成员使用下划线作为前缀,尽量少定义私有成员
7.3.变量名不应该带有类的信息,因为python是动态语言,随时类型都可以转换,所以类似names_list这样的都是不好的命名

8.常量: 全大写,用下划线连接,MAX_OVERFLOW,TOTAL
9.缩写:

9.1.尽量使用全拼单词
9.2.对于常见全大写的缩写,我们只大写其首字母,XML->XmlParser
9.3常见缩写:

function : fn
text : txt
object : obj
count : cnt
number : num
argument : arg
buffer : buff
clock : clk
configuration : cfg
compare : cmp
command : cmd
device : dev
error : err
hexadecimal : hex
increment : inc
initialize: init
maximum : max
message : msg
minimum : min
parameter : para
previous : prev
register : reg
semaphore : sem
statistic : stat
synchronize : sync
temp : tmp

1.高级特性

1.1切片

本质:切片的本质就是根据某些规则将一个列表切割成多个列表.

方法:

1.提取某个列表中的某一段:

list1[0:3],提取list1列表中的前3个元素
list1[4:7],提取list1中的list[4],list[5],list[6],list[7]
list1[:4],提取list1的前4个元素,如果第一个索引是0,可以省略
list1[-2:],提取list1的后两个元素

2.提取列表所有元素,这样可以复制一个列表的所有值,如果直接用列表等号另一个列表,只是复制了一个地址过去.

list1[:]

3.所有元素每5个元素提取一个,提取一个空4个这样提取.

list1[::5]

4.在前10个元素中,每3个元素提取一个,提取一个空2个这样提取.

list1[:10:3]

tuple元组也可以切片,切片结果依然是不可变的tuple.
1.2迭代

本质::简单来说,就是用for循环遍历某个list或tuple.

与c或者java的区别:: python的迭代更加高级,可以对任何有下标或者没有下标的数据类型进行遍历(例如dict字典).

方法:

1.对list迭代:

list1 = [‘I’,”love”,”SB”,”too”]
for i in list1:
结果:I love SB too

2.对dict迭代:

dict1 = {“1”:’I’,”2”:”love”,”3”:”SB”,”4”:”too”}
for i in dict1:
结果:1 2 3 4

说明对dict的迭代直接输出的是dict的key,如果需要输出key对应的值,可以这样做:
for key in dict1:
print(dict1[key])
输出:I love SB too

注意,由于dict的存储方式不一定是按照顺序存储的,所以迭代输出的dict可以会乱序.

3.判断一个数据是否可以迭代:

isinstance(list1,Iterable)
输出True说明可迭代,输出False说明不可迭代

4.在一个for循环中引用两个变量

for x,y in[(1,1),(2,4),(3,9)]:
输出:
1 1
2 4
3 9

1.3列表生成式

本质: 用一些规则生成有规律的列表

方法:

生成从2到10的一个列表:

list(range(2,11))
结果:[2, 3, 4, 5, 6, 7, 8, 9, 10] , 由于python是左存右不存的语法,所以需要range(2,11)

生成2*2,3*3…10*10的列表:

list(x*x for x in range(2,11))
或者[x*x for x in range(2,11)]
结果:[4, 9, 16, 25, 36, 49, 64, 81, 100]

生成2*2,4*4,6*6…20*20的列表:

list(x*x for x in range(2,21) if x%2 == 0)
或者[x*x for x in range(2,21) if x%2 == 0]
结果:[4, 16, 36, 64, 100, 144, 196, 256, 324, 400]

生成”ABC”和”XYZ”的全排列

list(x+y for x in “ABC” for y in “XYZ”)
结果:[‘AX’, ‘AY’, ‘AZ’, ‘BX’, ‘BY’, ‘BZ’, ‘CX’, ‘CY’, ‘CZ’]

列出当前目录下的所有文件和目录名:

import os
list(d for d in os.listdir(‘.’))
结果:[‘.idea’, ‘test1.py’, ‘venv’]

同时迭代dict的key和value

dict1 = {“1”:’I’,”2”:”love”,”3”:”SB”,”4”:”too”}
for key1,value1 in dict1.items():
结果:I love SB too
因为字典的items()方法可以返回整个字典的key和value

将一个有数字和字符串的list中的字符串提取出来,并且将其中大写全都转换为小写:

L1 = ['Hello', 'World', 18, 'Apple', None]
L2 = [i.lower() for i in L1 if isinstance(i,str)]
print(L2)
if L2 == ['hello', 'world', 'apple']:
    print('测试通过!')
else:
    print('测试失败!')
1.4生成器generator

本质:生成器是实时按照某种规则生成数据,需要某一项就是生成到哪一项.generator保存的是算法,每被next(g)调用一次,就计算下一个,直到计算完所有元素,如果没有更多元素返回,就抛出StopIteration错误.
与列表生成器的区别:区别在于列表生成器是一次性将所有元素都生成,并存在内存中,而生成器只是一个随时可以调用的生成器,调用一次生成一个,这样可以节省大量的内存.而且对于有些无穷多元素的生成,只能用生成器生成(例如斐波那契数列)

方法:
1.将列表生成式的[]改为()即可:

L = [x for x in range(1,11)]
print(L)#[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

g = (x for x in range(1,11))
print(g)#<generator object <genexpr> at 0x0000008631ABACA8>

2.打印生成器生成的元素,使用next()函数就可以获得生成器的下一个返回值.但是常用的一般是使用for循环去迭代提取生成器的元素.

g = (x for x in range(1,11))
print(next(g))  #1
print(next(g))  #2
print(next(g))  #3
for i in g:
    print(i) #4 5 6 7 8 9 10

3.斐波那契数列生成器,生成小于10000的斐波那契
说明: yield关键字的作用是运算的时候会返回yield关键字后面的值,并停止到这里,下一次运行这个函数的时候继续从下一句开始运行.

def fib():
    n,a,b = 0,0,1
    while True:
        yield b
        a,b =b,a+b
        n=n+1
    return 'done'

a = fib() #此时的a保存了fib(10)这个生成器
print(next(a))  # 1 可以通过next(a)提取元素
print(next(a))  # 1 可以通过next(a)提取元素
temp = 0
for i in a:
    if(i>100000): #输出
        break
    print(i) #2 3 5 8 13 21 34 55...

4.杨辉三角

#使用生成器生成杨辉三角
def triangles():
    old = [1]
    while True:
        new = [1]
        len_old = len(old)
        for index,i in enumerate(old):
            if(index>0 ):
                new.append(old[index-1]+i)
        new.append(i)   #别搞忘了最后一个元素
        yield old       #返回一行杨辉三角
        old = new[:]    #下一行重新开始

n = 0
results = []
for t in triangles():
    print(t)
    results.append(t)
    n = n + 1
    if n == 10:
        break
if results == [
    [1],
    [1, 1],
    [1, 2, 1],
    [1, 3, 3, 1],
    [1, 4, 6, 4, 1],
    [1, 5, 10, 10, 5, 1],
    [1, 6, 15, 20, 15, 6, 1],
    [1, 7, 21, 35, 35, 21, 7, 1],
    [1, 8, 28, 56, 70, 56, 28, 8, 1],
    [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
]:
    print('测试通过!')
else:
    print('测试失败!')
1.5迭代器

本质:迭代器的本质就是可以被next()函数调用的对象
,可以使用isinstance()判断一个一个对象是否是迭代器.
迭代器与生成器的区别:
list列表, tuple元组, dict字典, set集合, str字符串, generator生成器, generator function生成器函数 都是属于可迭代对象Iterable,因为都可以被for迭代.
但是只有generator和generator function可以被next()函数调用,所以我们称这两个对象类型为迭代器,所有迭代器都是属于惰性计算

2函数式编程

本质:属于面向过程的一种,其思想更接近数学计算
**特点:**1.函数式编程语言是一种抽象程度很高的编程,语言,纯函数式编程是没有变量的,对于任何一个函数,输入确定,输出就一定确定,这样的函数没有副作用.
2.函数式编程还允许将一个函数本身作为参数传递给另一个函数,还允许返回一个函数.
3.python允许变量,支持函数作参数,和返回函数,所以python是对函数式编程提供部分支持.

2.1高阶函数

本质:(Higher-order function)就是函数名是一个变量,指向函数地址,函数名可以作为参数,可以指向别的函数地址

特点:
1.变量可以指向函数,也就是说函数可以作为一个对象,而变量可以复制这个对象,并拥有函数的所有特质,例如:

def fun1(n):
    return n*n*n

a = fun1
print(a(3)) #结果为27

2.函数名也是变量,函数名其实是指向函数的变量,也就是说函数名可以指向别的函数(叛徒),甚至别的非函数类型的对象,例如:

def fun1(n):
    return n*n*n

def fun2(n):
    return n*n

fun1=fun2
print(fun1(3)) #结果为9
fun1 = "哈哈,老子怎么变成字符串啦"
print(fun1) #结果为"哈哈,老子怎么变成字符串啦"

3.函数名作为参数,注意,这里只有函数名,没有后面的(),所有传入的其实是函数的地址,是在函数内部如果调用才会运行

def fun1(n):
    return n*n*n

def fun2(n,m,f):
    return n*n*f(2)

print(fun2(3,2,fun1)) #3*3*(2*2*2)=72
2.2 map/reduce 映射/归纳

map本质:
map就是映射,将一个函数依次作用于一个Iterable,然后生成一个新的Iterable
map例子:
1.将 f(x)=x2 f ( x ) = x 2 作用在list[1,2,3,4,5,6,7,8,9]上

def fun(n):
    return n*n
list1 = [1,2,3,4,5,6,7,8,9]

r = map(fun,list1)
print(list(r))  #1 4 9 16 25 36 49 64 81

2.将用户不规范的输入变成首字母大写,其他小写的名字

def normalize(a):
    return a[0].upper()+a[1:].lower()

L1 = ['adam', 'LISA', 'barT']
L2 = list(map(normalize, L1))
print(L2)   #['Adam', 'Lisa', 'Bart']

reduce本质:
reduce就是一个接收两个参数的函数,这个函数可以沿着一个Iterable依次将结果和下一个元素作为参数累积计算,在python3.7中reduce需要从functools中导入
reduce例子:
1.将一个列表里面的数字(都是个位数)组合成一个大数字

from functools import reduce

def fun(m,n):
    return m*10+n

list1 = [1,2,3,4,5,6,7,8,9]

r = reduce(fun,list1)
print(r)    #123456789

2.对一个list求积

from functools import reduce

def prod(list1):
    def fun1(a,b):
        return a*b
    return reduce(fun1,list1)

print('3 * 5 * 7 * 9 =', prod([3, 5, 7, 9]))
if prod([3, 5, 7, 9]) == 945:
    print('测试成功!')
else:
    print('测试失败!')

3.将字符串转化为浮点数

from functools import reduce

def str2float(s):
    DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
    pos = len(s)-1-s.find('.')
    s=s.replace('.','')
    flo = reduce(lambda x,y:x*10+y,map(lambda K:DIGITS[K],s))
    return flo/pow(10,pos)

//测试
print('str2float(\'12321.456\') =', str2float('12321.456'))
if abs(str2float('12321.456') - 12321.456) < 0.00001:
    print('测试成功!')
else:
    print('测试失败!')
2.3 过滤filter

本质: filter()函数是用来过滤序列的
用法: filter()接收一个函数和一个序列, 然后用这个函数判断每个序列中的元素,如果发现函数返回值为Ture,则保留这个元素,如果返回值为False,则丢弃这个元素.

例子:
1.只保留list中的奇数

def is_odd(n):
    return n%2==1

ls1 = [1,23,12,3,1,4,2,3,4]

print(list(filter(is_odd,ls1))) #[1, 23, 3, 1, 3]

2.删掉序列中的空字符串

def not_empty(s):
    return s and s.strip() #strip可以脱去字符串首尾的某种字符,默认为空格或者换行符

ls1 = ["121",'I31',"2fasd","fasdlove","  ","SB","4234","too","",None]

print(list(filter(not_empty,ls1)))

3.用filter求100000以内的素数

#生成奇数的生成器
def _odd_iter():
    n = 1
    while True:
        n+=2
        yield n

#筛选函数, 这个函数是返回一个匿名函数,也就是返回一个表达式
def _not_divisible(n):
    return lambda x: x%n > 0

def primes():
    yield 2 #2作为一个特殊素数,单独第一返回.
    it = _odd_iter()    #首先将所有奇数都当做素数
    while True:
        n = next(it)    #在素数列表中取出下一个作为真正的素数
        yield n         #输出这个真正的素数
        it = filter(_not_divisible(n),it)   #将素数列表中的能被当前素数所整除的数字筛选掉

for n in primes():
    if n>100000:
        break
    else:
        print(n)

4.使用filter筛选出回文数

#回文数判断之字符串法
def is_palindrome(n):
    str_n = str(n)
    len_n = len(str_n)
    for i in range(0,len_n//2):
        if(str_n[i]!=str_n[len_n-i-1]):
            return False
    return True

output = filter(is_palindrome, range(1, 1000))
print('1~1000:', list(output))
if list(filter(is_palindrome, range(1, 200))) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99,
                                                  101, 111, 121, 131, 141, 151, 161, 171, 181, 191]:
    print('测试成功!')
else:
    print('测试失败!')
#回文数判断之反向数法
def is_palindrome(n):
    reverse_num =0
    n_copy = n
    while(n>0):
        temp = n%10
        reverse_num = reverse_num*10 + temp
        n//=10
    return reverse_num==n_copy


output = filter(is_palindrome, range(1, 1000))
print('1~1000:', list(output))
if list(filter(is_palindrome, range(1, 200))) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99,
                                                  101, 111, 121, 131, 141, 151, 161, 171, 181, 191]:
    print('测试成功!')
else:
    print('测试失败!')
2.4 sorted 排序算法

特点:
1.python内置的sorted可以对list排序, 还能接收一个key函数,然后按照key函数的规则对list排序
2.key指定的函数将作用于每个list元素上,然后类似一个map一样将list映射一遍,然后再从新排序,排序完后的结果返回原list的元素在映射后排序的位置.
3.默认从小到大排序
例子:
1.忽略大小写,对单词进行排序

ls1 = ['bob', 'about', 'Zoo', 'Credit']

def key_fn(s):
    return s.lower()

print(sorted(ls1,key = key_fn)) #['about', 'bob', 'Credit', 'Zoo']

2.对一个tuple中的学生名字和成绩按照成绩排序

L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]

def by_score(t):
    return -t[1]
def by_name(t):
    return t[0]

print(sorted(L,key = by_score))
print(sorted(L,key = by_name))
2.5函数作为返回值

本质:
高阶函数可以接受函数作为参数,也可以把函数作为结果值返回. 这样就可以实现很多骚操作.
比如实现闭包效果.
比如实现函数延后运行.
闭包:
1.在外部函数中定义一个内部函数.
2.这个内部函数可以引用外部函数的参数和局部变量
3.外部函数将内部函数作为返回值
4.相关参数和变量都保存在返回的函数中
5.这种称为“闭包(Closure)”的程序结构拥有极大的威力.
6.形象点讲: 就是一个母亲孕育一个孩子,这个孩子可以获取母体的资源(参数,局部变量), 当生孩子的时候(返回), 孩子携带着自己本身和母亲的部分信息(孩子函数地址,母亲参数,局部变量)
例子:
1.一个可变参数个数的求和程序

def calc_sum(*args):
    ax = 0
    for i in args:
        ax+=i
    return ax
print(calc_sum(1,2,3,4,5,6))    # 21

2.一个懒惰的求和程序

def lazy_sum(*args):
    def calc_sum():
        ax = 0
        for i in args:
            ax+=i
        return ax
    return calc_sum

print(lazy_sum(1,2,3,4,5,6))    #输出<function lazy_sum.<locals>.calc_sum at 0x000000621C7D50D0>
print(lazy_sum(1,2,3,4,5,6))    #输出<function lazy_sum.<locals>.calc_sum at 0x000000621C7D50D0>, 由于没执行,所有并没有分配新的空间
print(lazy_sum(1,2,3,4,5,6)())  #输出21
sum_fn1 = lazy_sum(1,2,3,4,5,6)
print(sum_fn1)                  #<function lazy_sum.<locals>.calc_sum at 0x000000621C7D50D0>
print(sum_fn1())                #21
sum_fn2 = lazy_sum(1,2,3,4,5,6)
print(sum_fn2)                  #<function lazy_sum.<locals>.calc_sum at 0x000000621C7D5158> 由于上面执行了,所以给这里分配的真实地址
print(sum_fn2())                #21
sum_fn3 = lazy_sum(1,2,3,4,5,6)
print(sum_fn3)                  #<function lazy_sum.<locals>.calc_sum at 0x000000621C7D5510>
print(sum_fn3())                #21

3.利用闭包返回一个计数器函数,每次调用他返回递增整数

def createCounter():
    a=[0]
    def counter():
        a[0] = a[0] + 1
        return a[0]
    return counter

counterA = createCounter()  #在这里只执行createCounter()中的初始化和返回counter()函数的地址.
print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5 , 在这里执行了counter()函数,每次执行都改变了a[0]的值
counterB = createCounter()  #这里重新执行了createCounter()中的初始化和返回counter()函数的地址.
if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:
    print('测试通过!')
else:
    print('测试失败!')

4.利用闭包生成一个函数,当我们多次调用这个函数, 这个函数会返回不同的带默认参数的函数

#错误答案:
#虽然这里循环了3次,并且生成了3个函数,保存在呢fs里面, 但是这3个函数都不约而同的引用了i,而i在循环结束的时候为3,所以这些函数在循环结束后再执行,活该都等于9
def count():
    fs = []
    for i in range(1, 4):   
        def f():
             return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count()
print(f1(),f2(),f3())   #9 9 9

#正确答案:
#用一个中间层函数将参数和函数入口都固定到一个列表中,这样真正调用的时候就不会去调用一个变化了的参数了
def count():
    def f(j):
        def g():
            return j*j
        return g

    fs = []
    for i in range(1,4):
        fs.append(f(i))
    return fs   #fs里面保存的是f(i)的返回值,也就是带参数的g()函数的入口地址

f1, f2, f3 = count()    #fs1 = fs[0], fs2= fs[1], f3 = fs[2], 
print(f1(),f2(),f3())   #1 4 9
2.6 匿名函数lambda

本质:
1.在有些场合, 只需要定义一次的简单函数,我们也懒得起名字
2.我们就直接使用lambda 参数1,参数2:函数表达式 的方式方便快捷的定义这个函数
3.常常会节省很多行代码, 整个代码的逻辑也更加清晰
例子:
1.使用匿名函数改造下面代码

def is_odd(n):
    return n % 2 == 1

L = list(filter(is_odd, range(1, 20)))
print(L)    #[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

L2 = list(filter(lambda n:n%2==1, range(1, 20)))
print(L2)   #[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
2.7 装饰器@ decorator

本质:
1.函数是一个对象,函数名是指向这个对象的指针
2.通过变量指向一个函数,然后通过 变量名() 这样也能调用函数
3.函数有一个name属性,可以拿到函数的名字
4.如果想增加某个函数的功能,但是这个函数在线工作,所以我们需要在代码运行期间增加其功能.
5.装饰器就是用来在代码运行期间动态增加函数功能而不对原来的函数作任何改动的方法.
6.其本质就是通过装饰器将用户函数包装一下,然后调用用户函数的时候实质是调用的装饰器函数,而装饰器一般内部都按照规矩将用户函数的地址和参数都保护得很好,能正常执行用户函数.
用法:
1.定义一个外部函数作为装饰器名称
2.在1中定义的函数内部定义一个内部函数作为装饰器的内容, 在这个函数内写入装饰器真正的内容
3.在外部函数中将内部函数名返回
4.在有需要装饰器的用户函数定义前面加上@装饰器名
5.调用用户函数的时候,会先执行装饰器,然后装饰器再调用

def decorator1(func):   #执行2
    def work(*args,**kw):   #执行4
        print("%s: I am work"%func.__name__)    #执行5
        return func(*args,**kw) #执行6
    return work         #执行3

@decorator1             #执行1
def user_fn1(a,b,c):    #执行7
    print(a+b+c)        #执行8

user_fn1(1,2,3)         #执行0

#输出:
user_fn1: I am work
6

例子:
1.一个作用于任何函数的,可以打印函数执行时间的装饰器

import time,functools
def metric(fn):
    @functools.wraps(fn)#防止客户函数损失原来信息,wraps可以将原函数对象指定的属性复制给包装函数对象.(包括__module__,__main__,__name__,__qualname__,__doc__,__annotations__,__dict__
    def wrapper(*args, **kw):
        t1 = time.clock()
        print('%s executed in %s ms' % (fn.__name__, str(t1)))
        return fn(*args, **kw)
    return wrapper



@metric
def fast(x, y):
    time.sleep(0.0012)
    return x + y

@metric
def slow(x, y, z):
    time.sleep(0.1234)
    return x * y * z

f = fast(11, 22)
s = slow(11, 22, 33)


if f != 33:
    print('测试失败!')
elif s != 7986:
    print('测试失败!')

2.装饰器也有自己的参数怎么办,很简单,在原有两层基础上再加一层用来传递装饰器的参数就好. 记得要在@装饰器 的时候带上参数

def decoratorext(text):       #执行2
    def decoratorint(func):   #执行4
        def work(*args,**kw):   #执行5
            print(text)         #执行6
            print("%s: I am work"%func.__name__)    #执行7
            return func(*args,**kw) #执行8
        return work         #执行3
    return decoratorint
@decoratorext("装饰器工作ing")    #执行1
def user_fn1(a,b,c):    #执行9
    print(a+b+c)        #执行10

user_fn1(1,2,3)         #执行0
#输出:
#装饰器工作ing
#user_fn1: I am work
#6
2.8 偏函数functions.partial

定义:传递给partial多个参数,第一个参数是待改变的函数名,后面的参数是默认参数,这样就可以把一个函数的参数给固定住, 不需要每次都重新设置参数啦
例子:
1.2进制字符串转十进制数字

import functools

str1 = "101010101"

#原始方案
num = int(str1,base=2)
print(num)  #341

#传统方案,使用默认参数
def int2(x,base=2):
    return int(x,base)
print(int2(str1))   #341

#偏函数方案
int22 = functools.partial(int,base=2)
print(int22(str1))  #341

3 模块

目的:
1.代码越来越多,不易于维护
2.将函数分组,分别放到不同的文件中
3.一个.py为一个模块
4.包的本质是文件夹,模块的本质是.py文件
5.包可以多级嵌套,也就是多级文件夹
6.每个包里面都要有init.py文件,python才会将其识别为一个包
7.自己创建模块的命名必须和python自带的模块名不冲突
8.模块名和包名要遵守python命名规则

3.1 使用模块

方法:
使用import关键字+模块名
例子:*

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

' a test module '

__author__ = 'Michael Liao'

import sys

def test():
    args = sys.argv #argv用list储存命令行的参数,第一个为这个.py文件的名称
    # test1.py
    # 发送到发斯蒂芬
    # 3425
    # 235234
    # 25345234
    # 2453245234
    for i in args:
        print(i)

    #Too many arguments!
    if len(args)==1:
        print('Hello, world!')
    elif len(args)==2:
        print('Hello, %s!' % args[1])
    else:
        print('Too many arguments!')

if __name__=='__main__':    #如果python将这个文件作为主文件,那么这个文件名会变成__main__, 如果在其它文件运行时导入的hello模块,那么这个if就会判断失败.
    test()

作用域:
1. 公开: python默认的变量和函数都是公开的
2. 特殊变量: name , author , doc 等变量属于特殊变量,我们不要乱修改和定义
3. _私有变量: _age, _gread, 这种变量为私有变量, 从编程习惯上来我们不要引用它, 但是你实在要引用python也拿你没有办法.甚至都没有提醒…

4 面向对象

定义: Object Oriented Programming
简单来说就是面向对象, 万物皆对象, 不管你这么实现的, 只管你有没有这个功能或者属性, 然后调用你的功能或者属性即可, 至于你这么实现, 那是你的事.

4.1类和实例

类与实例:
类似抽象的,不具体的, 一种模板
实例是具体的,看得见摸得着的对象,

定义类:

class Student(object):
    def __init__(self,name,age,grade):
        print("I'm a student")
        self.name = name
        self.age  = age
        self.grade = grade
    def eat(self):
        print("I eat")
    def get_name(self):
        print(self.name)

创建实例:

stu1 = Student("jadetree",12,3) #创建对象的时候会自动调用__init__
stu1.eat() #其它函数就需要对象自己执行了
stu1.get_name()
print(stu1.name)    #python对象的属性默认是公共属性,外部也能访问
4.2访问限制

python的变量默认都是公开的,这样很不安全,如果需要私有,我们可以使用”__变量名”这种将变量定义为私有变量
例子:
1.将Student对象的gender隐藏起来, 并提供get_gender()和set_gender()接口, 并检查参数有效性

class Student(object):
    def __init__(self, name, gender):
        self.__name = name
        self.__gender = gender

    def set_gender(self,gender):
        if(gender == 'mall' or gender == 'female'):
            self.__gender = gender
            return 0
        else:
            return -1

    def get_gender(self):
        return self.__gender

# 测试:
bart = Student('Bart', 'male')
if bart.get_gender() != 'male':
    print('测试失败!')
else:
    bart.set_gender('female')
    if bart.get_gender() != 'female':
        print('测试失败!')
    else:
        print('测试成功!')
4.3 继承与多态

继承的本质:
继承嘛,就是父类的东西(变量,方法),其子类默认继承这些东西, 其子类就不需要再次写这些重复代码了.
继承的实现:
1.父类不用管, 直接像普通类一样写, 子类在定义类的时候, 在括号里面写上父亲的名字就算是父亲的儿子了

例子:
1.一个父类叫Animal, 两个子类分别叫Dog,和Cat, Animal有run()这个功能

#定义动物类
class Animal():
    def __init__(self):
        pass
    def run(self):
        print('Animal is run')

#狗类
class Dog(Animal):
    def __init__(self):
        pass
#猫类
class Cat(Animal):
    def __init__(self):
        pass

Ani1 =Animal()
dog1 = Dog()
cat1 = Cat()
#虽然狗和猫都没有直接写run方法,但是他们爸爸又run方法,所以就可以直接用
Ani1.run()  #Animal is run
dog1.run()  #Animal is run
cat1.run()  #Animal is run

多态的本质:
多态是专门针对继承的缺点来的, 继承的东西,我们如果想DIY一下, 可以么, 答案是可以的.
多态的实现:
父类还是不用管, 子类将要改变的方法或者变量再重新写一遍即可
例子:
1.一个父类叫Animal, 两个子类分别叫Dog,和Cat, Animal有run()这个功能,和age这个变量 但是Cat想用自己独特的run(),Dog想修改自己的年龄

class Animal():
    def __init__(self):
        self.age = 0
    def run(self):
        print('Animal is run')


class Dog(Animal):
    def __init__(self,age):
        self.age = age
    def get_age(self):
        print("My age is:",self.age)


class Cat(Animal):
    def __init__(self):
        pass
    def run(self):
        print("Cat is run")

Ani1 =Animal()
dog1 = Dog(10)  #狗需要传入一个年龄
cat1 = Cat()

Ani1.run()  #Animal is run
dog1.run()  #Animal is run
cat1.run()  #Cat is run
dog1.get_age()  #10

多态的真正威力:
python允许定义一个函数, 用来传入一个类,并调用这个类的方法, 如果多态的话, 可以在不更改这个类的情况下实现自动化根据不同子类而产生不同操作
例子:

class Animal():
    def __init__(self):
        self.age = 0
    def run(self):
        print('Animal is run')


class Dog(Animal):
    def __init__(self):
        pass
    def run(self):
        print("Dog is run")


class Cat(Animal):
    def __init__(self):
        pass
    def run(self):
        print("Cat is run")

class Tortoise(Animal):
    def __init__(self):
        pass
    def run(self):
        print("Tortoise is runing slowly...")

class Car():
    def run(self):
        print("Car is runing fast..., but it's not an animal")


def run(Animal):
    Animal.run()


ani1 =Animal()
dog1 = Dog()
cat1 = Cat()
tor1 = Tortoise()
car1 = Car()

run(ani1)   #Animal is run
run(dog1)   #Dog is run
run(cat1)   #Cat is run
run(tor1)   #Tortoise is runing slowly...
run(car1)   #Car is runing fast..., but it's not an animal

鸭子类型:
上面的例子就是说明python是一个鸭子类型语言, run()函数可以接受所有带run()方法的对象, 即使那个对象不是符合规范的类.

4.4 获取对象信息

目的:
当我们拿到一个对象的时候, 如果知道这个对象 是什么类型, 有什么方法?
实现:
1.使用tpye()函数判断 某个对象是否是 某个 类

ani1 =Animal()
dog1 = Dog()
cat1 = Cat()
tor1 = Tortoise()
car1 = Car()


print(type(123))    #<class 'int'>
print(type('str2323'))    #<class 'str'>
print(type(ani1))    #<class '__main__.Animal'>
print(type(dog1))    #<class '__main__.Dog'>
print(type(cat1))    #<class '__main__.Cat'>
print(type(car1))    #<class '__main__.Car'>

判断对象是否是函数:
判断函数需要types这个扩展, 需要import types

import types

def func1():
    pass

a = func1   #将函数地址赋值给a
print(type(a)==types.FunctionType) #True ,普通函数

print(type(abs)==types.BuiltinFunctionType) #True, 内建函数

print(type(lambda x:x)==types.LambdaType) #True, 匿名函数

print(type((x for x in range(10))) == types.GeneratorType) #True, 生成器

isinstance():
type()不能检测父类, 也是就判断dog是否是animal会显示False
于是乎, isinstance()就是上场了

ani1 =Animal()
dog1 = Dog()
cat1 = Cat()
tor1 = Tortoise()
car1 = Car()

print(isinstance(ani1,Animal))  #True
print(isinstance(dog1,Animal))  #True
print(isinstance(cat1,Cat))     #True
print(isinstance(car1,Animal))  #False

dir():
最屌的莫过于这个dir()了, 它可以返回一个包含字符串的list, 这个list是这个对象的所有属性和方法:

ani1 =Animal()
print(dir(anil)) 
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'run']
#绝大多数都是内建方法, 只有最后的age和run是自定义的

其中len()这样的内建函数是可以直接访问的, 其实len()就是调用的对象的len()

str1 = '234234'
print(str1.__len__())   #6
print(len(str1))        #6

如果自己写的类也想拥有len(), 就可以在类里面写一个len()函数, 根据python 的鸭子理论, len()可以传入任何带len()函数的对象.

class MyClass(str):
    def __init__(self,str):
        self.str=str
    def __len__(self):
        return len(self.str)

a = MyClass("asdfasd")
print(len(a))
4.5 实例属性和类属性

实例属性:由于python是动态语言,所以根据类创建的实例可以任意绑定属性, 而且能绑定之前不存在的属性.

class Student():
    pass

stu1 = Student()
stu1.score = 99
print(stu1.score)   #99

类属性: 如果某个类整个类都需要绑定属性,就在定义类的时候初始化就好,这个类属性归所有实例所有, 所有实例都可以访问它:

class Student():
    age = 20

stu1 = Student()

print(stu1.age) #20

例子:
1.每创建一个Student对象, 就将Student类的count加1

class Student():
    count = 0                   #这条指令只有创建类的时候会执行,创建实例的时候不会再执行
    def __init__(self, name):
        self.name = name
        Student.count+=1        #对于类的属性,要使用'类名.属性',而'self.属性'是对象的属性


# 测试:
if Student.count != 0:
    print('测试失败1!')
else:
    bart = Student('Bart')
    if Student.count != 1:
        print('测试失败2!')
    else:
        lisa = Student('lisa')
        if Student.count != 2:
            print('测试失败3!')
        else:
            print('Students:', Student.count)
            print('测试通过!')

5 面向对象高级编程

定义:封装,继承,多态只是最基础的面向对象, 高级面向对象有多重继承,定制类,元类等高端东西

5.1 slots

动态绑定:
python支持以下4种动态绑定
1.实例绑定属性:

class Student():
    pass

stu1 = Student()
stu1.age = 20

2.实例绑定方法,注意,实例绑定的方法只有该实例可用,其它实例是不能用的

from types import MethodType

class Student():
    pass

def setAge(class_stu,age):  #
    class_stu.age = age

stu1 = Student()
stu1.setAge = MethodType(setAge,stu1)   #将setAge方法绑定到stu1实例上
stu1.setAge(10)     #调用stu1实例刚绑定的方法
print(stu1.age)     #10

3.类绑定属性, 类绑定的属性,所有实例都可以访问

class Student():
    pass

def setAge(class_stu,age):  #
    class_stu.age = age


Student.age = 33   #将age属性绑定到Student类上

stu1 = Student()
stu2 = Student()
print(stu1.age)     #33
print(stu2.age)     #33

4.类绑定方法,类绑定的方法, 所有实例都可以用啦

from types import MethodType

class Student():
    pass

def setAge(class_stu,age):  #
    class_stu.age = age


Student.setAge = MethodType(setAge,Student)   #将setAge方法绑定到Student类上

stu1 = Student()
stu2 = Student()
stu1.setAge(10)     #调用Student类刚绑定的方法
print(stu1.age)     #10
stu2.setAge(20)     #调用Student类刚绑定的方法
print(stu2.age)     #20

_slots_:
由于python的动态绑定太过强大, 也太过自由, 为了世界的和平与安危, 很多时候我们需要使用slots限制动态绑定的范围, 类似一个白名单模式, 只有白名单里面的属性或方法才能动态绑定, 其余的一概不行, 当然这个限制只对当前类起作用, 对其子类不起作用,并没有连带责任. 如果子类也使用了slots限制, 那么子类允许定义的属性就是自身的slots和父类的slots的和
例子:
1.限制Student类只能添加name,age属性

class Student():
    __slots__ = ('name','age')


stu1 = Student()
stu2 = Student()
stu3 = Student()
stu1.name = 'jade'
stu2.age = 23
print(stu1.name)     #jade
print(stu2.age)     #33
stu3.ssssbbbb = 24234   #AttributeError: 

2.限制Student类只能添加name,age属性,限制Student的子类UniversityMan还能添加course

class Student():
    __slots__ = ('name','age')


class UniversityMan(Student):
    __slots__ = ('course')

uni1 = UniversityMan()
uni1.name = 'jade'
uni1.age = 23
uni1.course = 'mathematics'
print(uni1.name)     #jade
print(uni1.name)     #33
print(uni1.course )  #mathematics
5.2 @property (属性调用)

本质:
1.为了解决python类默认为公开变量导致的不安全问题, 需要设置私有变量
2. 然后使用接口函数来调用和更改私有变量
3. 但是使用接口函数又太麻烦,
4. 能不能既使用接口函数, 又在调用的时候像普通变量赋值一样方便呢
5. @property就是用来解决这个问题的.

使用:
1. 和平时一样定义类的接口函数, 只是这个函数名是要接口的私有变量名, 函数参数根据获取值为(self),设置值为(self,value)
2. 在接口函数上面加上@property(获取值),@score.setter(设置值)
3. 调用的时候,直接像对待类的公开变量一样对待类的私有变量即可

例子:
1:

class Student():
    def __init__(self,name,age):
        self._name=name

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self,value):
        if isinstance(value,str):
            self._name = value
        else:
            raise ValueError("not string")


stu1 = Student('jade',12)
print(stu1.name)    #jade 等效于stu1.get_name()
stu1.name = 'tree'
print(stu1.name)    #tree  等效于 stu1.set_name()
5.3 多重继承

本质: 有些类带有多个特点, 所以可能就会有多个父亲而不是一个, 这样我们就是应该允许一个类有多个父亲

实现:
实现很简单, 之前的继承是在类的定义小括号里面输入父亲的类名, 现在只需要在小括号里面放入多个类的类名即可实现多重继承

例子:

class Animal(object):
    pass

#哺乳动物
class Mammal(Animal):
    pass

#鸟类
class Bird(Animal):
    pass

#能飞
class Runnable(object):
    def run(self):
        print('Running...')

#能跑
class Flyable(object):
    def fly(self):
        print("Flying...")

#鹦鹉
class Parrot(Bird):
    pass

#鸵鸟
class Ostrich(Bird):
    pass

#狗
class Dog(Mammal,Runnable):
    pass

#蝙蝠
class Bat(Mammal,Flyable):
    pass

dog1 = Dog()
dog1.run()  #Running...

bat1 = Bat()
bat1.fly()  #Running...
5.4 定制类

本质:
在python中有许多类似_ slots , len _之类的特殊用途函数, 而且我们还可以通过对这些特殊用途函数重构, 从而实现个性化的功能

例子:
1.对某个类的_ str _重构, 从而使我们打印这个类的信息的时候更加个性化

class Animal(object):
    pass

# 默认的__str__
print(Animal()) #<__main__.Animal object at 0x000000CDB6C747F0>

class Animal(object):
    def __str__(self):
        return '我是有个性的输出'

#重构__str__后的类
print(Animal()) #我是有个性的输出

2.对_ iter _重构, 实现对类进行for…in迭代
,从而实现斐波那契数列

class Fib(object):
    def __init__(self):
        self.a, self.b = 0,1    #初始化计数器

    def __iter__(self):         #重构__iter__函数
        return self

    def __next__(self):         #对__next__()函数重构, 因为for...in的本质也是一直调用对象的__next__()
        self.a,self.b = self.b,self.a+self.b
        if self.a>100000:
            raise StopIteration()   #显示地引发异常, 后面语句不再执行
        return self.a

for n in Fib():
    print(n)    #1 1 2 3 ...75025

3.对_ getiterm _重构, 实现对类进行类似list的取第n个元素的操作

class Fib(object):
    def __getitem__(self, n):
        a, b = 1, 1
        for x in range(n):
            a,b = b, a+b
        return a


fib1 = Fib()
print(fib1[5]) #8
print(fib1[6]) #13

4.对_ getiterm _重构, 实现对类进行类似list的取第n个元素的操作,并且支持切片操作

class Fib(object):
    def __getitem__(self, n):
        #如果是整数对象
        if isinstance(n,int):
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        #如果是切片对象
        if isinstance(n,slice):
            start = n.start #切片开始位置
            stop = n.stop   #切片结束位置
            if start is None:
                start = 0
            a, b = 1, 1
            L = []
            #从第一个斐波那契开始, 找到切片的开始位置就放入到list中,直到结束位置
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a+b
            return L   

fib1 = Fib()
print(fib1[2:5]) #[2,3,5]

5.动态返回属性_ getattr _()
注意, 只有当没有找到对应属性的时候, 才会调用getattr(self,’变量名’),
而且变量名也可以是函数,只是调用的时候需要写成 ‘对象.函数名()’ 这样

class Student(object):
    def __init__(self):
        self.name = 'Michacl'

    def __getattr__(self,attr):
        if attr == 'score':
            return 99
        if attr == 'age':
            return 12


stu1 = Student()
print(stu1.name)    #Michacl 这里正常输出
#当属性不存在的时候, Python解释器试图调用__getattr__(self,'score')来尝试获取属性
print(stu1.score)   #99 虽然没有定义score, 但是定义了attr (属性), 所以可以正常选择默认的99输出
print(stu1.age)     #12

动态调用的作用:
如果别人还没想好到时候调用你的某个功能的名称, 你可以提前写好动态调用, 到时候他随便用一个方法名来调用你的功能, 你由于没有写这个方法名, 就会跳转到_ getattr _中去执行你的文件, 这样就实现了一个类可以被调用任何功能.

class Chain(object):
    def __init__(self,path = ''):
        self._path = path

    #1.
    def __getattr__(self,path):
        return Chain('%s/%s' % (self._path,path))

    #自定义_ Str__ 将本文件的目录返回
    def __str__(self):
        return self._path

    #这一句一般是用来偷懒的, __str__是运行时直接显示对象信息, __repr__是调试时显示对象信息
    __repr__ = __str__

cha1 = Chain()
print(cha1.status.user.timeline.list)   #/status/user/timeline/list
print(cha1.a.b.c.d.e.f)                 #/a/b/c/d/e/f

6.对象调用自己 : _ call _
通过对_ call _的自定义,我们便可以像函数一样运行一个实例

class Student(object):
    def __init__(self,name):
        self.name = name

    def __call__(self):
        print('My name is %s' % self.name)

stu1 = Student('jade')
stu1()     #My name is jade

7.函数与对象的区别
因为可以自定义_ call _, 所以函数和类可能在调用方式上无区别

def func_add(a,b):
    return a+b

class Class_add():
    def __call__(self, a, b):
        return a+b


print(func_add(11,22))  #33 函数

c1 = Class_add()
print(c1(11,22))        #33 对象

于是乎, 有时候我们需要使用callable()函数判断一个变量是否可以被调用执行, 也就是可以在名字后面加上()来执行的

def func_add(a,b):
    return a+b

class Class_add():
    def __call__(self, a, b):
        return a+b

class Student():
    pass

cadd1 = Class_add()

print(callable(Class_add))  #True 重构__ Call __的类名 可以
print(callable(Student))    #True 未重构__ Call __的类名 也可以
print(callable(cadd1))      #True 对象名可以
print(callable(func_add))   #True 函数名可以
print(callable([1,2,3]))    #False 列表不可以
5.5 枚举类

python中的枚举是一个独立的class类型, 每个常量都是唯一实例

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)
    #输出
    # Jan = > Month.Jan, 1
    # Feb = > Month.Feb, 2
    # Mar = > Month.Mar, 3
    # Apr = > Month.Apr, 4
    # May = > Month.May, 5
    # Jun = > Month.Jun, 6
    # Jul = > Month.Jul, 7
    # Aug = > Month.Aug, 8
    # Sep = > Month.Sep, 9
    # Oct = > Month.Oct, 10
    # Nov = > Month.Nov, 11
    # Dec = > Month.Dec, 12

value属性默认从为从1开始的int
我们可以用Enum派生出自定义类:

from enum import Enum, unique

@unique #unique是帮助我们检查是否有重复值的
class Weekday(Enum):
    Sun = 0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

#访问方式有很多
day1 = Weekday.Mon
print(day1) #Weekday.Mon

print(Weekday['Tue'])   #Weekday.Tue

print(Weekday.Tue.value)    #2

print(day1 == Weekday.Mon)  #True

print(day1 == Weekday.Fri)  #False

把Student的gender属性改造为枚举类型, 可以避免使用字符串

from enum import Enum,unique

class Gender(Enum):
    Male = 0
    Female = 1

class Student(object):
    def __init__(self,name,gender):
        self.name = name
        self.gender = gender

#测试
bart = Student('Bart',Gender.Male)
if bart.gender == Gender.Male:
    print('测试通过!')
else:
    print('测试失败')
5.5 使用元类 metaclass

动态语言和静态语言区别: 动态语言的函数和类的定义是运行时创建的, 而静态语言是编译时定义的

type()函数, 查看一个类型或者变量的类型

class Hello(object):
    def hello(self,name = 'world'):
        print('Hello, %s' % name)

h = Hello()
h.hello()   #Hello, world

print(type(Hello))  #<class 'type'>  Hello是一个class, 所以类型为type
print(type(h))  #<class '__main__.Hello'> h是一个实例, 所以类型为class Hello

type()其实还有个更重要的功能, type()既能返回一个对象的类型, 又可以创建
新类型, 我们可以通过type()函数创建出Hello类,而且通过type()创建的类和使用class定义的类是一毛一样的, 因为python解释器扫描到class后也是通过调用type()函数来创建类的.

def fn(self,name = 'world'):
    print('hello, %s' % name)

#type有三个参数, 1.class名称, 2.继承父类集合,使用元组括起来,如果只有一个父类,记得加逗号在后面 , 3 class的方法名称与函数绑定
Hello = type('Hello',(object,),dict(hello = fn))    #创建hello class

h1 =Hello() #生成一个对象
h1.hello()  #hello, world 调用对象方法

print(type(Hello))  #<class 'type'>
print(type(h1))     #<class '__main__.Hello'>

metaclass 元类
什么是元类: 元类就是类制造机, 可以通过参数动态制造类,而不是在一开始就在代码中写好类. 这样就可以实现 元类创造类, 类创造对象, 这样一个流程

应用场景: 比如一个软件可以根据数据库里面的每一行生成一个类, 那么我们就需要动态生成类.

6 错误,调试和测试

python有一套异常处理机制, pdb可以方便的单步执行

6.1 错误处理

1.程序运行中发生错误(除0,内存不足,打开文件失败等等),
2.一般可以返回事先约定的错误码.
3.但是错误码比较麻烦,而且调用者要使用一大堆判断语句判断是否出错.
4.而且一旦出错, 需要一级一级的上报, 直到遇到能处理这个错误的程序
5.机智的python给出了try…(except…as..)…finally…机制
6.try里面放可能发生的错误,
7.except … as …里面放对某种错误的处理办法, 可以有多个except连续写,这样就可以捕获多种错误.except后面还能接else,用来处理如果没有错误的情况
8.finally是个骈头, 不管是否错误都会执行, 并且可以不写finally
例子:

#发生除0错误
try:
    print('try...') #tyr... 在错误发生前的代码执行
    r = 10/0        #发生错误,转到except
    print('hh')     #不再执行

except ZeroDivisionError as e:
    print('except:',e) # 打印错误信息e: division by zero

finally:
    print('finally...') #finally...
print('END')
#未发生任何错误
try:
    print('try...') #tyr...
    r = 10/2       #
    print('hh')     #hh

except ZeroDivisionError as e:
    print('except:',e) # 无错误信息

finally:
    print('finally...') #finally...
print('END')            #END
#多个except
try:
    print('try...')
    r = 10/int('a')
    print('result:',r)

except ValueError as e:
    print('ValueError:',e)  #ValueError: invalid literal for int() with base 10: 'a'
except ZeroDivisionError as e:
    print('ZeroDivisionError:',e)
finally:
    print('finally...')
print('END')
#except 后面接 else
try:
    print('try...')     #try...
    r = 10/2
    print('result:',r)  #result: 5.0

except ValueError as e:
    print('ValueError:',e)      #no error
except ZeroDivisionError as e:  #finally...
    print('ZeroDivisionError:',e)   #
else:
    print('no error')
finally:
    print('finally...')
print('END')

7.跨层调用

#try...except 捕获错误可以跨层调用, 比如main()调用foo(),foo()调用bar(), bar()出错后, 会层层上报, 如果main()中写有try...except就可以处理这个错误.

def foo(s):
    return 10/int(s)

def bar (s):
    return foo(s)*2


def main():
    try:
        bar('0')
    except BaseException as e:
        print('Error:',e)   #Error: division by zero
    finally:
        print('finally...')

main()

8.python错误类型树状图

#python错误类型树状图
BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration
      +-- StopAsyncIteration
      +-- ArithmeticError
      |    +-- FloatingPointError
      |    +-- OverflowError
      |    +-- ZeroDivisionError
      +-- AssertionError
      +-- AttributeError
      +-- BufferError
      +-- EOFError
      +-- ImportError
      |    +-- ModuleNotFoundError
      +-- LookupError
      |    +-- IndexError
      |    +-- KeyError
      +-- MemoryError
      +-- NameError
      |    +-- UnboundLocalError
      +-- OSError
      |    +-- BlockingIOError
      |    +-- ChildProcessError
      |    +-- ConnectionError
      |    |    +-- BrokenPipeError
      |    |    +-- ConnectionAbortedError
      |    |    +-- ConnectionRefusedError
      |    |    +-- ConnectionResetError
      |    +-- FileExistsError
      |    +-- FileNotFoundError
      |    +-- InterruptedError
      |    +-- IsADirectoryError
      |    +-- NotADirectoryError
      |    +-- PermissionError
      |    +-- ProcessLookupError
      |    +-- TimeoutError
      +-- ReferenceError
      +-- RuntimeError
      |    +-- NotImplementedError
      |    +-- RecursionError
      +-- SyntaxError
      |    +-- IndentationError
      |         +-- TabError
      +-- SystemError
      +-- TypeError
      +-- ValueError
      |    +-- UnicodeError
      |         +-- UnicodeDecodeError
      |         +-- UnicodeEncodeError
      |         +-- UnicodeTranslateError
      +-- Warning
           +-- DeprecationWarning
           +-- PendingDeprecationWarning
           +-- RuntimeWarning
           +-- SyntaxWarning
           +-- UserWarning
           +-- FutureWarning
           +-- ImportWarning
           +-- UnicodeWarning
           +-- BytesWarning
           +-- ResourceWarning

9.栈保存错误

#调用栈, 如果错误未被捕获, 它会一直往上抛, 最后被python解释器捕获,打印错误信息,程序退出
def foo(s):
    return 10/int(s)

def bar(s):
    return foo(s)*2

def main():
    bar('0')

main()

#所以出错的时候, 一定要分析错误的调用栈信息, 才能定位真正的错误位置

10.记录错误 logging
可以通过loggin将错误记录在文件中

import logging

def foo(s):
    return 10/int(s)

def bar(s):
    return foo(s)*2

def main():
    try:
        bar('0')
    except Exception as e:
        logging.exception(e)


main()          #执行main打印了一堆错误
print('END')    #依然执行了这一句,说明错误被main函数处理

11.抛出错误: raise,
错误的本质是一个class,
捕获一个错误就是捕获到错误class的一个实例,
因此错误是有意创建并抛出的,
python的内置函数会抛出很多类型的错误,
我们也可以自定义抛出错误,
首先我们需要定义一个错误class, 选择好继承关系,
然后用raise语句抛出一个错误的实例:

class FooError(ValueError):
    pass

def foo(s):
    n = int(s)
    if n == 0:
        raise FooError('costom invalid value: %s' % s) #自定义除0错误
    return 10 / n

foo('0')

注意: raise的错误必须要在上级有处理的except, 否则就会抛给python解释器,导致整个程序退出

def foo(s):
    if s == 1:  #没事乱抛出
        raise


def main(s):
    foo(s)

main(2) #正常运行
main(1) #抛出错误给python解释器,程序结束
print('sb') #不执行

13.错误转换
可以通过raise语句将捕获的错误转换为另一个错误然后抛出

def foo():
    try:
        10 / 0
    except ZeroDivisionError:
        raise ValueError('input error')


def main():
    try:
        foo()
    except ValueError as e: #这里接收valueError
        print(e)


main()  #input error
print(10)   #10

14.使用try…except实现对一个带小数点或者不带小数点的字符串的转换区分:

from functools import reduce

def str2num(s):
    try:
        return int(s)   #尝试使用int(s)
    except:
        return float(s) #不行就用float(s)


def calc(exp):
    ss = exp.split('+')
    ns = map(str2num, ss)
    return reduce(lambda acc, x: acc + x, ns)

def main():
    r = calc('100 + 200 + 345')
    print('100 + 200 + 345 =', r)
    r = calc('99 + 88 + 7.6')
    print('99 + 88 + 7.6 =', r)

main()
6.2 调试

程序一次写完正常运行的概率为1%
写BUG才是常态, 所以我需要调试调试

方法1: print()大法

#print大法的好处是方便,直观,快捷, 
#缺点是影响性能,定位不准确, 最后还得删掉
def foo(s):
    n = int(s)
    print('>>> n = %d' % n) #在出错前打印出了n的值为0
    return 10/n

def main():
    foo('0')

main()

方法2: 断言assert大法

#凡是用print的地方都可以用断言
#优点:比print好像好了那么一丢丢,至少不用在正式运行中删除
#缺点:和print一样混乱和多
# 可以使用 -o参数在执行python文件的时候选择不执行assert
#语法: assert 判断句,输出句
def foo(s):
    n = int(s)
    assert n !=0,'n is zero'    #执行到这里就抛出了断言错误了, 后面的就不再执行
    return 10/n

def main():
    foo('0')

main()

方法3: logging大法

#logging和assert差不多, 都是可以替代print的
#优点:logging不会抛出错误, 可以输出到文件, 可以方便控制显示与否
#缺点:定义麻烦

import logging
#设置信息显示级别 debug,info,warning,error, 等级由低到高, 如果设置为高级别的信息等级,低级别的信息就不会打印
logging.basicConfig(level = logging.INFO)


s = '1'
n = int(s)
logging.info('n=%d'%n)  #在INFO中添加: root:n=0
print(10/n)

方案4: pdb大法

#.1写好程序并保存为test1.py
a = 10
b = 20
print(a+b)

#.2 使用python -m pdb test1.py 进入调试

#.3  输入l可以查看前11行代码 ,再次输出l,查看下一个11行

#.4 输入n单步执行

#.5 在任何时候输入p变量名可以查看变量

#.6 输入q可以结束调试

在代码中插入pdb.set_trace() 可以添加断点,调试的时候, 会运行到断点处停下来
import pdb
print('开始调试')
a = 10
b = 20
print('断点1')
pdb.set_trace()
print('断点2')
pdb.set_trace()
print('断点3')
pdb.set_trace()
print(a+b)

方案5:IDE大法
虽然用IDE调试方便,但是最后你会发现,logging大法才是终极武器

6.3 单元测试

测试驱动开发 Test Driven Development
1 定义:
单元测试是对一个模块,一个函数或者一个类进行正确性校验的测试工作.

2 单元测试unittest模块:
(1)先准备待测代码, 例如mydict.py

class Dict(dict):

    def __init__(self, **kw):
        super().__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Dict' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

(2)然后写单元测试文件, 先引入unittest, 和mydict中的Dict, 再编写测试类(从unittest.Testcase继承),可以获得很多内置条件判断

import unittest

from mydict import Dict

class TestDict(unittest.TestCase):

    def test_init(self):
        d = Dict(a=1, b='test')
        self.assertEqual(d.a, 1)
        self.assertEqual(d.b, 'test')
        self.assertTrue(isinstance(d, dict))

    def test_key(self):
        d = Dict()
        d['key'] = 'value'
        self.assertEqual(d.key, 'value')

    def test_attr(self):
        d = Dict()
        d.key = 'value'
        self.assertTrue('key' in d)
        self.assertEqual(d['key'], 'value')

    def test_keyerror(self):
        d = Dict()
        with self.assertRaises(KeyError):
            value = d['empty']

    def test_attrerror(self):
        d = Dict()
        with self.assertRaises(AttributeError):
            value = d.empty

(3)运行单元测试:

if __name__ == '__main__':
    unittest.main()

#结果:
.....
----------------------------------------------------------------------
Ran 5 tests in 0.001s

OK

也可以把mydict_test.py当做正常的python脚本运行(需要在文件中写上运行单元测试的代码)

python mydict_test.py

还可以通过参数-m unittest直接运行单元测试(不需要在文件中写上运行单元测试的代码,写了也不影响). 推荐这种方式, 可以批量运行多个单元测试, 还有很多工具可以自动来运行这些单元测试

python -m unittest mydict_test

(4)setUp与tearDown
**作用:**setUp()和tearDown()是一对, 会分别在每调用一个测试方法前后分别被执行

class TestDict(unittest.TestCase):

    def setUp(self):
        print('setUp...')

    def tearDown(self):
        print('tearDown...')

(5) 完整例子,对学生成绩等级函数测试
mystudent.py

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score
    def get_grade(self):
        if self.score>100 or self.score<0:
            raise ValueError

        if self.score >= 80:
            return 'A'

        elif self.score >= 60:
            return 'B'
        else:
            return 'C'

test1.py

import unittest
from mystudent import Student

class TestStudent(unittest.TestCase):

    #测试用例1,80-100
    def test_80_to_100(self):
        s1 = Student('Bart', 80)
        s2 = Student('Lisa', 100)
        self.assertEqual(s1.get_grade(), 'A')
        self.assertEqual(s2.get_grade(), 'A')

    # 测试用例2,60-80
    def test_60_to_80(self):
        s1 = Student('Bart', 60)
        s2 = Student('Lisa', 79)
        self.assertEqual(s1.get_grade(), 'B')
        self.assertEqual(s2.get_grade(), 'B')

    # 测试用例3, 0-60
    def test_0_to_60(self):
        s1 = Student('Bart', 0)
        s2 = Student('Lisa', 59)
        self.assertEqual(s1.get_grade(), 'C')
        self.assertEqual(s2.get_grade(), 'C')

    # 测试用例4,非法数据
    def test_invalid(self):
        s1 = Student('Bart', -1)
        s2 = Student('Lisa', 101)
        with self.assertRaises(ValueError): #判断是否抛出值错误的异常
            s1.get_grade()
        with self.assertRaises(ValueError):
            s2.get_grade()

if __name__ == '__main__':
    unittest.main()
6.4 文档测试

python的文档, 也就是注释其实是可以执行的, 于是乎我们可以在注释中写测试用例, 然后既可以自己测试,又可以作为例子作为用户帮助文档, 哈哈哈
我们使用内置的”文档测试”(doctest)模块可以直接提取注释中的代码并执行测试.
**1.例子1: 对函数进行文档测试

def foo(a,b):
    '''
    >>> foo(1,3)    #正确,所以文档测试通过
    4
    >>> foo(4,7)    #这里我故意将5作为答案, 果然在执行文档测试的时候报错Expected:12  Got:11
    12
    '''
    return a+b



if __name__ == '__main__':
    import doctest          #引入doctest
    doctest.testmod()       #执行文档测试

对raise错误的用例进行文档测试

def fact(n):
    '''
    Calculate 1*2*...*n

    >>> fact(1)
    1
    >>> fact(10)
    3628800
    >>> fact(-1)
    Traceback (most recent call last):
    ...    #这里可以用省略符号将中间报错的信息省略掉
    ValueError
    '''
    if n < 1:
        raise ValueError()
    if n == 1:
        return 1
    return n * fact(n - 1)


if __name__ == '__main__':
    import doctest
    doctest.testmod()

7 IO编程

什么是IO:
IO就是input/output, 包括磁盘,网络都属于IO

什么是Stream(流):
从(磁盘,网络)进入内存叫input Stream
从内存写出到(磁盘,网络)叫out Stream

什么是同步IO:
同步IO是指CPU等待IO执行缓慢的操作,效率低,编程简单

什么是异步IO:
异步IO是指CPU不等待IO执行缓慢的操作,效率高,编程复杂(需要轮询或者回调)

谁可以直接操纵IO:
只有操作系统有这个权限,并提供低级的C接口,然后高级的语言将这些低级C接口封装起来使用.

7.1 文件读写

python的文件读写函数写法和C兼容
1文件读写的本质:
由于现代操作系统不允许普通程序直接操作磁盘,所以读写文件的本质就是请求操作系统打开一个文件对象(通常称为文件描述符), 然后可以通过操作系统提供的接口从这个文件对象中读取数据,或者将内存中的数据写入这个文件对象.

2读文件语法:

f = open('./test.txt','r')
print(f.readline()) #hello

更安全的做法:

try:
    f = open('./test1.txt','r')
    print(f.readline())  # hello
except Exception:
    print('打开文件失败')

3关闭文件
每次打开都要记得关闭文件, 因为如果你不关闭文件,那么文件对象会占用操作系统的资源,并且操作系统同时只能打开有限数量的文件,
为了保证每次无论出错都关闭文件,我们可以这样:

try:
    f = open('./test.txt','r')
    print(f.readline())  # hello
finally:
    if f:
        f.close()

但是每次这样写太麻烦,我们可以用with语句调用close()方法:

with open('./test.txt','r') as f:
    print(f.read())

4读文件
o( ̄▽ ̄)d
f.read()会一次性读完所有文件到内存,如果是小文件倒没啥,如果是大文件,内存就会爆炸,
如果是小文件,可以用read()
如果不确定大小,反复调用read(size)
如果是配置文件,调用readlines()最方便

5 file-like Object
像open()函数一样返回这种有个read()方法的对象,我们称为file-like Object, 这种对象除了文件, 还有内存的字节流(比如在内存中创建的file-like Object StringIO,网络流, 以及自定义流(其实在类里面写个read()方法就属于自定义流啦)

6 二进制打开方式

f = open('./code.jpg','rb')
print(f.read()) #一堆16进制数

7 选择字符编码打开:

f = open('./utf-8.txt','r')
print(f.read())    #打开乱码: 鎴戞槸鐗规畩缂栫爜
f.close()

f = open('./utf-8.txt','r',encoding = 'utf-8')
print(f.read())    #打开正确: 我是特殊编码
f.close()

8 写文件

#写字符文件
f = open('./test2.txt','w')
f.write('hello, world')
f.close()

f = open('./test2.txt','r')
print(f.read())
f.close()

#写二进制文件
with open('./bin.txt','wb') as f:
    f.write(b'sdfsdf')

with open('./bin.txt','r') as f:
    print(f.read()) #sdfsdf

#注意,由于操作系统的读写操作有个缓冲区,所以我们执行write的时候并没有真正写入硬盘,只有当close()的时候操作系统才会将所有文件写入磁盘.为了防止我们搞忘close(),我们可以用with来保险:
with open('./bin.txt','wb') as f:
    f.write(b'你个sb')
7.2 StringIO和ByteIO

很多时候,数据读写不一定是文件, 也可以在内存中读写
什么是StringIO
StringIO就是在内存中读取str

from io import StringIO
f = StringIO()
f.write('hello')
f.write(' ')
f.write('world!')

print(f.getvalue()) #hello world!
from io import StringIO
f = StringIO('Hello!\nHi!\nGoodbye!')
while True:
    s = f.readline()
    if s =='':
        break
    print(s.strip())
#输出:
Hello!
Hi!
Goodbye!

BytesIO
BytesIO就是在内存中读写str
我们只要先创建StringIO,然后像文件一样将str写入即可

from io import BytesIO
f = BytesIO()
f.write('中文'.encode('utf-8'))
print(f.getvalue()) #b'\xe4\xb8\xad\xe6\x96\x87'
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值