Python学习

本文介绍了Python编程中的函数使用,包括调用函数、自定义函数、参数检查、返回多个值和参数类型。重点讲解了高阶函数如map、filter和reduce的应用,特别是使用filter求素数的示例。此外,文章还提到了闭包的概念,并通过示例展示了如何创建和使用闭包。最后,提供了相关练习题,巩固读者对这些概念的理解。

# -*- coding: utf-8 -*-
# @Time    : 2020/1/21 23:04
# @Author  : Fighter.Lu

list  集合
simple=["one","two","three"] #list集合存储数据
#print(len(simple))  #list集合数据个数
#print(simple[0])  #list集合中的第一个元素u
#print(simple[-1]) #list集合中最后一个元素
#simple.append('four') #list集合在尾部添加元素
#simple.insert('3','ten')
#simple.pop() #从尾部删除元素
#simple.pop(1) #从指定位置删除元素
#simple[3]='ten' 根据下标替换list集合中元素
#s=['one','two',['three','four'],'five',simple] #list集合插入另一个list集合
#print(s)

tuple  元组
#classmate=('one','two') #初始化元素
#print(classmate)

# classmates这个tuple不能变了,它也没有append(),insert()这样的方法。其他获取元素的方法和list是一样的,你可以正常地使用classmates[0],classmates[-1],但不能赋值成另外的元素。
#定义空元组
#student=()
#print(student)
#student=(1)
#student[0]='zhangsan'
#print(student)
# L = [
#     ['Apple', 'Google', 'Microsoft'],
#     ['Java', 'Python', 'Ruby', 'PHP'],
#     ['Adam', 'Bart', 'Lisa']
# ]
# print(L[0][0])
# print(L[1][1])
# print(L[2][2]) BMI = 體重(公斤) / 身高2(厘米)

#用户输入
#s=input("请输入您的年龄:")
#字符串转整型
# s=int(s)
# if s>20:
#     print("你是成年人")
# elif s<5:
#     print(s)
# else:
#     print()
#print(55/(1.67*1.67))

#for遍历
# for i in simple:
#
#     print('集合:'+i)
# sum=0
#range 函数生成序列  range(4)=[0,1,2,3]
# for i in range(101):
#     sum=sum+i;
# print(sum)
#dict 字典 类似于 map
scoreinfo={'zhangsan':11,'lisi':33,'wangwu':33}
#scoreinfo["zhaoliu"]=22 #如果key存在就修改 如果不存在就添加
#print(scoreinfo['zhangsan']) #查询某个key -value
#scoreinfo['zhangsan']=111 #修改字典scoreinfo中key为zhangsan的value
#print(scoreinfo['zhangsan'])
#print('zhangsan' in scoreinfo) #判断key在字典中是否存在

#通过dict提供的get方法判断key在集合中是否存在;不存在就返回none,否则返回value
#print(scoreinfo.get("zhangsan"))

#通过下标判断
#print(scoreinfo.get("lisi",-1))
#删除字典scoreinfo中key
#scoreinfo.pop("zhangsan")
#print(scoreinfo)

#list和字典dict区别
#1、dict查询和插入的速度很快,不会随着key增多而变慢;需要占用大量的内存,内存浪费严重
#2、list查询和插入随着集合的元素添加,速度变慢


set和dict很相似,也是一组key的集合,只存储key的集合,set不能重复key存在,如果有重复set会自动剔除
id=set([1,2,3])
print(id)
#  id=set({1,11,2,3,2,3})
# print(id)
# {3, 1, 2, 11}
#set 无序集合

#set 添加元素
id.add(4)
print(id)
#如果id重复添加相同的key,不会有效果
# id
{3, 1, 2, 11}
# id.add(3)
# id
#{3, 1, 2, 11}
#id 删除key
#id.remove(3)
#>>> id
#{1, 2, 11}

#查询两个set集合重叠的元素
# id
# {1, 2, 11}
# >>> id1
# {3, 4, 5, 6, 7}
# >>> id &id1
# set()
# >>> id |id1
# {1, 2, 3, 4, 5, 6, 7, 11}
# >>>

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

函数

  1、调用函数

      1.1  取正数:abs(x)  注意:只能有一个参数,且参数只能是数字

     1.2   取最大值:max(x,y,z) 可以多个参数,参数只能是数字

      1.3  整型转化:int('222') 只能是数字类型的字符串   (int('222')、str(22))    

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

2、自定义函数

   使用关键字:def

   例如:

def my_abs(x):
    if x >= 0:
        return x
    else:
        return -x


调用函数:my_abs(-1)  结果:1

注意:函数中如果没有加返回值,显示none

3、空函数:当你暂时不知道写什么,可以在函数里面加入pass,这表示什么都不做

def show():

pass

注意:如果函数里面没东西,同时也没加pass,会报错

4、参数检查

调用函数时,如果参数个数不对,Python解释器会自动检查出来,并抛出TypeError

>> my_abs(1, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: my_abs() takes 1 positional argument but 2 were given

   参数类型不对:

def my_abs(x):
    if not isinstance(x, (int, float)):
        raise TypeError('bad operand type')
    if x >= 0:
        return x
    else:
        return -x

my_abs('A')  :参数类型错误,就会出错误

>> my_abs('A')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in my_abs
TypeError: bad operand type

5、返回参数多个值

import math

def move(x, y, step, angle=0):
    nx = x + step * math.cos(angle)
    ny = y - step * math.sin(angle)
    return nx, ny

r=move(100,100,60,math.pi/6)
 print(r)
(151.96152422706632, 70.0)

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

请定义一个函数quadratic(a, b, c),接收3个参数,返回一元二次方程 ax^2+bx+c=0ax2+bx+c=0 的两个解。

 def quadratic(a,b,c):
    return (-b+math.sqrt(b*b-4*a*c))/2*a,(-b-math.sqrt(b*b-4*a*c))/2*a

6、函数的参数

     6.1、位置参数:传入有且只有一个参数

计算x平方的函数
def power(x):
    return x*x

power(2)
传入多个参数:立方、平方 x^2 x^3
def power(x,n):
    s=1
    while n>0:
        n=n-1
        s=s*x
    return s

power(2,3)


   6.2、默认参数:函数中有默认参数,传入的参数被函数中默认的参数代替

def power(x, n=2):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s

power(5) -->25
power(5,3) -->25

   6.3、可变参数:参数可传入集合、元组

计算a^2+b^2+c^3

def calc(numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum

>>> calc([1, 2, 3])
14
>>> calc((1, 3, 5, 7))
84

>>> calc(1, 2, 3)
14
>>> calc(1, 3, 5, 7)
84

定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*号。在函数内部,参数numbers接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数:

>>> calc(1, 2)
5
>>> calc()
0

如果已经有一个list或者tuple,要调用一个可变参数怎么办?可以这样做:

>>> nums = [1, 2, 3]
>>> calc(nums[0], nums[1], nums[2])
14

这种写法当然是可行的,问题是太繁琐,所以Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去:

>>> nums = [1, 2, 3]
>>> calc(*nums)
14

关键字参数

在对用户注册的过程中,在对参数个数的控制情况下,传入多个属性值,就会用到字典

示例1:

def 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'}
>>> person('Jack', 24, city=extra['city'], job=extra['job'])
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}

高级特性

  •   切片:更好的取list、tuple、dict、set中数据
  •   格式:[start : end : step]

        Start:起始索引,从0开始,-1表示结束

        End:结束索引

        Step:步长

        end-start=正数时,从左向右取值,=负数时反向取值

        注意:切片结果不包含结束索引,即不包含最后一位,-1代表最后一个位置索引

  • 常用的几种方式:

 [:] 如:list2=list1[:] 全部截取

 [0:1:n] 如:list1[0:3;1] 从0开始到3每次增加1截取,不包含索引结束位置

 [0:-1:1]:从0开始到结束,每次增加1,截取不包含索引结束位置

 [:3]:默认从起始位置索引,每次增加1截取,结束位置索引为3

 [3:0:-1]反向取值,每次增加1截取,不包含索引结束位置

  • 例如:
L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
1、取list集合数据
    1、1:没用切边取list集合数据
        >>> [L[0], L[1], L[2]]
        ['Michael', 'Sarah', 'Tracy']
    1.2、:使用切片取list集合数据
        >>> L[0:3]
        ['Michael', 'Sarah', 'Tracy']

        L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3。即索引0,1,2,正好是3个元                        
        素。
        如果第一个索引是0,还可以省略:

        >>> L[:3]
        ['Michael', 'Sarah', 'Tracy']

        也可以从索引1开始,取出2个元素出来:
        >>> L[1:3]
        ['Sarah', 'Tracy']
        类似的,既然Python支持L[-1]取倒数第一个元素,那么它同样支持倒数切片,试试:
        >>> L[-2:]
        ['Bob', 'Jack']
        >>> L[-2:-1]
        ['Bob']
        记住倒数第一个元素的索引是-1。

        切片操作十分有用。我们先创建一个0-99的数列:
        >>> L = list(range(100))
        >>> L
        [0, 1, 2, 3, ..., 99]
        可以通过切片轻松取出某一段数列。比如前10个数:

        >>> L[:10]
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
        后10个数:

        >>> L[-10:]
        [90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
        前11-20个数:

        >>> L[10:20]
        [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
        前10个数,每两个取一个:

        >>> L[:10:2]
        [0, 2, 4, 6, 8]
        所有数,每5个取一个:

        >>> L[::5]
        [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
        甚至什么都不写,只写[:]就可以原样复制一个list:

        >>> L[:]
        [0, 1, 2, 3, ..., 99]
        tuple也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作,只是操作的结果                仍是tuple:

        >>> (0, 1, 2, 3, 4, 5)[:3]
        (0, 1, 2)
        字符串'xxx'也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,        只是操作结果仍是字符串:

        >>> 'ABCDEFG'[:3]
        'ABC'
        >>> 'ABCDEFG'[::2]
        'ACEG'
  • 案例:
利用切片操作,实现一个trim()函数,去除字符串首尾的空格,注意不要调用str的strip()方法:
def trim(s):
    if s[:1] != ' ' and s[-1:] != ' ':
        return s
    elif s[:1] == ' ':
        return trim(s[1:])
    else:
        return trim(s[:-1])

迭代

  • 如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。
使用迭代遍历list集合
for i in list:
    print(i)

使用迭代遍历dict字典
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> for key in d:
     print(key)
a
c
b

使用迭代遍历字符串
>>> for ch in 'ABC':
     print(ch)

A
B
C
判断对象是否是可迭代的对象

>>> from collections import 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循环里,同时引用了两个变量,在Python里是很常见的,比如下面的代码:

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

请使用迭代查找一个list中最小和最大值,并返回一个tuple:
def findMinAndMax(L):
     max = L[0]
     for i in L:
             if i >max:
                     max=i
     min=L[0]
     for i in L:
             if i<min:
                     min=i
     return (max,min)
print(findMinAndMax([3,2,1,4,2,5,6,77,44]))

列表生成式

  • 概念:即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。
  • 实例:
要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用list(range(1, 11)):

>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

但如果要生成[1x1, 2x2, 3x3, ..., 10x10]
方法一是循环:
>>> L = []
>>> for x in range(1, 11):
...    L.append(x * x)
...
>>> L
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list:

>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来

for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:

>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]

还可以使用两层循环,可以生成全排列:

>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

三层和三层以上的循环就很少用到了。

运用列表生成式,可以写出非常简洁的代码。例如,列出当前目录下的所有文件和目录名,可以通过一行代码实现:

>>> import os # 导入os模块,模块的概念后面讲到
>>> [d for d in os.listdir('.')] # os.listdir可以列出文件和目录
['.emacs.d', '.ssh', '.Trash', 'Adlm', 'Applications', 'Desktop', 'Documents', 'Downloads', 'Library', 'Movies', 'Music', 'Pictures', 'Public', 'VirtualBox VMs', 'Workspace', 'XCode']

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

因此,列表生成式也可以使用两个变量来生成list:

>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']
最后把一个list中所有的字符串变成小写:

>>> L = ['Hello', 'World', 'IBM', 'Apple']
>>> [s.lower() for s in L]
['hello', 'world', 'ibm', 'apple']

如果list中既包含字符串,又包含整数,由于非字符串类型没有lower()方法,所以列表生成式会报错:

>>> [s.lower() for s in L if(isinstance(s,str))]
['hello', 'world', 'apple']

生成器

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

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>

创建Lg的区别仅在于最外层的[]()L是一个list,而g是一个generator。

我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?

如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值:

>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

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

当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

>>> g = (x * x for x in range(10))
>>> for n in g:
...     print(n)
... 
0
1
4
9
16
25
36
49
64
81

所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过for循环来迭代它,并且不需要关心StopIteration的错误。

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

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'

注意,赋值语句:

a, b = b, a + b
相当于:

t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]
但不必显式写出临时变量t就可以赋值。

上面的函数可以输出斐波那契数列的前N个数:

>>> fib(6)
1
1
2
3
5
8
'done'

仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

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

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

for i in fib(3):
    print(i)

这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

举个简单的例子,定义一个generator,依次返回数字1,3,5:

def odd():
    print('step 1')
    yield 1
    print('step 2')
    yield(3)
    print('step 3')
    yield(5)

for i in odd():
    print(i)

调用该generator时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值:

>>> o = odd()
>>> next(o)
step 1
1
>>> next(o)
step 2
3
>>> next(o)
step 3
5
>>> next(o)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration


可以看到,odd不是普通函数,而是generator,在执行过程中,遇到yield就中断,下次又继续执行。执行3次yield后,已经没有yield可以执行了,所以,第4次调用next(o)就报错。

回到fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。

同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:


但是用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

案例:杨辉三角

n = 0
results = []
def triangle():
    N = [1]
    while True:
        yield N     #generator特点在于:在执行过程中,遇到yield就中断,下次又继续执行
        N.append(0)  #每次都要在最后一位加个0,用于后续的叠加
        N = [N[i]+N[i-1] for i in range(len(N))]


for t in triangle():
    results.append(t)
    n = n + 1
    if n == 10:
        break

for t in results:
    print(t)

迭代器

我们已经知道,可以直接作用于for循环的数据类型有以下几种:

  • 一类是集合数据类型,如listtupledictsetstr等;
  • 一类是generator,包括生成器和带yield的generator function。

    这些可以直接作用于for循环的对象统称为可迭代对象:Iterable

    可以使用isinstance()判断一个对象是否是Iterable对象:

>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False

而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。

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

可以使用isinstance()判断一个对象是否是Iterator对象:

>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False

生成器都是Iterator对象,但listdictstr虽然是Iterable,却不是Iterator

listdictstrIterable变成Iterator可以使用iter()函数:

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

你可能会问,为什么listdictstr等数据类型不是Iterator

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

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

小结

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

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

集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

Python的for循环本质上就是通过不断调用next()函数实现的,例如:

for x in [1, 2, 3, 4, 5]:
    pass

等价于:

# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
    try:
        # 获得下一个值:
        x = next(it)
    except StopIteration:
        # 遇到StopIteration就退出循环
        break

 函数式编程

  • 高阶函数
  1.           map/reduce:MapReduce是一个基于集群的计算平台,是一个简化分布式编程的计算框架,是一个将分布式计算抽象为Map和Reduce两个阶段的编程模型

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

举例说明,比如我们有一个函数f(x)=x2,要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]上,就可以用map()实现如下:

>>> def f(x):
...     return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]

map()传入的第一个参数是f,即函数对象本身。由于结果r是一个IteratorIterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。

你可能会想,不需要map()函数,写一个循环,也可以计算出结果:

L = []
for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
    L.append(f(n))
print(L)

的确可以,但是,从上面的循环代码,能一眼看明白“把f(x)作用在list的每一个元素并把结果生成一个新的list”吗?

所以,map()作为高阶函数,事实上它把运算规则抽象了,因此,我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数,比如,把这个list所有数字转为字符串:

>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']

只需要一行代码。

再看reduce的用法。reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

比方说对一个序列求和,就可以用reduce实现:

>>> from functools import reduce
>>> def add(x, y):
...     return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25

当然求和运算可以直接用Python内建函数sum(),没必要动用reduce

但是如果要把序列[1, 3, 5, 7, 9]变换成整数13579reduce就可以派上用场:

>>> from functools import reduce
>>> def fn(x, y):
...     return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579

这个例子本身没多大用处,但是,如果考虑到字符串str也是一个序列,对上面的例子稍加改动,配合map(),我们就可以写出把str转换为int的函数:

>>> from functools import reduce
>>> 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'))
13579

整理成一个str2int的函数就是:

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 str2int(s):
    def fn(x, y):
        return x * 10 + y
    def char2num(s):
        return DIGITS[s]
    return reduce(fn, map(char2num, s))

还可以用lambda函数进一步简化成:

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

 

案例:

Python提供的sum()函数可以接受一个list并求和,请编写一个prod()函数,可以接受一个list并利用reduce()求积:

>>> def fn(x,y):
...     return x*y
...
>>> from functools import reduce
>>> reduce(fn,map([1,3,5,9]))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: map() must have at least two arguments.
>>> reduce(fn,[1,3,5,9])
135
>>>

利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:['adam', 'LISA', 'barT'],输出:['Adam', 'Lisa', 'Bart']

def UpperToLower(L):
    L=L[0].upper()+L[1:].lower()
    return L
L=["one","TWO"]
print(list(map(UpperToLower,L)))

2、filter

Python内建的filter()函数用于过滤序列。

map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

例如,在一个list中,删掉偶数,只保留奇数,可以这么写:

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

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

把一个序列中的空字符串删掉,可以这么写:

def not_empty(s):
    return s and s.strip()

list(filter(not_empty, ['A', '', 'B', None, 'C', '  ']))
# 结果: ['A', 'B', 'C']

可见用filter()这个高阶函数,关键在于正确实现一个“筛选”函数。

注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list。

用filter求素数

计算素数的一个方法是埃氏筛法,它的算法理解起来非常简单:

首先,列出从2开始的所有自然数,构造一个序列:

2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...

取序列的第一个数2,它一定是素数,然后用2把序列的2的倍数筛掉:

3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...

取新序列的第一个数3,它一定是素数,然后用3把序列的3的倍数筛掉:

5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...

取新序列的第一个数5,然后用5把序列的5的倍数筛掉:

7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...

不断筛下去,就可以得到所有的素数。

用Python来实现这个算法,可以先构造一个从3开始的奇数序列:

def _odd_iter():
    n = 1
    while True:
        n = n + 2
        yield n

注意这是一个生成器,并且是一个无限序列。

然后定义一个筛选函数:

def _not_divisible(n):
    return lambda x: x % n > 0

最后,定义一个生成器,不断返回下一个素数:

def primes():
    yield 2
    it = _odd_iter() # 初始序列
    while True:
        n = next(it) # 返回序列的第一个数
        yield n
        it = filter(_not_divisible(n), it) # 构造新序列

这个生成器先返回第一个素数2,然后,利用filter()不断产生筛选后的新的序列。

由于primes()也是一个无限序列,所以调用时需要设置一个退出循环的条件:

# 打印1000以内的素数:
for n in primes():
    if n < 1000:
        print(n)
    else:
        break

注意到Iterator是惰性计算的序列,所以我们可以用Python表示“全体自然数”,“全体素数”这样的序列,而代码非常简洁。

练习

回数是指从左向右读和从右向左读都是一样的数,例如12321909。请利用filter()筛选出回数:

 def is_palindrome(n):
     nn = str(n)
     return nn==nn[::-1]

>>> list(filter(is_palindrome,range(1,1000)))
[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, 202, 212, 222, 232, 242, 252, 262, 272, 282, 292, 303, 313, 323, 333, 343, 353, 363, 373, 383, 393, 404, 414, 424, 434, 444, 454, 464, 474, 484, 494, 505, 515, 525, 535, 545, 555, 565, 575, 585, 595, 606, 616, 626, 636, 646, 656, 666, 676, 686, 696, 707, 717, 727, 737, 747, 757, 767, 777, 787, 797, 808, 818, 828, 838, 848, 858, 868, 878, 888, 898, 909, 919, 929, 939, 949, 959, 969, 979, 989, 999]
>>>

 

 

filter

阅读: 24179215


Python内建的filter()函数用于过滤序列。

map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

例如,在一个list中,删掉偶数,只保留奇数,可以这么写:

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

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

把一个序列中的空字符串删掉,可以这么写:

def not_empty(s):
    return s and s.strip()

list(filter(not_empty, ['A', '', 'B', None, 'C', '  ']))
# 结果: ['A', 'B', 'C']

可见用filter()这个高阶函数,关键在于正确实现一个“筛选”函数。

注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list。

用filter求素数

计算素数的一个方法是埃氏筛法,它的算法理解起来非常简单:

首先,列出从2开始的所有自然数,构造一个序列:

2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...

取序列的第一个数2,它一定是素数,然后用2把序列的2的倍数筛掉:

3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...

取新序列的第一个数3,它一定是素数,然后用3把序列的3的倍数筛掉:

5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...

取新序列的第一个数5,然后用5把序列的5的倍数筛掉:

7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...

不断筛下去,就可以得到所有的素数。

用Python来实现这个算法,可以先构造一个从3开始的奇数序列:

def _odd_iter():
    n = 1
    while True:
        n = n + 2
        yield n

注意这是一个生成器,并且是一个无限序列。

然后定义一个筛选函数:

def _not_divisible(n):
    return lambda x: x % n > 0

最后,定义一个生成器,不断返回下一个素数:

def primes():
    yield 2
    it = _odd_iter() # 初始序列
    while True:
        n = next(it) # 返回序列的第一个数
        yield n
        it = filter(_not_divisible(n), it) # 构造新序列

这个生成器先返回第一个素数2,然后,利用filter()不断产生筛选后的新的序列。

由于primes()也是一个无限序列,所以调用时需要设置一个退出循环的条件:

# 打印1000以内的素数:
for n in primes():
    if n < 1000:
        print(n)
    else:
        break

注意到Iterator是惰性计算的序列,所以我们可以用Python表示“全体自然数”,“全体素数”这样的序列,而代码非常简洁。

练习

回数是指从左向右读和从右向左读都是一样的数,例如12321909。请利用filter()筛选出回数:

# -*- coding: utf-8 -*-
# 测试:
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('测试失败!')

 Run

小结

filter()的作用是从一个序列中筛出符合条件的元素。由于filter()使用了惰性计算,所以只有在取filter()结果的时候,才会真正筛选并每次返回下一个筛出的元素。

 

3、sorted

排序算法

排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但如果是字符串或者两个dict呢?直接比较数学上的大小是没有意义的,因此,比较的过程必须通过函数抽象出来。

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

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

此外,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序:

>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]

key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。对比原始的list和经过key=abs处理过的list:

list = [36, 5, -12, 9, -21]

keys = [36, 5,  12, 9,  21]

然后sorted()函数按照keys进行排序,并按照对应关系返回list相应的元素:

keys排序结果 => [5, 9,  12,  21, 36]
                |  |    |    |   |
最终结果     => [5, 9, -12, -21, 36]

我们再看一个字符串排序的例子:

>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']

默认情况下,对字符串排序,是按照ASCII的大小比较的,由于'Z' < 'a',结果,大写字母Z会排在小写字母a的前面。

现在,我们提出排序应该忽略大小写,按照字母序排序。要实现这个算法,不必对现有代码大加改动,只要我们能用一个key函数把字符串映射为忽略大小写排序即可。忽略大小写来比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),再比较。

这样,我们给sorted传入key函数,即可实现忽略大小写的排序:

>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']

要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True

>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']

从上述例子可以看出,高阶函数的抽象能力是非常强大的,而且,核心代码可以保持得非常简洁。

小结

sorted()也是一个高阶函数。用sorted()排序的关键在于实现一个映射函数。

 

4、返回函数

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

例如:求和函数

def calc_sum(*args){
 ax=0
 for x in args:
    ax=sx+x
 return ax;

}

但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数:

>>> def lazy_sum(*args):
...     def calc_sum():
...             ax=0
...             for n in args:
...                     ax=ax+n
...             return ax
...     return calc_sum
...
>>> f=lazy_sum(1,3,5,7,9)
>>> f()
25

5、闭包

含义:注意到返回的函数在其定义内部引用了局部变量args,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。

另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。

我们来看一个例子:

>>> def count():
...     fs=[]
...     for i in range(1,4):
...             def f():
...                     return i*i
...             fs.append(f)
...     return fs
...

>>> f1,f2,f3=count()
>>> f1()
9

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

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

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

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

缺点是代码较长,可利用lambda函数缩短代码。

练习

利用闭包返回一个计数器函数,每次调用它返回递增整数:

>>> s=0
>>> def createCount():
...     def count():
...             global s
...             s=s+1
...             return s
...     return count
...
>>> counta=createCount()
>>> counta()
1
>>> counta()
2
>>>
>>> counta()
3

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值