Python总结与复习

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

基本语法

数据类型

Python的数据类型有

  1. 整数
  2. 浮点数
  3. 字符串 'str1' / "str2" / ''' str3''' / """str4"""
  4. 布尔值: True / False
  5. 空值: None , 是一个特殊的空值。
  6. 常量: 通常用全部大写的变量名表示常量, 只是一个习惯,值可以被改变

变量

a = 5 <= 3 or True
a = 10 % 3 + 10 // 3  #地板除得到整数
a = 10 % 3 + 10 / 3 #除法得到浮点数
a = 2**3 #计算乘方
a = 'ABC' 
b = a
a = 'XYZ' #输出b: ABC, a和b都是地址
PI = 3.1415

输入/输出

从键盘输入

name = input('Please enter your name: ')
print(name)

输出字符串

字节
#b表示bytes
print('ABC'.encode('ascii'))
print('ABC'.encode('utf-8'))
print('中文'.encode('utf-8'))
a = b'\xe4\xb8\xad\xff'.decode('utf-8',errors='ignore')
print(a)
print(len(a),len(b'\xe4\xb8\xad'),len('中'.encode('utf-8')))
print('中文测试正常')
不转义

r 表示内部字符默认不转义

print(r'seek\t\n\\')
print('''line1
line2
line3''')
print(r'''line1
line2
line3''')
字符串格式化
#格式化方式1
print('age: %d, name: %s, stunum: %04d, ave_score:%.2f' % (25, '然然', 32, 73.1415926))

#格式化方式2: format()
print('{0}成绩提升了{1:.2f}%'.format('童童', 5.2316))
print('my name is {0}, I am {1} years old. Please call me {0}'.format('陈明',12))

#格式化方式3: f-string
r = 2.5
s = 3.14 * r ** 2
print(f'The area of a circle with radius {r} is {s:.2f}')
占位符 替换内容
%d 整数
%f 浮点数
%s 字符串
%x 十六进制整数
字符串其他操作

替换字符

a = 'abc'
b = a.replace('a', 'A')

条件与循环

条件语句

height = float(input('height: '))
weight = float(input('weight: '))
bmi = weight / height**2
if bmi < 18.5:
    print('过轻')
elif bmi >= 18.5 and bmi < 25:
    print('正常')
elif bmi >= 25 and bmi < 28:
    print('过重')
else:
    print('肥胖')

if age >= 18:
    #什么都不做
    pass

for循环

names = ['Michael', 'Bob', 'Tracy']
for name in names:
    print(name)

sum = 0
for x in range(101):
    sum = sum + x
if sum == 5050:
    print('Gauss is correct.')
else:
    print('Gauss is wrong.')
d = {'a': 1, 'b': 2, 'c': 3}   
for key in d:
     print(key)

range() 函数左闭右开

while循环

n = 201
pow = 1
while n > 0:
    n = n - 1
    if n % 5 == 0:
        continue
    pow = pow * n
print(pow)

字典/集合/列表/元组

dictionary 字典,对应于其他语言的map结构

#创建dic
ad = {'mar': 13, (1,2):55,'wat': 67} 

#判断key是否存在
'mar' in ad
#取值
ad['hap'] #不存在抛异常
ad.get('mar') #不存在返回None
ad.get('mar',-1) #不存在返回指定值-1

#增加key
ad['hap'] = 23 
#删除key
ad.pop('mar')
print(ad)

set 集合

s = set([2,2,3,(1,2),5]) #要创建一个set,需要提供一个list作为输入集合
s.add(4) #添加元素
s.remove(4) #删除元素
se = set([5,2,(2,3)])
print(se & s) #set做交集
print(se | s) #set做并集

list 列表

roommate = ['suhong','ziheng','siyuan'] #创建list
len(roommate) #获取list中元素个数
print(roommate[2]) #取list中元素
print(roommate[-1]) #取list中元素
roommate.append('xinran') #追加元素
roommate.insert(1,['panpan','xinran']) #指定位置添加元素
roommate.pop() #删除末尾元素
roommate.pop(i) #删除索引i处的元素

roommate.sort() #对list进行排序
sorted(roomate) #对list进行排序

tuple 元组,不可变

she = ('suhong',) #创建tuple
she1 = ('suhong') #这不是创建tuple
she2 = () #创建空tuple
roommate = ('suhong', 'siyuan', ['panpan', 'ziheng'])
roommate[2][1] = 'bianbian'
print(roommate)
print(len(roommate))

函数

python内置函数列表

对函数的理解: 函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:

a = abs # 变量a指向abs函数
a(-1) # 所以也可以通过a调用abs函数

定义函数

#定义一个函数
def my_abs(x):
    if not isinstance(x, (int, float)):
        raise TypeError('bad operand type')
    if x >= 0:
        return x
    else:
        return -x

#定义空函数
def nop():
    pass     

#定义函数返回多个值    
def move(x, y, step, angle=0):
    nx = x + step * math.cos(angle)
    ny = y - step * math.sin(angle)
    return nx, ny   
 x, y = move(100, 100, 60, math.pi / 6)    #返回多值实际上是返回tuple

函数参数

def move(x, y, step, angle=0):
  1. 位置参数 : x,y,step叫做位置参数。

  2. 默认参数 : angle=0 叫默认参数。定义默认参数要牢记一点:默认参数必须指向不变对象!

    def add_end(L=[]): #这种定义指向了可变对象,错误
    def add_end(L=None): #这种定义执行不变对象,正确
    
  3. 可变参数: 传入一列值或一个list,tuple

    def calc(*numbers):
        sum = 0
        for n in numbers:
            sum = sum + n * n
        return sum
    calc(1, 2) 
    
    nums = [1, 2, 3]
    calc(*nums) #在list或tuple前面加一个*号,表示list的所有元素作为可变参数传进去
    

    可变参数在函数调用时自动组装为一个tuple

  4. 关键字参数

    def person(name, age, **kw):
        print('name:', name, 'age:', age, 'other:', kw)
    
    person('Michael', 30)    
    person('Bob', 35, city='Beijing')
    extra = {'city': 'Beijing', 'job': 'Engineer'}
    person('Jack', 24, **extra)
    

    关键字参数在函数内部自动组装为一个dic

  5. 命名关键字参数

    def person(name, age, *, city, job):
        print(name, age, city, job)
    def man(name, age, *, city='Beijing', job):
        print(name, age, city, job)    
    
    #函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了
    def person(name, age, *args, city, job):
        print(name, age, args, city, job)
    

    如果没有可变参数,就必须加一个*作为特殊分隔符。如果缺少*

其中默认参数、可变参数、关键字参数都可传可不传;位置参数和命名关键字参数必须传入。

参数定义的顺序必须是:位置参数、默认参数、可变参数、命名关键字参数和关键字参数。

对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。使用参考廖雪峰博客函数的参数

递归函数

def fact(n):
    if n==1:
        return 1
    return n * fact(n - 1)

高级特性

切片

L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
L[0:3] #取前3个元素,等同于 L[:3]
L[-2:] #从倒数第2个元素开始,取到 L[len-2],L[len-1]
L[-3:-1] #从倒数第2个元素开始,到倒数第1个元素,取到 L[len-3],L[len-2]
L[-1:-3:-1] #从最后一个元素开始,倒数取,取到 L[len-1],L[len-2],等同于L[:-3:-1]

tuple也是一种list,唯一区别是tuple不可变

(0, 1, 2, 3, 4, 5)[:3]

字符串'xxx'也可以看成是一种list

'ABCDEFG'[::2]

Python没有针对字符串的substring截取函数,只需要切片一个操作就可以完成

迭代

d = {'a': 1, 'b': 2, 'c': 3}
for key in d:
    print(key)
for value in d.values()
for k, v in d.items()

只要作用于一个可迭代对象,for循环就可以正常运行。判断可迭代对象

from collections.abc import Iterable
isinstance('abc', Iterable)

list实现循环下标

for i, value in enumerate(['A', 'B', 'C']):
    print(i, value)

for 循环可以同时引用多个变量

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

列表生成式

未来代替繁琐的for循环

[x * x for x in range(1, 11)]
[x * x for x in range(1, 11) if x % 2 == 0]
[m + n for m in 'ABC' for n in 'XYZ']
d = {'x': 'A', 'y': 'B', 'z': 'C' }
[k + '=' + v for k, v in d.items()]

L = ['Hello', 'World', 'IBM', 'Apple']
[s.lower() for s in L]

常用例子: 列出当前目录下的所有文件和目录名

import os
[d for d in os.listdir('.')]

for后面的if是一个筛选条件,不能带else.for前面的部分是一个表达式,它必须根据x计算出一个结果,必须带 else

[x if x % 2 == 0 else -x for x in range(1, 11)]

生成器

Python中,这种一边循环一边计算的机制,称为生成器:generator。

创建生成器

L = [x * x for x in range(10)] #这是1个list
g = (x * x for x in range(10)) #这是1个生成器

通过next()函数获得generator

next(g)

generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

实际上最常用到for取生成器的元素

g = (x * x for x in range(10))
for n in g:
    print(n)

经常将函数作为生成器

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'
>>> f = fib(6)
>>> f
<generator object fib at 0x104feaaa0>  
#取fib的值
while True:
    try:
        x = next(f)
        print('f:', x)
    except StopIteration as e:
        print('Generator return value:', e.value)
        break

fib函数是一个生成器。

如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator

generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。即 普通函数调用直接返回结果;generator函数的“调用”实际返回一个generator对象

迭代器

凡是可作用于for循环的对象都是Iterable类型;

凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。详细参考廖雪峰博客迭代器

函数式编程

高阶函数

高阶函数: 一个函数就可以接收另一个函数作为参数.

def add(x, y, f):
    return f(x) + f(y)

print(add(-5, 6, abs))

map/reduce

map()函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

def f(x):
    return x * x
r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
list(r)

list() 函数将迭代器转换为list。

reduce把结果和序列的下一个元素做累积计算。效果等价于下面

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
from functools import reduce
def fn(x, y):
    return x * 10 + y
reduce(fn, [1, 3, 5, 7, 9])

一个例子: str转换为int的函数:

from functools import reduce

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

def char2num(s):
    return DIGITS[s]

def str2int(s):
    return reduce(lambda x, y: x * 10 + y, map(char2num, s))

filter

filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

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

list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))

sorted

Python内置的sorted()函数就可以对list进行排序

sorted([36, 5, -12, 9, -21])

sorted()函数是一个高阶函数,接收一个key函数来实现自定义的排序

sorted([36, 5, -12, 9, -21], key=abs) #结果为[5, 9, -12, -21, 36]
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower) #忽略大小写的排序
sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True) #逆序排序

返回函数

不需要立刻计算,而是在后面的代码中,根据需要再计算。

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum
f = lazy_sum(1, 3, 5, 7, 9)    
f()#调用函数f时,才真正计算求和

说明: 内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。

闭包: 返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。参考廖雪峰博客闭包

匿名函数

关键字lambda表示匿名函数,冒号前面的x表示函数参数。

list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))

匿名函数lambda x: x * x实际上就是

def f(x):
    return x * x

匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

也可以把匿名函数作为返回值返回,比如:

def build(x, y):
    return lambda: x * x + y * y

装饰器

在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)

两层嵌套

本质上,decorator就是一个返回函数的高阶函数。我们要定义一个能打印日志的decorator:

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

函数对象有一个__name__属性,可以拿到函数的名字

借助Python的@语法,把decorator置于函数的定义处

@log
def now():
    print('2015-3-25')
now()

调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志.把@log放到now()函数的定义处,相当于执行了语句:

now = log(now)

由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。

三层嵌套

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:

import functools
def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
            #wrapper.__name__=func.__name__
        return wrapper
    return decorator

@log('execute')
def now():
    print('2015-3-25')    
now()
print(now.__name__)

和两层嵌套的decorator相比,3层嵌套的效果是这样的:

now = log('execute')(now)

面向对象(OOP)的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator

偏函数

functools.partial就是帮助我们创建一个偏函数的.

对于一个字符串转2进制整数的函数:

int('12345', base=2) #每次都传入int(x, base=2)非常麻烦
int2('1000000')
#于是定义
def int2(x, base=2):
    return int(x, base)

使用偏函数,创建新函数

import functools
int2 = functools.partial(int, base=2)
int2('1000000')

当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。

模块

使用模块

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

' a test module '

__author__ = 'Michael Liao'

import sys

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

if __name__=='__main__':
    test()

sys模块有一个argv变量,用list存储了命令行的所有参数。argv至少有一个元素,因为第一个参数永远是该.py文件的名称,例如:

运行python3 hello.py获得的sys.argv就是['hello.py']

运行python3 hello.py Michael获得的sys.argv就是['hello.py', 'Michael']

当我们在命令行运行hello模块文件时,Python解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该hello模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。

作用域

在Python中,是通过_前缀来实现的。

正常的函数和变量名是公开的(public),可以被直接引用,比如:abcx123PI等;

类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author____name__就是特殊变量,hello模块定义的文档注释也可以用特殊变量__doc__访问,我们自己的变量一般不要用这种变量名;

类似_xxx__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc__abc等;

private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。

private函数或变量不应该被别人引用,那它们有什么用呢?请看例子:

def _private_1(name):
    return 'Hello, %s' % name

def _private_2(name):
    return 'Hi, %s' % name

def greeting(name):
    if len(name) > 3:
        return _private_1(name)
    else:
        return _private_2(name)

我们在模块里公开greeting()函数,而把内部逻辑用private函数隐藏起来了,这样,调用greeting()函数不用关心内部的private函数细节,这也是一种非常有用的代码封装和抽象的方法,即:

外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public。

第三方模块

安装第三方模块,是通过包管理工具pip完成的

默认情况下,Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys模块的path变量中:

import sys
sys.path

如果我们要添加自己的搜索目录,有两种方法:

一是直接修改sys.path,添加要搜索的目录:

import sys
sys.path.append('/Users/michael/my_py_scripts')

这种方法是在运行时修改,运行结束后失效。

第二种方法是设置环境变量PYTHONPATH,该环境变量的内容会被自动添加到模块搜索路径中。设置方式与设置Path环境变量类似。

面向对象

类与实例

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

    def print_score(self):
        print('%s: %s' % (self.name, self.score))

    def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >= 60:
            return 'B'
        else:
            return 'C'        
bart = Student()

和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self

和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同:

bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.age = 8
>>> bart.age
8
>>> lisa.age
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'age'

访问限制

  1. 两个下划线__ 表示私有

    把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问.

     class Student(object):
    
         def __init__(self, name, score):
             self.__name = name
             self.__score = score
    
         def print_score(self):
             print('%s: %s' % (self.__name, self.__score))
    
         def get_name(self):
             return self.__name
    
         def get_score(self):
             return self.__score
    
         def set_score(self, score):
             if 0 <= score <= 100:
                 self.__score = score
             else:
                 raise ValueError('bad score')
    

    不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量.

     bart._Student__name
    

    禁用这种错误写法

     bart.__name = 'New Name' # 设置__name变量。 错误!
    
  1. 以双下划线开头,并且以双下划线结尾

    以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name____score__这样的变量名。

  2. 以一个下划线开头

    实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

继承和多态

class Animal(object):
    def run(self):
        print('Animal is running...')

class Dog(Animal):
    def run(self):
        print('Dog is running...')

class Cat(Animal):
    def run(self):
        print('Cat is running...')

def run_twice(animal):
    animal.run()
    animal.run()        

run_twice(Animal())
run_twice(Dog())
run_twice(Cat())

class Timer(object):
    def run(self):
        print('Start...')
run_twice(Timer())

前面是继承的写法。

后面是动态语言“file-like object“的“鸭子类型”: 则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了.

获取对象信息

使用type()

>>> type(123)==type(456)
True
>>> type(123)==int
True
>>> type('abc')==type('123')
True
>>> type('abc')==str
True
>>> type('abc')==type(123)
False

>>> import types
>>> def fn():
...     pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True

使用isinstance()

对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。

>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
>>> isinstance(d, Dog) and isinstance(d, Animal)
True

使用dir()

要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list

>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:

>>> len('ABC')
3
>>> 'ABC'.__len__()
3

我们自己写的类,如果也想用len(myObj)的话,就自己写一个__len__()方法:

>>> class MyDog(object):
...     def __len__(self):
...         return 100
...
>>> dog = MyDog()
>>> len(dog)
100

配合getattr()setattr()以及hasattr(),我们可以直接操作一个对象的状态

>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19

如果试图获取不存在的属性,会抛出AttributeError的错误:

>>> getattr(obj, 'z') # 获取属性'z'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'MyObject' object has no attribute 'z'

可以传入一个default参数,如果属性不存在,就返回默认值:

>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404

也可以获得对象的方法:

>>> hasattr(obj, 'power') # 有属性'power'吗?
True
>>> getattr(obj, 'power') # 获取属性'power'
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn
>>> fn # fn指向obj.power
<bound method MyObject.power of <__main__.MyObject object at 0x10077a6a0>>
>>> fn() # 调用fn()与调用obj.power()是一样的
81

类属性

创建类属性

class Student(object):
    name = 'Student'

在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。

>>> s = Student() # 创建实例s
>>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
Student
>>> print(Student.name) # 打印类的name属性
Student
>>> s.name = 'Michael' # 给实例绑定name属性
>>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
Michael
>>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
Student
>>> del s.name # 如果删除实例的name属性
>>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
Student

对象高级

多重继承、定制类、元类

使用slots

定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。

尝试给实例绑定一个方法:

>>> def set_age(self, age): # 定义一个函数作为实例方法
...     self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
>>> s.set_age(25) # 调用实例方法
>>> s.age # 测试结果
25

给class绑定方法:

>>> def set_score(self, score):
...     self.score = score
...
>>> Student.set_score = set_score

为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

试试:

>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的.除非在子类中也定义__slots__

使用@property

Python内置的@property装饰器负责把一个方法变成属性调用的:

class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值

>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
  ...
ValueError: score must between 0 ~ 100!

只定义getter方法,不定义setter方法就是一个只读属性

要特别注意:属性的方法名不要和实例变量重名。

多重继承

class Dog(Mammal, Runnable):
    pass

多重继承设计叫做MixIn。MixIn的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能,而不是设计多层次的复杂的继承关系。

定制类

错误调试与测试

错误处理

try:
    print('try...')
    r = 10 / int('2')
    print('result:', r)
except ValueError as e:
    print('ValueError:', e)
except ZeroDivisionError as e:
    print('ZeroDivisionError:', e)
else:
    print('no error!')
finally:
    print('finally...')
print('END')

没有错误发生,可以在except语句块后面加一个else,当没有错误发生时,会自动执行else语句。

Python的错误其实也是class,所有的错误类型都继承自BaseException,所以在使用except时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”。

常见的错误类型和继承关系

记录错误

Python内置的logging模块可以非常容易地记录错误信息:

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()
print('END')

通过配置,logging还可以把错误记录到日志文件里。

抛出错误

raise语句抛出一个错误的实例

class FooError(ValueError):
    pass

def foo(s):
    n = int(s)
    if n==0:
        raise FooError('invalid value: %s' % s)
    return 10 / n

foo('0')

调试

  1. 使用print()函数

  2. 用断言(assert):如果断言失败,assert语句本身就会抛出AssertionError

    def foo(s):
        n = int(s)
        assert n != 0, 'n is zero!'
        return 10 / n
    
    def main():
        foo('0')
    

    启动Python解释器时可以用-O参数来关闭assert

    $ python -O err.py
    
  3. logging 记录

    import logging
    
    logging.basicConfig(level=logging.INFO)
    s = '0'
    n = int(s)
    logging.info('n = %d' % n)
    print(10 / n)
    

    logging 有debuginfowarningerror等几个级别,当我们指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debuginfo就不起作用了。

  4. Python的调试器pdb

  5. IDE工具

单元测试

编写单元测试

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

编写单元测试,我们需要引入Python自带的unittest模块,编写mydict_test.py

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

编写单元测试时,我们需要编写一个测试类,从unittest.TestCase继承。

test开头的方法就是测试方法,不以test开头的方法不被认为是测试方法,测试的时候不会被执行。

对每一类测试都需要编写一个test_xxx()方法。由于unittest.TestCase提供了很多内置的条件判断,我们只需要调用这些方法就可以断言输出是否是我们所期望的。最常用的断言就是assertEqual()

self.assertEqual(abs(-1), 1) # 断言函数返回的结果与1相等

另一种重要的断言就是期待抛出指定类型的Error,比如通过d['empty']访问不存在的key时,断言会抛出KeyError

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

运行单元测试

最简单的运行方式是在mydict_test.py的最后加上两行代码:

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

这样就可以把mydict_test.py当做正常的python脚本运行:

$ python mydict_test.py

另一种方法是在命令行通过参数-m unittest直接运行单元测试:

$ python -m unittest mydict_test

这是推荐的做法,因为这样可以一次批量运行很多单元测试,并且,有很多工具可以自动来运行这些单元测试。

setUp与tearDown

class TestDict(unittest.TestCase):

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

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

这两个方法会分别在每调用一个测试方法的前后分别被执行。比如测试前需要启动数据库,可以在setUp()方法中连接数据库。

文档测试

# mydict2.py
class Dict(dict):
    '''
    Simple dict but also support access as x.y style.

    >>> d1 = Dict()
    >>> d1['x'] = 100
    >>> d1.x
    100
    >>> d1.y = 200
    >>> d1['y']
    200
    >>> d2 = Dict(a=1, b=2, c='3')
    >>> d2.c
    '3'
    >>> d2['empty']
    Traceback (most recent call last):
        ...
    KeyError: 'empty'
    >>> d2.empty
    Traceback (most recent call last):
        ...
    AttributeError: 'Dict' object has no attribute 'empty'
    '''
    def __init__(self, **kw):
        super(Dict, self).__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

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

运行python mydict2.py

$ python mydict2.py

什么输出也没有。这说明我们编写的doctest运行都是正确的。

参考

  1. 廖雪峰 Python基础

results matching ""

    No results matching ""