Python - 廖雪峰

Python 基础

~ 数据类型

(3)除法:

~ 变量

~ 常量

~ 字符编码和编码

  • Python的字符串:在最新的Python 3版本中,字符串是以Unicode编码的,也就是说,Python的字符串支持多语言;
  • 对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符:
    • ord() 字符 –> 整数
    • chr() 整数 –> 字符
>~ ord('A')
65
>~ ord('中')
20013
>~ chr(66)
'B'
>~ chr(25991)
'文'

注意:
由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码;当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上这两行:

#!/usr/bin/env python3      //注释是为了告诉Linux/OS X系统,这是一个Python可执行程序,Windows系统会忽略这个注释;
# -*- coding: utf-8 -*-     //注释是为了告诉Python解释器,按照UTF-8编码读取源代码,否则,你在源代码中写的中文输出可能会有乱码;

同时必须并且要确保文本编辑器正在使用UTF-8 without BOM编码:
这里写图片描述


~ 格式化

我们经常会输出类似'亲爱的xxx你好!你xx月的话费是xx,余额是xx'之类的字符串,而xxx的内容都是根据变量变化的,所以,需要一种简便的格式化字符串的方式;Python中,采用的格式化方式和C语言是一致的,用%实现;

%运算符就是用来格式化字符串的,有几个%?占位符,后面就跟几个变量或者值,顺序要对应好;如果只有一个%?,括号可以省略;

  • %s表示用字符串替换; 若不确定用什么,就用%s
  • %d表示用整数替换;
  • %f表示用浮点数替换;
  • %x 表示用十六进制整数替换;
>~ 'Hello, %s' % 'world'
'Hello, world'
>~ 'Hi, %s, you have $%d.' % ('Michael', 1000000)
'Hi, Michael, you have $1000000.'

(1)格式化整数和浮点数还可以指定是否补0和整数与小数的位数:

print('%2d-%02d' % (3, 1))    //输出:空3-01
print('%.2f' % 3.1415926)     //输出:3.14

(2)有些时候,字符串里面的%是一个普通字符,这个时候就需要转义,用%%来表示一个%:

>~ 'growth rate: %d %%' % 7
'growth rate: 7 %'

~ format()

另一种格式化字符串的方法是使用字符串的format()方法,它会用传入的参数依次替换字符串内的占位符{0}、{1}……,不过这种方式写起来比%要麻烦得多:

>~ 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.1%'

~ 列表list []

list是一种有序的集合,可以随时添加和删除其中的元素;

>~ classmates = ['Michael', 'Bob', 'Tracy']   //变量classmates就是一个list
>~ classmates
['Michael', 'Bob', 'Tracy']

>~ len(classmates)             //用len()函数可以获得list元素的个数
3

>~ classmates[1]               //用索引来访问list中每一个位置的元素,记得索引是从0开始的
'Bob'

>~ classmates[-1]              //如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素
'Tracy'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ classmates.append('Adam')         //list是一个可变的有序表,所以,可以往list中追加元素到末尾;
>~ classmates
['Michael', 'Bob', 'Tracy', 'Adam']
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ classmates.insert(1, 'Jack')      //也可以把元素插入到指定的位置,比如索引号为1的位置
>~ classmates
['Michael', 'Jack', 'Bob', 'Tracy', 'Adam']
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ classmates.pop()                  //要删除list末尾的元素,用pop()方法
'Adam'
>~ classmates
['Michael', 'Jack', 'Bob', 'Tracy']
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ classmates.pop(1)                 //要删除指定位置的元素,用pop(i)方法,其中i是索引位置:
'Jack'
>~ classmates
['Michael', 'Bob', 'Tracy']
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ classmates[1] = 'Sarah'            //要把某个元素替换成别的元素,可以直接赋值给对应的索引位置
>~ classmates
['Michael', 'Sarah', 'Tracy']
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ L = ['Apple', 123, True]           //list里面的元素的数据类型也可以不同

>~ s = ['python', 'java', ['asp', 'php'], 'scheme']   //list元素也可以是另一个list ,len(s)=4

>~ L = []                     //如果一个list中一个元素也没有,就是一个空的list,它的长度为0,len(L)=0

~ 元组tuple ()

另一种有序列表叫元组:tuple;tuple和list非常类似,但是tuple一旦初始化就不能修改;

>~ classmates = ('Michael', 'Bob', 'Tracy')

classmates这个tuple不能变了,它没有append(),insert()这样的方法;其他获取元素的方法和list是一样的,你可以正常地使用classmates[0],classmates[-1],但不能赋值成另外的元素;

tuple的陷阱:当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来,比如:

>~ t = (1, 2)
>~ t
(1, 2)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ t = ()       //如果要定义一个空的tuple,可以写成():
>~ t
()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ t = (1)      //但是,要定义一个只有1个元素的tuple,如果你这么定义,定义的不是tuple,是1这个数!
                 //这是因为括号()既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,
                 //因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1;
>~ t
1
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ t = (1,)      //所以,只有1个元素的tuple定义时必须加一个逗号,,来消除歧义;
>~ t
(1,)

~ 条件判断

age = 3
if age >= 18:                         //注意不要少写了冒号:
    print('your age is', age)
    print('adult')
elif age >= 6:                        //elif
    print('teenager')
else:                                 //注意不要少写了冒号:
    print('kid')

input

input()返回的数据类型是str,str不能直接和整数比较,必须先把str转换成整数;Python提供了int()函数来完成这件事情;

  • 若直接将s与2000进行比较,报错;
  • 若输入字符串,报错:int()函数发现一个字符串并不是合法的数字时就会报错,程序就退出了;
s = input('birth: ')
birth = int(s)
if birth < 2000:
    print('00前')
else:
    print('00后')

例题

# -*- coding: utf-8 -*-          //告诉Python解释器,按照UTF-8编码读取源代码,否则,源代码中写中文输出可能会有乱码
height = 1.75
weight = 80.5
BIM = (weight/height)**2         //n的m次方:n**m

if BIM<=18.5:                    //注意写冒号:      
    print("过轻")
elif 18.5<BIM and  BIM<=25:      //多个表达式之间用 and 连接
    print("正常")
elif 25<BIM and  BIM<=28:
    print("过重")
elif 28<BIM and BIM<=32:
    print("肥胖")
else:                             //注意写冒号:
    print("严重肥胖")
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
输出:
C:\Users\盗情\Desktop>python a.py
严重肥胖

~ 循环

(1)for x in ...循环:依次把*listtuple中的每个元素迭代出来*

for x in …循环就是把每个元素代入变量x,然后执行缩进块的语句;

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

for循环其实可以同时使用两个甚至多个变量,比如dict的items()可以同时迭代key和value:

>~ d = {'x': 'A', 'y': 'B', 'z': 'C' }
>~ for k, v in d.items():
...     print(k, '=', v)
...
y = B
x = A
z = C

range()函数:可以生成一个整数序列;

**例如:**range(101)就可以生成0-100的整数序列;

>~ list(range(5))
[0, 1, 2, 3, 4]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sum = 0
for x in range(101):      //0-100
    sum = sum + x
print(sum)

(2)while循环:

sum = 0
n = 99
while n > 0:
    sum = sum + n  //注意,这行按回车之后,在下一行会自动tab缩进,这行是按空格进行的缩进,两行缩进方式不一样,所以会报错
    n = n - 2      //将这行自动Tab缩进 改成按4个空格缩进
print(sum)

break:提前退出循环

continue:结束本次循环,开始下次循环

这两个语句通常都必须配合if语句使用;


~ 字典dict {}

Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度;

>~ d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}   //初始化时指定键值对
>~ d['Michael']
95
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ d['Adam'] = 67      //把数据放入dict的方法,除了初始化时指定外,还可以通过key放入:
>~ d['Adam']
67
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ d['Jack'] = 90      //由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉;
>~ d['Jack']
90
>~ d['Jack'] = 88
>~ d['Jack']
88
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ d['Thomas']          //如果key不存在,dict就会报错;
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'Thomas'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ 'Thomas' in d        //要避免key不存在的错误,有两种办法,一是通过in判断key是否存在;
False
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ d.get('Thomas')      //二是通过dict提供的get()方法,如果key不存在,可以返回None,或者自己指定的value
>~ d.get('Thomas', -1)  //注意:返回None的时候Python的交互环境不显示结果
-1
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ d.pop('Bob')         //要删除一个key,用pop(key)方法,对应的value也会从dict中删除
75 
>~ d
{'Michael': 95, 'Tracy': 85}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ key = [1, 2, 3]      //list不能作为key
>~ d[key] = 'a list'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

注意:
- 请务必注意,dict内部存放的顺序和key放入的顺序是没有关系的;
- dict的key必须是不可变对象:因为dict根据key来计算value的存储位置,如果每次计算相同的key得出的结果不同,那dict内部就完全混乱了;这个通过key计算位置的算法称为哈希算法(Hash)
- 要保证hash的正确性,作为key的对象就不能变;在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key;而list是可变的,就不能作为key;


~ set {}

set和dict类似,也是一组key的集合,但不存储value;由于key不能重复,所以,在set中,没有重复的key

要创建一个set,需要提供一个list作为输入集合:
注意:传入的参数[1, 2, 3]是一个list,而显示的{1, 2, 3}只是告诉你这个set内部有1,2,3这3个元素,显示的顺序也不表示set是有序的;

>~ s = set([1, 1, 2, 2, 3, 3])
>~ s                            
{1, 2, 3}                        //重复元素在set中自动被过滤
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ s.add(4)                     //通过add(key)方法可以添加元素到set中,可以重复添加,但不会有效果
>~ s
{1, 2, 3, 4}
>~ s.add(4)
>~ s
{1, 2, 3, 4}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ s.remove(4)                  //通过remove(key)方法可以删除元素
>~ s
{1, 2, 3}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ s1 = set([1, 2, 3])
>~ s2 = set([2, 3, 4])
>~ s1 & s2                      //取交集 &
{2, 3}
>~ s1 | s2                      //取并集 |
{1, 2, 3, 4}

set和dict的唯一区别仅在于没有存储对应的value,但是,set的原理和dict一样,所以,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证set内部“不会有重复元素”


~ 不可变对象

str是不变对象,而list是可变对象;

对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容;相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的;

>~ 每一行都是一个语句,当语句以冒号:结尾时,缩进的语句视为代码块;


Python(3)函数

函数是最基本的一种代码抽象的方式;

~ 调用函数

Python内置了很多有用的函数,我们可以直接调用;官方函数文档:https://docs.python.org/3/library/functions.html

  • 调用函数的时候,如果传入的参数数量不对,会报TypeError的错误,并且Python会明确地告诉你:abs()有且仅有1个参数,但给出了两个 :TypeError: abs() takes exactly one argument (2 given)

  • 如果传入的参数数量是对的,但参数类型不能被函数所接受,也会报TypeError的错误,并且给出错误信息:str是错误的参数类型: TypeError: bad operand type for abs(): 'str'

  • max函数max()可以接收任意多个参数,并返回最大的那个:>~ max(2, 3, 1, -5) 输出:3

数据类型转换函数

Python内置的常用函数还包括数据类型转换函数,比如int()函数可以把其他数据类型转换为整数;

>~ int('123')
123
>~ int(12.34)
12
>~ float('12.34')
12.34
>~ str(1.23)
'1.23'
>~ str(100)
'100'
>~ bool(1)
True
>~ bool('')
False

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

>~ a = abs        // 变量a指向abs函数,(abs:取绝对值)
>~ a(-1)          // 所以也可以通过a调用abs函数
1

~ 定义函数 - def

在Python中,定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回;

def my_abs(x):          //注意冒号 :
    if x>=0:            //注意冒号 :
        return x
    else:               //注意冒号 :
        return -x

如果你已经把my_abs()的函数定义保存为文件名.py文件了,那么,可以在该文件的当前目录下启动Python解释器,用from 文件名 import my_abs来导入my_abs()函数;

>~ from a import my_abs
>~ my_abs(-89)
89
  • 直接运行.py文件时,在cmd下输入 python a.py
  • 使用自定义函数时,在交互环境中先引入自定义函数 from a import my_abs ,再调用自定义函数 my_abs(-89)

~ 空函数 - pass语句

如果想定义一个什么事也不做的空函数,可以用pass语句:

def nop():
    pass

if age >= 18:     //pass还可以用在其他语句里
    pass          //缺少了pass,代码运行就会有语法错误

pass语句什么都不做,可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来;


~ 参数检查isinstance

  • 调用函数时,如果参数个数不对,Python解释器会自动检查出来,并抛出TypeError;(与Python内置函数一致);
  • 但是如果参数类型不对,Python解释器就无法帮我们检查;(与Python内置函数不一样)
    当传入了不恰当的参数时,内置函数abs会检查出参数错误,而我们定义的my_abs没有参数检查,会导致if语句出错,出错信息和abs不一样;所以,这个函数定义不够完善;

完善函数 需要修改一下my_abs的定义:对参数类型做检查,只允许整数和浮点数类型的参数;数据类型检查可以用内置函数isinstance()实现:

def my_abs(x):
    if not isinstance(x, (int, float)):        //如果x不是int、float则报错
        raise TypeError('bad operand type')    //报错显示的信息
    if x >= 0:
        return x
    else:
        return -x

~ 函数返回多个值 - 返回的是一个值tuple

比如在游戏中经常需要从一个点移动到另一个点,给出坐标、位移和角度,就可以计算出新的新的坐标:

import math          //import math语句表示导入math包,并允许后续代码引用math包里的sin、cos等函数;

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)
>~ print(x, y)
151.96152422706632 70.0
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ r = move(100, 100, 60, math.pi / 6)    //但其实这只是一种假象,Python函数返回的仍然是单一值
>~ print(r)
(151.96152422706632, 70.0)

原来返回值是一个tuple!但是,在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便;


~ 函数的参数

Python的函数定义非常简单,但灵活度却非常大;除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码

(1)位置参数

power(x)函数,计算x的平方,参数x就是一个位置参数;
修改后的power(x, n)函数有两个参数:x和n,这两个参数都是位置参数,调用函数时,传入的两个值按照位置顺序依次赋给参数x和n;

(2)默认参数 - n=value

新的power(x, n)函数定义没有问题,但是,旧的调用代码失败了,原因是我们增加了一个参数,导致旧的代码因为缺少一个参数而无法正常调用:

>~ power(5)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: power() missing 1 required positional argument: 'n'   //调用函数power()缺少了一个位置参数n

这个时候,默认参数就排上用场了:

def power(x, n=2):     //由于我们经常计算x2,所以,完全可以把第二个参数n的默认值设定为2
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ power(5)           //当我们调用power(5)时,相当于调用power(5, 2):
25
>~ power(5, 2)
25
  • 默认参数可以简化函数的调用;设置默认参数时,有几点要注意:
    • 一是必选参数在前,默认参数在后,否则Python的解释器会报错;
    • 二是如何设置默认参数;
    • 当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面;变化小的参数就可以作为默认参数;
    • 定义默认参数要牢记一点:默认参数必须指向不变对象;

(3)可变参数 - 参数前面加星号*

在Python函数中,还可以定义可变参数;顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个;

给定一组数字a,b,c……,计算a2 + b2 + c2 + ……,要定义出这个函数,我们必须确定输入的参数;由于参数个数不确定,我们首先想到可以把a,b,c……作为一个list或tuple传进来,这样,函数可以定义如下:

def calc(numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ calc([1, 2, 3])          //调用的时候,需要先组装出一个list或tuple
14
>~ calc((1, 3, 5, 7))
84
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def calc(*numbers):         //定义可变参数,在参数前面加 * 
    sum = 0                      
    for n in numbers:
        sum = sum + n * n
    return sum
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ calc(1, 3, 5, 7)              //调用该函数时,可以传入任意个参数,包括0个参数;
84
>~ calc()
0
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ nums = [1, 2, 3]       //如果已经有一个list或者tuple,要调用一个可变参数,在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去;
>~ calc(*nums)
14

(4)关键字参数 - 参数前面加两个星号**

  • 可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple
  • 关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict
def person(name, age, **kw):               //函数person除了必选参数name和age外,还接受关键字参数kw
    print('name:', name, 'age:', age, 'other:', kw)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ person('Michael', 30)                      //在调用该函数时,可以只传入必选参数
name: Michael age: 30 other: {}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ person('Bob', 35, city='Beijing')          //也可以传入任意个数的关键字参数
name: Bob age: 35 other: {'city': 'Beijing'}
>~ person('Adam', 45, gender='M', job='Engineer')
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ extra = {'city': 'Beijing', 'job': 'Engineer'}     //和可变参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去
>~ person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

**extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra;

(5)命名关键字参数

  • 对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数;
  • 对于命名关键字参数,可以限制关键字参数的名字,只接受设置的关键字参数;
def person(name, age, *, city, job):    //命名关键字参数需要一个特殊分隔符 * , * 后面的参数被视为命名关键字参数;
    print(name, age, city, job)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~    
>~ person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def person(name, age, *args, city, job):    //如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了
    print(name, age, args, city, job)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ person('Jack', 24, 'Beijing', 'Engineer')    //命名关键字参数必须传入参数名,这和位置参数不同;如果没有传入参数名,调用将报错
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: person() takes 2 positional arguments but 4 were given
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def person(name, age, *, city='Beijing', job):   //命名关键字参数可以有缺省值,从而简化调用:定义city默认值Beijing
    print(name, age, city, job)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ person('Jack', 24, job='Engineer')          //由于命名关键字参数city具有默认值,调用时,可不传入city参数:
Jack 24 Beijing Engineer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def person(name, age, city, job):      //使用命名关键字参数时,要特别注意,若没有可变参数,就必须加一个*作为特殊分隔符;  
    pass                  //如果缺少*,Python解释器将无法识别位置参数和命名关键字参数:city和job被视为位置参数 

(6)参数组合

  • 在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数命名关键字参数,这5种参数都可以组合使用;
  • 参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数;
  • 在函数调用的时候,Python解释器自动按照参数位置和参数名把对应的参数传进去;
  • 对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的;
def f1(a, b, c=0, *args, **kw):       //ab是必选参数(位置参数),c是默认参数,args是可变参数,kw是关键字参数
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

def f2(a, b, c=0, *, d, **kw):        //d是命名关键字参数,调用时需要传入参数名:key=value ;
    print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ f1(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>~ f1(1, 2, c=3)
a = 1 b = 2 c = 3 args = () kw = {}
>~ f1(1, 2, 3, 'a', 'b')
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
>~ f1(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
>~ f2(1, 2, d=99, ext=None)
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ args = (1, 2, 3, 4)                         //通过一个tuple和dict,你也可以调用上述函数
>~ kw = {'d': 99, 'x': '#'}
>~ f1(*args, **kw)
a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
>~ args = (1, 2, 3)
>~ kw = {'d': 88, 'x': '#'}
>~ f2(*args, **kw)
a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}

练习:以下函数允许计算两个数的乘积,请稍加改造,变成可接收一个或多个数并计算乘积;

def product(x, y):
    return x * y

def product(x,*args): 
    sum = 1
    for n in args :
        sum = sum * n
    return sum*x

(7)小结

  • 默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!
  • 要注意定义可变参数和关键字参数的语法:
    • *args是可变参数,args接收的是一个tuple;
    • **kw是关键字参数,kw接收的是一个dict;
  • 调用函数时如何传入可变参数和关键字参数的语法:
    • 可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3))
    • 关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})
  • 使用*args**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法;
  • 命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值;
  • 定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数;

~ 递归函数

在函数内部,可以调用其他函数;如果一个函数在内部调用自身本身,这个函数就是递归函数;

  • 注意:递归调用的次数过多,会导致栈溢出!
def fact(n):
    if n==1:
        return 1
    return n * fact(n - 1)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ fact(1000)                              //栈溢出
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in fact
  ...
  File "<stdin>", line 4, in fact
RuntimeError: maximum recursion depth exceeded in comparison

解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的;

尾递归

是指在函数返回的时候,调用自身本身,并且,return语句不能包含表达式;这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况;

上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了;要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:

def fact(n):
    return fact_iter(n, 1)

def fact_iter(num, product):
    if num == 1:
        return product
    return fact_iter(num - 1, num * product)

Python(4)高级特性

~ 切片Slice

切片:用于取一个listtuple字符串的部分元素;相当于针对字符串提供的很多各种截取函数(例如,substring),其实目的就是对字符串切片;

>~ L = list(range(100))     //创建一个0-99的数列
>~ L
[0, 1, 2, 3, ..., 99]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ L[0:10]                   //取出前10个数,从索引0开始取,直到索引3为止,包含左,不包含右;
>~ L[:10]                    //如果第一个索引是0,还可以省略
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ L[10:20]                  //也可以从索引10开始,取出10个元素出来
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ L[-10:]                   //取出后10个数;(Python支持L[-1]取倒数第一个元素,那么它同样支持倒数切片)
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ L[:10:2]                  //前10个数,每两个取一个
[0, 2, 4, 6, 8]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
>~ L[::5]                    //所有数,每5个取一个
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ L[:]                      //什么都不写,只写[:]就可以原样复制一个list
[0, 1, 2, 3, ..., 99]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ (0, 1, 2, 3, 4, 5)[:3]    //tuple也是一种list,唯一区别是tuple不可变;因此,tuple也可以用切片操作,只是操作的结果仍是tuple
(0, 1, 2)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ 'ABCDEFG'[:3]             //字符串'xxx'也可以看成是一种list,每个元素就是一个字符;因此,字符串也可以用切片操作,只是操作结果仍是字符串
'ABC'
>~ 'ABCDEFG'[::2]
'ACEG'

~ 迭代Iteration

  • 迭代(Iteration):通过for ... in循环来遍历一个给定list、tuple、其他可迭代对象;
  • 只要是可迭代对象,无论有无下标,都可以迭代,比如dict就可以迭代;
    • 迭代key :for key in d:
    • 迭代key :for value in d.values():
    • 同时迭代key和value :for k, v in d.items():
    • 迭代字符串 :for ch in 'ABC':
>~ d = {'a': 1, 'b': 2, 'c': 3}    //迭代dict

>~ for key in d:                   //默认情况下,dict迭代的是key
...     print(key)
...
a                                   //因为dict的存储不是按照list的方式顺序排列,所以,迭代出的结果顺序很可能不一样
c
b
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ for value in d.values():        //迭代value
>~ for k, v in d.items():          //同时迭代key和value
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ for ch in 'ABC':                //迭代字符串
...     print(ch)
...
A
B
C

通过collections模块的Iterable类型判断一个对象是可迭代对象

>~ from collections import Iterable    //引入Iterable
>~ isinstance('abc', Iterable)         //str是否可迭代
True
>~ isinstance([1,2,3], Iterable)       //list是否可迭代
True
>~ isinstance(123, Iterable)           //整数是否可迭代
False

对list实现类似Java那样的下标循环

Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:

>~ for i, value in enumerate(['A', 'B', 'C']):
...     print(i, value)
...
0 A
1 B
2 C

同时引用了两个变量

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

练习:请使用迭代查找一个list中最小和最大值,并返回一个tuple

def q(L):
    if L==[]:
        return (None, None)
    else:
        m=L[0]
        n=L[0]
        for x in L:
            if m>x:
                m=x
            if n<x:
                n=x
        return (m,n)

~ 列表生成式List Comprehensions

列表生成式是Python内置的非常简单却强大的可以用来创建list的生成式;通过列表生成式,我们可以直接创建一个列表;但是,受到内存限制,列表容量肯定是有限的;

生成[1x1, 2x2, 3x3, ..., 10x10],用循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list;

>~ [x * x for x in range(1, 11)]        //写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ [x * x for x in range(1, 11) if x % 2 == 0]   //for循环后面还可以加上if判断,筛选出仅偶数的平方
[4, 16, 36, 64, 100]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ [m + n for m in 'ABC' for n in 'XYZ']         //使用两层循环,可以生成全排列
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ d = {'x': 'A', 'y': 'B', 'z': 'C' }           //列表生成式也可以使用两个变量来生成list
>~ [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ L = ['Hello', 'World', 'IBM', 'Apple']        //把一个list中所有的字符串变成小写
>~ [s.lower() for s in L]                        //如果list中既包含字符串,又包含整数,由于非字符串类型没有lower()方法,所以列表生成式会报错
['hello', 'world', 'ibm', 'apple']

~ 生成器generator

通过列表生成式,我们可以直接创建一个列表;但是,受到内存限制,列表容量肯定是有限的;而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了;

在Python中,一边循环一边计算的机制,称为生成器:generator;生成器在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省大量的空间;

创建一个generator方法一:

把一个列表生成式的[]改成(),就创建了一个generator;

>~ L = [x * x for x in range(10)]          //创建了一个列表list
>~ L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>~ g = (x * x for x in range(10))          //创建了一个生成器generator
>~ g
<generator object <genexpr> at 0x1022ef630>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ next(g)            //要一个一个打印出generator的每一个元素,可以通过next()函数获得generator的下一个返回值
0
.......
>~ next(g)            //generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ g = (x * x for x in range(10))   //创建了一个generator后,通过for循环来迭代它,不需关心StopIteration的错误
>~ for n in g:
...     print(n)

创建一个generator方法二:

如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现;

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:1, 1, 2, 3, 5, 8, 13, 21, 34, ... ; 斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易;

函数和generator仅一步之遥;要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        //print(b)             
        yield b           //如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator;
        a, b = b, a + b
        n = n + 1
    return 'done'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ f = fib(6)            //f是一个generator
>~ f
<generator object fib at 0x104feaaa0>
>>>next(f)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ for n in fib(6):       //使用for循环来迭代generator的值
...     print(n)
...
1
1
2
3
5
8
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//但是用for循环调用generator时,发现拿不到generator的return语句的返回值;如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中
>~ g = fib(6)
>~ while True:
...     try:
...         x = next(g)
...         print('g:', x)
...     except StopIteration as e:
...         print('Generator return value:', e.value)
...         break
...
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done

最难理解的就是generator和函数的执行流程不一样:

  • 函数是顺序执行,遇到return语句或者最后一行函数语句就返回;
  • 而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行;

~ 迭代器 Iterator(可迭代对象 Iterable)

可以直接作用于for循环的数据类型有以下几种:

  • 一类是集合数据类型,如list、tuple、dict、set、str等;
  • 一类是generator,包括生成器和带yield的generator function;
    生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了;

  • Iterable:这些可以直接作用于for循环的对象统称为可迭代对象;可以使用isinstance()判断一个对象是否是Iterable对象;

  • Iterator:可以被next()函数调用并不断返回下一个值的对象称为迭代器;可以使用isinstance()判断一个对象是否是Iterator对象

iter()函数**

生成器都是Iterator对象,但list、dict、str虽然是Iterable,却不是Iterator
把list、dict、str等Iterable变成Iterator可以使用iter()函数:

>~ isinstance(iter([]), Iterator)
True
>~ isinstance(iter('abc'), Iterator)
True

为什么list、dict、str等数据类型不是Iterator?

这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误;可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算;

Iterator甚至可以表示一个无限大的数据流,例如全体自然数;而使用list是永远不可能存储全体自然数的;

  • 注意: Python的for循环本质上就是通过不断调用next()函数实现的;

Python(5)函数式编程 Functional Programming

~ 函数式编程

  • 函数与函数式编程

    • 函数是面向过程的程序设计的基本单元;
    • 函数式编程(Functional Programming),虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算;
  • 对于编程语言:

    • 越低级的编程语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;
    • 越高级的编程语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言;
  • 函数式编程就是一种抽象程度很高的编程范式:

    • 纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用;
    • 允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的;
  • Python对函数式编程提供部分支持;由于Python允许使用变量,因此,Python不是纯函数式编程语言;

(1)什么是函数式编程

函数式编程使用一系列的函数解决问题;

  • 函数仅接受输入并产生输出,不包含 任何能影响产生输出的 内部状态,输出只依赖于输入
  • 任何情况下,使用相同的参数调用函数始终能产生同样的结果;
  • 函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数;
  • 在一个函数式的程序中,输入的数据“流过”一系列的函数,每一个函数根据它的输入产生输出;
  • 函数式风格避免编写有“边界效应”的函数:修改内部状态,或者是其他无法反应在输出上的变化;
  • 完全没有边界效应的函数被称为“纯函数式的”;避免边界效应意味着不使用在程序运行时可变的数据结构

可以认为函数式编程刚好站在了面向对象编程的对立面,但是同时使用函数式编程和面向对象编程:

  • 对象通常包含内部状态(字段),和许多能修改这些状态的函数,程序则由不断修改状态构成;
  • 函数式编程则极力避免状态改动,并通过在函数间传递数据流进行工作;

(2)为什么使用函数式编程 (优点)

  • 逻辑可证没有边界效应使得更容易从逻辑上证明程序是正确的(而不是通过测试);
  • 模块化:函数式编程推崇简单原则,一个函数只做一件事情,将大的功能拆分成尽可能小的模块;小的函数更易于阅读和检查错误;
  • 组件化:小的函数更容易加以组合形成新的功能;
  • 易于调试:细化的、定义清晰的函数使得调试更加简单;当程序不正常运行时,每一个函数都是检查数据是否正确的接口,能更快速地排除没有问题的代码,定位到出现问题的地方;
  • 易于测试:不依赖于系统状态的函数无须在测试前构造测试桩,使得编写单元测试更加容易;
  • 更高的生产率:函数式编程产生的代码比其他技术更少(往往是其他技术的一半左右),并且更容易阅读和维护;

~ 高阶函数

既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数; 编写高阶函数,就是让函数的参数能够接收别的函数;

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回;

(1)变量可以指向函数

>~ abs(-10)                   //abs(-10)是函数调用
10
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ abs                        //abs是函数本身
<built-in function abs>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ x = abs(-10)               //把函数调用结果赋值给变量 
>~ x
10
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ f = abs                    //把函数本身赋值给变量,即:变量可以指向函数
>~ f
<built-in function abs>        //变量f现在已经指向了abs函数本身;直接调用abs()函数和调用变量f()完全相同
>~ f(-10)
10

(2)函数名也是变量

函数名就是指向函数的变量;对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数;

如果把abs指向其他对象:

>~ abs = 10                      
>~ abs(-10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable

把abs指向10后,就无法通过abs(-10)调用该函数了!因为abs这个变量已经不指向求绝对值函数而是指向一个整数10;
当然实际代码绝对不能这么写,这里是为了说明函数名也是变量;要恢复abs函数,请重启Python交互环境;

  • 注:由于abs函数实际上是定义在 import builtins 模块中的,所以要让修改abs变量的指向在其它模块也生效,要用 import builtins; builtins.abs = 10

(3)传入函数

Eg:

def add(x, y, f):         //调用add(-5, 6, abs)时,参数x,y和f分别接收-56和abs
    return f(x) + f(y)    //结果返回11

(4)高阶函数map/reduce函数

  • map()函数接收两个参数:一个是函数f(x),一个是Iterable;
    map将传入的函数f依次作用到序列的每个元素,并把结果作为新的Iterator返回;
>~ def f(x):                               //函数f(x)=x^2
...     return x * x
...
>~ r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])   //map()传入的第一个参数是f,即函数对象本身;
>~ list(r)                                   //结果r是一个Iterator,Iterator是惰性序列,通过list()函数让它把整个序列都计算出来并返回一个list
[1, 4, 9, 16, 25, 36, 49, 64, 81]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))  //把list所有数字转为字符串
['1', '2', '3', '4', '5', '6', '7', '8', '9']
  • reduce()函数接收两个参数:一个是函数f(x,y),一个是Iterable;
    把一个函数f作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数x,yreduce把结果继续和序列的下一个元素做累积计算,其效果就是: reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
>~ from functools import reduce     //Eg1:把序列[1, 3, 5, 7, 9]变换成int:13579
>~ def fn(x, y):                  
...     return x * 10 + y
...
>~ reduce(fn, [1, 3, 5, 7, 9])
13579
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ from functools import reduce     //Eg2:把字符串str:‘13579’ 变换成 int:13579
>~ def fn(x, y):
...     return x * 10 + y
...
>~ def char2num(s):
...     digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
...     return digits[s]
...
>~ reduce(fn, map(char2num, '13579'))   //先用map调用函数char2num 将字符串‘13579’ 变换成序列[1, 3, 5, 7, 9],再reduce 变换成int 
13579
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//将交互模式下的输入步骤 整理成一个str2int的函数:
from functools import reduce           //引入reduce

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}  //定义常量list

def str2int(s):                          //自定义函数str2int(s)
    def fn(x, y):                        //自定义函数里面再自定义一个函数fn
        return x * 10 + y
    def char2num(s):                     //自定义函数里面再自定义一个函数char2num
        return DIGITS[s]
    return reduce(fn, map(char2num, s))  //使用reduce返回结果13579
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from functools import reduce             //用lambda函数进一步简化函数

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))

(5)filter筛选

filter()函数用于过滤(筛选)序列;接收两个参数:一个是函数,一个是序列;

  • map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素;
  • filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list

练习:用filter求素数:(>1的整数 没有因数)

def _odd_iter():                     //先构造一个从3开始的奇数序列(偶数都不是素数)
    n = 1                            //注意这是一个生成器,并且是一个无限序列
    while True:
        n = n + 2
        yield n
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def _not_divisible(n):                //定义一个筛选函数,也是filter函数的第一个参数
    return lambda x: x % n > 0        //返回
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def primes():                         //定义一个生成器,不断返回下一个素数
    yield 2                           //这个生成器先返回第一个素数2,然后,利用filter()不断产生筛选后的新的序列
    it = _odd_iter()                  //初始序列
    while True:
        n = next(it)                  //返回序列的第一个数
        yield n
        it = filter(_not_divisible(n), it) //构造新序列
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        
for n in primes():                 //打印1000以内的素数:            
    if n < 1000:
        print(n)
    else:
        break                      //由于primes()也是一个无限序列,所以调用时需要设置一个退出循环的条件

(6)sorted 排序算法

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

  • sorted()函数也是一个高阶函数
  • 用sorted()排序的关键在于实现一个映射函数;
  • 它还可以接收一个**key=函数** 参数来实现自定义的排序 ;key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果(新的list列表keys)进行排序;sorted()函数按照keys进行排序之后,再按照对应关系返回list相应的元素;
>~ sorted([36, 5, -12, 9, -21])           //传入list 按大小排序
[-21, -12, 5, 9, 36]
>~ sorted([36, 5, -12, 9, -21], key=abs)  //传入两个参数:list、key=函数,按绝对值大小排序
[5, 9, -12, -21, 36]                    
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
>~ sorted(['bob', 'about', 'Zoo', 'Credit'])   //字符串排序:是按照ASCII的大小比较的,
['Credit', 'Zoo', 'about', 'bob']               //由于'Z' < 'a',结果,大写字母Z会排在小写字母a的前面
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
>~ sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)  //字符串排序,忽略大小写;给sorted传入key函数,
['about', 'bob', 'Credit', 'Zoo']                             //用一个key函数把字符串映射为忽略大小写排序即可
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]   //用一组tuple表示学生名字和成绩
def by_name(t):                                               //按名字排序
    return t[0].lower()
def by_score(t):                                              //按成绩从高到低排序
    return t[1]
L2 = sorted(L, key=by_name)
print(L2)
L2 = sorted(L, key=by_score)
print(L2)

~ 返回函数 - 把函数作为返回值

  • 高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回;
def calc_sum(*args):          //可变参数求和
    ax = 0
    for n in args:
        ax = ax + n
    return ax
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
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)   //调用lazy_sum()时,返回的并不是求和结果,而是求和函数
>~ f                             //相当于f=sum
<function lazy_sum.<locals>.sum at 0x101c6ed90>
>~ f()                           //调用函数f时,才真正计算求和的结果
25
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
>~ f1 = lazy_sum(1, 3, 5, 7, 9)
>~ f2 = lazy_sum(1, 3, 5, 7, 9)
>~ f1==f2                         //注意:当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数
False                              //f1()和f2()的调用结果互不影响

闭包Closure

在上面例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”;

注意:

  • 返回的函数并没有立刻执行,而是直到调用了f()才执行;
  • 返回闭包时:返回函数不要引用任何循环变量,或者后续会发生变化的变量

如果一定要引用循环变量怎么办?
方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变;

def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs  
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
def count():
    def f(j):
        def g():
            return j*j
        return g
    fs = []
    for i in range(1, 4):
        fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
    return fs  
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
>~ f1, f2, f3 = count()
>~ f1()
1
>~ f2()
4
>~ f3()
9  

修改前:全部都是9!原因就在于返回的函数引用了变量i,但它并非立刻执行;等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9;


~ 匿名函数lambda

匿名函数lambda x: x * x

  • 冒号前面的x表示函数参数;
  • 匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果;
  • 匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数;f = lambda x: x * x
  • 可以把匿名函数作为返回值返回;return lambda: x * x + y * y

匿名函数相当于:

def f(x):
    return x * x

~ 装饰器Decorator

装饰器Decorator:在代码运行期间动态增加功能;Eg:在函数调用前后自动打印日志,但又不希望修改now()函数的定义

  • 本质上,decorator就是一个返回函数的高阶函数;
  • 函数对象有一个name属性,可以拿到函数的名字;函数名.__name__

(1)不传入参数:两层嵌套

import functools

def log(func):                                 //定义一个能打印日志的decorator
    @functools.wraps(func)                     //wrapper.__name__ = func.__name__把原始函数的__name__等属性复制到wrapper()函数中
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)               //调用原始函数
    return wrapper
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
@log         (now = log(now))        //借助Python的@语法,把decorator置于函数的定义处
def now():
    print('2015-3-25')
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
>~ now()                          //调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志
call now():                        //now()指向了wrapper()函数
2015-3-25

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

wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用;在wrapper()函数内,首先打印日志,再紧接着调用原始函数;

(2)传入参数:三层嵌套

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)
        return wrapper
    return decorator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
@log('execute')    ( now = log('execute')(now))
def now():
    print('2015-3-25')
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
>~ now()
execute now():
2015-3-25

首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数;

以上两种decorator的定义都没有问题,但还差最后一步;因为我们讲了函数也是对象,它有name等属性,但你去看经过decorator装饰之后的函数,它们的name已经从原来的’now’变成了’wrapper’;


~ 偏函数Partial function

>~ int('12345')          //int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换
12345
>~ int('12345', base=8)  //int()函数还提供额外的base参数,默认值为10;如果传入base参数,就可以做N进制的转换
5349
>~ int('12345', 16)
74565
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def int2(x, base=2):             //定义一个int2()的函数,默认把base=2传进去,固定base值
    return int(x, base)
>~ int2('1000000')              //调用方便了
64
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ import functools
>~ int2 = functools.partial(int, base=2)   //functools.partial用来创建一个偏函数,不需要自己定义int2()
>~ int2('1000000')
64
>~ int2('1000000', base=10)           //把base参数重新设定默认值为2,也可以在函数调用时传入其他值
1000000

functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单;

创建偏函数时,实际上可以接收函数对象、*args**kw这3个参数;

max2 = functools.partial(max, 10)  //把10作为*args的一部分自动加到左边
max2(5, 6, 7)
相当于:
args = (10, 5, 6, 7)
max(*args)

Python(6)模块Module

  • 一个.py文件就称之为一个模块Module),相当于java的类;

  • 模块是一组Python代码的集合,可以使用其他模块,也可以被其他模块使用;

  • 模块存放在目录(包Package)里;

  • 每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包;__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是mycompany;

  • 可以有多级目录,组成多级层次的包结构;
    这里写图片描述
    文件www.py的模块名就是mycompany.web.www,两个文件utils.py的模块名分别是mycompany.utilsmycompany.web.utils

~ 使用模块

hello.py

#!/usr/bin/env python3                //标准注释:可以让这个hello.py文件直接在Unix/Linux/Mac上运行
# -*- coding: utf-8 -*-               //标准注释:表示.py文件本身使用标准UTF-8编码

' a test module '                     //是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;

__author__ = 'Michael Liao'           //使用__author__变量,把作者写进去

import sys                            //导入sys模块,有了变量sys指向该模块,利用这个变量,可访问sys模块的所有功能

def test():
    args = sys.argv                 //sys模块有一个argv变量,用list存储了命令行的所有参数;argv至少有一个元素,因为第一个参数永远是该.py文件的名称
                                    //运行python3 hello.py获得的sys.argv就是['hello.py'];
                                    //运行python3 hello.py Michael获得的sys.argv就是['hello.py', 'Michael]
    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()             
//当我们在命令行运行hello模块文件时,Python解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该hello模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试

~ 作用域

  • 在Python中,是通过_前缀来控制函数、变量的作用域;

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

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

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

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

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


Python(7)面向对象编程

  • 在Python中,所有数据类型都可以视为对象,当然也可以自定义对象;自定义的对象数据类型就是面向对象中的Class)的概念

  • 面向对象的设计思想是抽象出Class,根据Class创建Instance

  • 面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法;

  • 是抽象的模板,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同;

  • 在Python中,定义类是通过class关键字

~ 类和实例

  • 处理学生的成绩表:
    • 采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是Student这种数据类型应该被视为一个对象,这个对象拥有namescore这两个属性Property);如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个print_score消息,让对象自己把自己的数据打印出来;
    • 给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method);
class Student(object):                 //class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),
                                       //表示该类是从哪个类继承下来的,若没有合适的继承类,就使用object类,这是所有类最终都会继承的类;
    def __init__(self, name, score):   //通过定义__init__方法,在创建实例时,就把name,score等属性强制绑上去
        self.__name = name               //__init__方法的第一个参数永远是self,表示创建的实例本身
        self.__core = score             //有了__init__方法,创建实例时,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传

    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))

    def get_name(self):
        return self.__name
    def set_score(self, score):
        self.__score = score

bart = Student('Bart Simpson', 59)     //定义好Student类,就可根据Student类创建出Student的实例,创建实例:类名+()
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ bart = Student()    //变量bart指向的就是一个Student的实例,后面的0x10a67a590是内存地址,每个object的地址都不一样
>~ bart
<__main__.Student object at 0x10a67a590>
>~ Student                       //Student本身则是一个类
<class '__main__.Student'>

>~ bart.name = 'Bart Simpson'    //可以自由地给一个实例变量绑定属性,比如,给实例bart绑定一个name属性
>~ bart.name
'Bart Simpson'

和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数;除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数;


~ 数据封装

  • 类的方法:封装数据的函数;这些方法是和类本身关联起来的;和普通函数不同,方法可以直接访问实例的数据;

  • 要定义一个方法,除了第一个参数是self外,其他和普通函数一样;要调用一个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入;

  • 通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节;

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


~ 访问限制

  • Class内部,可以有属性方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑;

  • 注意将属性设置为private,避免外部代码自由地修改一个实例的name、score属性;

  • __xxx__:以双下划线开头,并且以双下划线结尾的变量,是特殊变量,不是private变量,可以直接访问的,

  • _name :单下划线开头的变量,外部是可以访问的,但是,按照约定俗成的规定,视为私有变量,不要随意访问;

  • __name :双下划线开头的变量 是private变量,外部不能直接访问,但是也可以访问;因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量;

    注意:

>~ bart = Student('Bart Simpson', 59)
>~ bart.get_name()
'Bart Simpson'
>~ bart.__name = 'New Name' # 设置__name变量!
>~ bart.__name
'New Name'

表面上看,外部代码“成功”地设置了__name变量,但实际上这个__name变量和class内部的__name变量不是一个变量!内部的__name变量已经被Python解释器自动改成了_Student__name,而外部代码给bart新增了一个__name变量;


~ 继承和多态

文章整理自廖雪峰官方网站:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000

猜你喜欢

转载自blog.csdn.net/qq_37546891/article/details/79360864
今日推荐