当前位置: 首页 > news >正文

Python-函数、参数及参数解构-返回值作用域-递归函数-匿名函数-生成器-学习笔记

        欠4年前的自己一份笔记,献给今后的自己 。

函数、参数及参数解构

函数

  • 函数
            数学定义:y=f(x),y是x的函数,x是自变量
            Python函数
            由若干语句组成的语句块、函数名称、参数列表构成,它是组织代码的最小单元
            完成一定的功能

  • 函数的作用

  • 函数
            结构化编程对代码的最基本的封装,一般按照功能组织一段代码口封装的目的为了复用,减少冗余代码
            代码更加简洁美观、可读易懂

  • 函数的分类

  • 函数
            内建函数,如max()、reversed()等

  • 函数
            库函数,如math.ceil()等

函数定义、调用

  • def语句定义函数

  • def 函数名(参数列表):
    函数体(代码块)
    [return 返回值]
            函数名就是标识符,命名要求一样
            语句块必须缩进,约定4个空格
            Python的函数没有return语句,隐式会返回一个None值
            定义中的参数列表成为形式参数,只是一种符号表达,简称形参

  • 调用
            函数定义,只是声明了一个函数,它不会被执行,需要调用
            调用的方式,就是函数名加上小括号,括号内写上参数
            调用时写的参数是实际参数,是实实在在传入的值,简称实参

  • 函数举例

def add(x, y):result = x + yreturn resultout = add(4, 5)
print(out)                  # 9 
  • 上面只是一个函数的定义,有一个函数叫做add,接收2个参数口计算的结果,通过返回值返回

  • 调用通过函数名add加2个参数,返回值可使用变量接收

  • 定义需要在调用前,也就是说调用时,已经被定义过了,否则抛NameError异常口函数是可调用的对象,callable()

  • 看看这个函数是不是通用的?体会一下函数的好处

函数参数

参数调用时传入的参数要和定义的个数相匹配(可变参数例外)

  • 位置参数
    def f(x, y, z)调用使用f(1,3,5)
    按照参数定义顺序传入实参

  • 关键字参数
    def f(x, y, z) 调用使用 f(x=1, y =3, z=5)

  • 使用形参的名字来出入实参的方式,如果使用了形参名字,那么传参顺序就可和定义顺序不同

  • 传参
    f(z= None, y =10, x=[1])
    f((1,), z= 6, y =4.1)
    f(y =5, z= 6, 2) #

  • 要求位置参数必须在关键字参数之前传入,位置参数是按位置对应的

函数参数默认值

  • 参数默认值

         定义时,在形参后跟上一个值

def add (x=4,y=5):return x+y

        测试调用 add(6, 10)、add(6,y=7)、add(×=5) add()。 add (y=7)。 add(x=5, 6), add(y =8, 4)。 add (x=5, y =6), add (y = 5, x=6)

测试 def add (x=4,y)

  • 作用
    参数的默认值可以在未传入足够的实参的时候,对没有给定的参数赋值为默认值
    参数非常多的时候,并不需要用户每次都输入所有的参数,简化函数调用

  • 举例
    定义一个函数login,参数名称 host、port、 username、password

def login(host='127.0.0.1',port='8080',username='wayne',password ='magedu'):print('{}:{}@{}/{}'.format(host, port, username, password))login()                     # 127.0.0.1:8080@wayne/magedulogin ('127.0.0.1', 80, 'tom', 'tom')       # 127.0.0.1:80@tom/tomlogin('127.0.0.1', username='root')     # 127.0.0.1:8080@root/magedulogin('localhost', port= 80, password = 'com')      # localhost:80@wayne/comlogin(port=80, password = 'magedu', host= 'www')        #www:80@wayne/magedu

可变参数

  • 问题
    有多个数,需要累加求和
def add(nums):sum = 0for x in nums:sum += xreturn sumprint(add([1, 3, 5]))  # 9 
print(add((2, 4, 6)))  # 12

        传入一个可迭代对象,迭代元素求和

  • 可变参数
    一个形参可以匹配任意个参数
  • 位置参数的可变参数
    有多个数,需要累加求和
def add(*nums):sum = 0print(type(nums))       # <class 'tuple'>for x in nums:sum += xprint(sum)add(3, 6, 9)  # 18
  • 在形参前使用*表示该形参是可变参数, 可以接收多个实参

  • 收集多个实参为一个tuple

  • 思考:关键字参数能否也能传递任意多个吗?

  • 关键字参数的可变参数
    配置信息打印

def showconfig(**kwargs):for k, v in kwargs.items():print('{}={}'.format(k, v))showconfig(host='127.0.0.1', port='8080',username='wayne', password = 'magedu')# host=127.0.0.1
# port=8080
# username=wayne
# password=magedu
  • 形参前使用**符号,表示可以接收多个关键字参数

  • 收集的实参名称和值组成一个字典

  • 可变参数混合使用
    配置信息打印

def showconfig(username, password, **kwargs):print(username, username, kwargs)def showconfig(username, *args, **kwargs):print(username, args, kwargs)# 下面代码会报错
def showconfig(username, password, **kwargs, *args):print(username, password, kwargs, args)
  • 总结
    1、有位置可变参数和关键字可变参数
    2、位置可变参数在形参前使用一个星号*
    3、关键字可变参数在形参前使用两个星号** 浅院
    4、位置可变参数和关键字可变参数都可以收集若干个实参,位置可变参数收集形成一个tuple,关键字可变参数收集形成一个dict
    5、混合使用参数的时候,可变参数要放到参数列表的最后,普通参数需要放到参数列表前面,位置可变参数需要在关键字可变参数之前

  • 举例

def fn(x, y, *args, **kwargs):print(x)print(y)print(args)print(kwargs)fn(3, 5, 7, 9, 10, a=1, b='python')
print('*'*60)
fn(3, 5)
print('-'*60)fn(3, 5, 7)
print('~'*60)fn(3, 5, a=1, b='python')
print('|'*60)# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 18, in <module>
#     fn(7, 9, y=5, x=3, a=1, b='python')
#     ~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# TypeError: fn() got multiple values for argument 'y'
fn(7, 9, y=5, x=3, a=1, b='python')  # 错误,7和9分别赋给了x,y,又y=5、X=3,重复了输出:
3
5
(7, 9, 10)
{'a': 1, 'b': 'python'}
************************************************************
3
5
()
{}
------------------------------------------------------------
3
5
(7,)
{}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3
5
()
{'a': 1, 'b': 'python'}
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Traceback (most recent call last):File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 27, in <module>fn(7, 9, y=5, x=3, a=1, b='python')~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: fn() got multiple values for argument 'y'

举例

def fn(*args, x, y, **kwargs):print(x)print(y)print(args)print(kwargs)# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 11, in <module>
#     fn(3, 5)
#     ~~^^^^^^
# TypeError: fn() missing 2 required keyword-only arguments: 'x' and 'y'
# fn(3, 5)# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 18, in <module>
#     fn(3, 5, 7)
#     ~~^^^^^^^^^
# TypeError: fn() missing 2 required keyword-only arguments: 'x' and 'y'
# fn(3, 5, 7)# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 26, in <module>
#     fn(3, 5, a=1, b='python')
#     ~~^^^^^^^^^^^^^^^^^^^^^^^
# TypeError: fn() missing 2 required keyword-only arguments: 'x' and 'y'
# fn(3, 5, a=1, b='python')fn(7, 9, y=5, x=3, a=1, b='python')# 输出:
# 3
# 5
# (7, 9)
# {'a': 1, 'b': 'python'}

keyword-only参数

  • keyword-only参数(Python3加入)
    如果在一个星号参数后,或者一个位置可变参数后,出现的普通参数,实际上已经不是普通的参数了,而是keyword-only参数
def fn(*args, x):print(x)print(args)# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 7, in <module>
#     fn (3,5)
#     ~~~^^^^^
# TypeError: fn() missing 1 required keyword-only argument: 'x'
# fn(3, 5)# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 14, in <module>
#     fn(3, 5, 7)
#     ~~^^^^^^^^^
# TypeError: fn() missing 1 required keyword-only argument: 'x'
# fn(3, 5, 7)fn(3, 5, x=7)# 输出:
# 7
# (3, 5)
  • 举例
def(**kwargs, x):print(x)print(kwargs)

直接报语法错误
可以理解为kwargs会截获所有的关键字参数,就算你写了x=5,x也永远得不到这个值,所以语法

  • keyword-only 参数另一种形式
def fn(*, x, y):print(x, y)   # 5 6 fn(x=5, y=6)        

* 号之后,普通形参都变成了必须给出的keyword-Only 参数

可变参数和参数默认值

  • 举例一
def fn(*args, x=5):print(x)print(args)fn()  # 等价于fn(x=5) fn(5)print('*' * 30 )
fn(x=6)
print('~' * 30 )
fn(1, 2, 3, x=10)
输出:
5
()
******************************
6
()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
10
(1, 2, 3)
  • 举例二
def fn(y, *args, x=5):print('x={}, y ={}'.format(x, y))print(args)# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 6, in <module>
#     fn()
#     ~~^^
# TypeError: fn() missing 1 required positional argument: 'y'
# fn()fn(5)
print('|'*30)
# 输出:
# x=5, y =5
# ()# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 16, in <module>
#     fn(x=6)  #
#     ~~^^^^^
# TypeError: fn() missing 1 required positional argument: 'y'
# fn(x=6)  #fn(1, 2, 3, x=10)
# 输出:
# x=10, y =1
# (2, 3)print('~' * 30 )
# 编译报错
# fn(y=17, 2, 3, x=10)  #print('*'* 30 )
# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 42, in <module>
#     fn(1, 2, y=3, x=10)  #
#     ~~^^^^^^^^^^^^^^^^^
# TypeError: fn() got multiple values for argument 'y'
fn(1, 2, y=3, x=10)  #
  • 举例三
def fn(x=5, **kwargs):print('x=0'.format(x))print(kwargs)fn()print('*'*60)
fn(5)
print('-'*60)fn(x=6)  #
print('!'*60)fn(y=3, x=10)
print('~'*60)fn(3, y=10)输出:x=0
{}
************************************************************
x=0
{}
------------------------------------------------------------
x=0
{}
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
x=0
{'y': 3}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
x=0
{'y': 10}

函数参数

  • 参数规则
    参数列表参数一般顺序是,普通参数、缺省参数、可变位置参数、keyword-only参数(可带缺省值)、可变关键字参数
    在这里插入图片描述

  • 注意
    代码应该易读易懂,而不是为难别人
    请按照书写习惯定义函数参数

  • 参数规则举例
    参数列表参数一般顺序是,普通参数、缺省参数、可变位置参数、keyword-only参数(可带缺省值)、可变关键字参数

def connect(host='localhost', port='3306', user='admin', password='admin', **kwargs):print(host, port)print(user, password)print(kwargs)connect(db='cmdb')print('*'*60)connect(host='192.168.1.123', db='cmdb')print('~'*60)connect(host='192.168.1.123', db='cmdb', password='mysql')输出:
localhost 3306
admin admin
{'db': 'cmdb'}
************************************************************
192.168.1.123 3306
admin admin
{'db': 'cmdb'}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
192.168.1.123 3306
admin mysql
{'db': 'cmdb'}

参数解构

举例

加法函数

def add(x, y):return x + yprint(add(4, 5))   # 9# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 8, in <module>
#     print(add((4, 5)))
#           ~~~^^^^^^^^
# TypeError: add() missing 1 required positional argument: 'y'
# print(add((4, 5)))t = (4, 5)print(add(t[0], t[1]))    # 9print(add(*t))          # 9
print(add(*(4, 5)))   # 9
print('*' * 30 , add(*range(1, 3)))  #  3 print(add(*[4, 5]))  # 9 print(add(*{4, 6}))  # 10 
参数解构
  • 给函数提供实参的时候,可以在集合类型前使用*或者**,把集合类型的结构解开,提取出所有元素作为函数的实参
  • 非字典类型使用 ** 解构成位置参数
  • 字典类型使用**解构成关键字参数
  • 提取出来的元素数目要和参数的要求匹配,也要和参数的类型匹配
def add(x, y):return x + yprint(add(*(4, 5)))  # 9print('*' * 30 )print(add(*[4, 5]))  # 9
print('~' * 30 )print(add(*{4, 6}))  # 10print('-' * 30 )d = {'x': 5, 'y': 6}print(add(**d))  # 11 # Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 25, in <module>
#     print(add(**{'a': 5, 'b': 6}))
#           ~~~^^^^^^^^^^^^^^^^^^^^
# TypeError: add() got an unexpected keyword argument 'a'
# print(add(**{'a': 5, 'b': 6}))
  • 参数解构和可变参数
    给函数提供实参的时候,可以在集合类型前使用*或者**,把集合类型的结构解开,提取出所有元素作为函数的实参
def add(*iterable):result = 0for x in iterable:result += xreturn resultprint(add(1, 2, 3))     # 6 print(add(*[1, 2, 3]))  # 6 print(add(*range(10)))  # 45 
练习
  • 编写一个函数,能够接受至少2个参数,返回最小值和最大值
def fn(x, y ):if x > y:return xelse :return yprint(fn(1,2))      # 2 
print(fn(3,2))      # 3 
  • 编写一个函数,接受一个参数n,n为正整数,左右两种打印方式。要求数字必须对齐

在这里插入图片描述

上三角

def triangle_print(n):for i in range(1, n + 1):for j in range(n, 0, -1):if i < j:print(' ' * len(str(j)), end=' ')  # 因为随着数字位数不同,宽度不同# ‘12 ’   ‘100 ’  ‘1000 ’else:print(j, end=' ')print()triangle_print(12)

在这里插入图片描述

下三角


def triangle_print(n):for i in range(1, n+ 1):for j in range(1, n + 1):if i > j:print(' ' * len(str(j)), end=' ')else:print(n + 1 - j, end=' ')print()triangle_print(12)

在这里插入图片描述

函数返回值、作用域

函数的返回值

  • 举例一
def showplus(x):print(x)   # 5 return x + 1print(showplus(5) )  # 6 
  • 举例二
def showplus(x):print(x)        # 5return x + 1#   File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 7#     print(x+1)# 会执行吗?#          ^# SyntaxError: invalid character '(' (U+FF08)#print(x+1)# 会执行吗?showplus(5)     # 6 

函数的返回值

  • 多条return语句 def guess(x):
def guess(x):if x > 3:return "> 3"else:return "< = 3"print(guess(10))   # > 3
  • 多条return语句
def showplus(x):print(x)		# 5 return x + 1return x + 2print(showplus(5))      # 6 
  • 举例
def fn(x):for i in range(x):if i > 3:return ielse:print("O is not greater than 3".format(x))print(fn(5))  # 打印什么?print('*' * 60)print(fn(3))  # 打印什么?
输出:
O is not greater than 3
O is not greater than 3
O is not greater than 3
O is not greater than 3
4
************************************************************
O is not greater than 3
O is not greater than 3
O is not greater than 3
None

总结

  • Python函数使用return语句返回“返回值”

  • 所有函数都有返回值,如果没有return语句,隐式调用return None

  • return 语句并不一定是函数的语句块的最后—条语句

  • 一个函数可以存在多个return语句,但是只有一条可以被执行。如果没有一条return语句被执行到,隐式调用return None

  • 如果有必要,可以显示调用return None,可以简写为return

  • 如果函数执行了return语句,函数就会返回,当前被执行的return语句之后的其它语句就不会被执行了

  • 作用:结束函数调用、返回值

  • 返回多个值

def showlist():return [1, 3,5]print(showlist())       # [1, 3, 5]
print(type(showlist())) # <class 'list'>

showlist函数是返回了多个值吗?

这次showlist函数是否返回了多个值呢?

def showlist():return 1, 3, 5print(showlist())               # (1, 3, 5)
print(type(showlist()))         # <class 'tuple'>
  • 返回多个值

  • 函数不能同时返回多个值

  • return [1, 3, 5] 是指明返回一个列表,是一个列表对象

  • return 1, 3. 5 看似返回多个值,隐式的被python封装成了一个元组 def

x, y, z = showlist()# 使用解构提取更为方便

def showlist():return 1, 3, 5x, y, z = showlist()
print(x, y, z) # 1 ,3, 5 

函数嵌套

  • 函数嵌套
    在一个函数中定义了另外一个函数
def outer():def inner():print("inner")print("outer")inner()outer()# 输出:
# outer
# inner# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 9, in <module>
#     inner()
#     ^^^^^
# NameError: name 'inner' is not defined. Did you mean: 'iter'?
# inner()
  • 函数有可见范围,这就是作用域的概念
  • 内部函数不能被外部直接使用,会抛NameError异常

作用域***

  • 作用域
    一个标识符的可见范围,这就是标识符的作用域。一般常说的是变量的作用域口举例,对比下面2个函数
x = 5def foo():print(x)foo()                   # 5 

x到底可见还是不可见?

在这里插入图片描述

  • 全局作用域
    在整个程序运行环境中都可见
  • 局部作用域
    在函数、类等内部可见
    局部变量使用范围不能超过其所在的局部作用域

在这里插入图片描述

嵌套结构
def outer1():  #o = 65def inner():print("inner {}".format(o))     # inner 65print(chr(o))    # Aprint("outer {}".format(o))     # outer 65inner()outer1()
def outer2():  #o = 65def inner():o = 97print("inner {}".format(o))     # inner 97print(chr(o))       # aprint("outer {}".format(o))     # outer 65inner()outer2()

区别上面o 的区别

  • 从嵌套结构例子看出
    1、外层变量作用域在内层作用域可见
    2、内层作用域inner中,如果定义了0=97,相当于当前作用域中重新定义了一个新的变量。,但是这个o并没有覆盖外层作用域outer中的o
    3、再看下面代码
    在这里插入图片描述

  • 代码
    在这里插入图片描述

  • x+=1其实是×=×+1

  • 相当于在foo内部定义一个局部变量X,那么foo内部所有x都是这个局部变量X了

  • 但是这个x还没有完成赋值,就被右边拿来做加1操作了

  • 如何解决这个问题?

  • 全局变量global

x = 5
def foo():global xx += 1print(x)        # 6 foo()
  • 使用global关键字的变量,将foo内的x声明为使用外部的全局作用域中定义的x

  • 全局作用域中必须有x的定义

  • 如果全局作用域中没有x定义会怎样?

  • 全局变量global

# x = 5def foo():global xx = 10x += 1print(x)        # 11# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 11, in <module>
#     print(x)
#           ^
# NameError: name 'x' is not defined
# print(x)foo()
# 做这些实验建议不要使用ipython、jupyter,因为它会上下文中有x定义,可能测试不出效果
  • 使用global关键字的变量,将foo内的x声明为使用外部的全局作用域中定义的x
  • 但是,x = 10赋值即定义,x在内部作用域为一个外部作用域的变量赋值,所以x+=1不会报错。注意,这里x的作用域还是全局的
  • global总结
            x+=1这种是特殊形式产生的错误的原因?先引用后赋值,而python动态语言是赋值才算定义, 才能被引用。解决办法,在这条语句前增加×=0之类的赋值语句,或者使用global告诉内部作用域,去全局作用域查找变量定义涔悦
            内部作用域使用x=5之类的赋值语句会重新定义局部作用域使用的变量x,但是,一旦这个作用域中使用global声明x为全局的,那么X=5相当于在为全局作用域的变量x赋值

        global使用原则
                外部作用域变量会内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离
                如果函数需要使用外部全局变量,请使用函数的形参传参解决
                一句话:不用global。学习它就是为了深入理解变量作用域

闭包*

  • 自由变量:未在本地作用域中定义的变量。例如定义在内存函数外的外层函数的作用域中的变量
  • 闭包:就是一个概念,出现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,就形成了闭包。很多语言都有这个概念,最熟悉就是JavaScript
  • 先看右边一段代码
            第4行会报错吗?为什么
            第8行打印什么结果?
            第10行打印什么结果?

在这里插入图片描述

  • 代码解析
    第4行会报错吗?为什么
            不会报错,c已经在counter函数中定义过了。而且inc中的使用方式是为c的元素修改值,而不是重新定义变量
    第8行打印什么结果?
            打印 12
    第10行打印什么结果?
            打印 3
            第9行的c和counter中的c不一样,而inc引用的是自由变量正式counter的变量c
    这是Python2中实现闭包的方式,Python3还可以使用nonlocal 关键字

  • 下面这段代码会报错吗?为什么?

  • 使用globa能否解決?
    在这里插入图片描述

  • 使用global可以解决,但是这使用的是全局变量,而不是闭包

  • 如果要对普通变量的闭包,Python3中可以使用nonlocal

nonlocal关键字

  • 使用了nonlocal关键字,将变量标记为不在本地作用域定义,而在上级的某一级局部作用域中定义,但不能是全局作用域中定义
def counter():count = 0def inc():nonlocal countcount += 1return countreturn incfoo = counter()print(foo())        # 1print(foo())        # 2
a = 50def counter():#   File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 5#     nonlocal a  # 上一级作用域是全局作用域#     ^^^^^^^^^^# SyntaxError: no binding for nonlocal 'a' foundnonlocal a  # 上一级作用域是全局作用域a += 1print(a)count = 0def inc():nonlocal countcount += 1return countreturn incfoo = counter()
foo()
foo()
  • count 是外层函数的局部变量,被内部函数引用
  • 内部函数使用nonlocal关键字声明count变量在上级作用域而非本地作用域中定义
  • 左边代码可以正常使用,且形成闭包
  • 右边代码不能正常运行,变量a 不能在全局作用域中

默认值的作用域

  • 默认值举例一
def foo(xyz=1):print(xyz)foo()  # 打印什么? 1 foo()  # 打印什么?  1 # Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 9, in <module>
#     print(xyz)  # 打印什么?
#           ^^^
# NameError: name 'xyz' is not defined
print(xyz)  # 打印什么?
  • 默认值举例二
def foo (xyz = []):xyz.append (1)print(xyz)# 打印什么?foo()#打印什么? [1]foo() # #打印什么?  [1, 1]
  • 为什么第二次调用foo函数打印的是[1,1]?

因为函数也是对象,python把函数的默认值放在了属性中,这个属性就伴随着这个函数对象的整个生命周期
查看foo.__defaults__属性

  • 运行这个例子
def foo(xyz=[], u='abc', z=123):xyz.append(1)return xyzprint(foo(), id(foo))               # [1] 4373267616print(foo.__defaults__)     # ([1], 'abc', 123)print(foo(), id(foo))       #  [1, 1] 4373267616 print(foo.__defaults__)     # ([1, 1], 'abc', 123)
  • 函数地址并没有变,就是说函数这个对象的没有变,调用它,它的属性 __defaults__中使用元组保存默认值

  • xyz默认值是引用类型,引用类型的元素变动,并不是元组的变化

  • 非引用类型例子

def foo(w, u='abc', z=123):u = 'xyz'z = 789print(w, u, z)print(foo.__defaults__)                         # ('abc', 123)foo('magedu')                                   # magedu xyz 789print(foo.__defaults__)                         # ('abc', 123)

属性__defaults__中使用元组保存所有位置参数默认值,它不会因为在函数体内使用了它而发生改变

  • 举例
def foo(w, u='abc', *, z=123, zz=[456]):u = 'xyz'z = 789zz.append(1)print(w, u, z, zz)                          # magedu xyz 789 [456, 1]print(foo.__defaults__)                 #  ('abc',)foo('magedu')print(foo.__kwdefaults__)                   # {'z': 123, 'zz': [456, 1]}
  • 属性__defaults__ 中使用元组保存所有位置参数默认值

  • __kwdefaults__中使用字典保存所有keyword-only参数的默认值

  • 使用可变类型作为默认值,就可能修改这个默认值

  • 有时候这个特性是好的,有的时候这种特性是不好的,有副作用

  • 如何做到按需改变呢?看下面的2种方法

def foo(xyz=[], u='abc', z=123):xyz = xyz[:]  # 影子拷贝xyz.append(1)print(xyz)foo()  # [1]
print('*' * 60)print('1、', foo.__defaults__)  # 1、 ([], 'abc', 123)print('-' * 60)foo()  # [1]print('2、', foo.__defaults__)  # 2、 ([], 'abc', 123)foo([10])  # [10, 1]print('3、', foo.__defaults__)  # 3、 ([], 'abc', 123)foo([10, 5])  # [10, 5, 1]print('4、', foo.__defaults__)  # 4、 ([], 'abc', 123)

1、函数体内,不改变默认值
xyz都是传入参数或者默认参数的副本,如果就想修改原参数,无能为力

  • 第一种方法
    使用影子拷贝创建一个新的对象,永远不能改变传入的参数

  • 第二种方法
    通过值的判断就可以灵活的选择创建或者修改传入对象
    这种方式灵活,应用广泛
    很多函数的定义,都可以看到使用None这个不可变的值作为默认参数,可以说这是一种惯用法

变量名解析原则LEGB

  • Local,本地作用域、局部作用域的local命名空间。函数调用时创建,调用结束消亡

  • Enclosing,Python2.2时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间

  • Global,全局作用域,即一个模块的命名空间。模块被impor 创建,解释器退出时消亡

  • Build-in,内置模块的命名空间,生命周期从python解释器启动时创建到解释器退出时消亡。例如 print(open),print和open都是内置的变量

  • 所以一个名词的查找顺序就是LEGB

在这里插入图片描述

函数的销毁

全局函数
def foo(xyz=[], u='abc', z=123):xyz.append(1)return xyzprint(foo(), id(foo), foo.__defaults__)  # [1] 4374004896 ([1], 'abc', 123)def foo(xyz=[], u='abc', z=123):xyz.append(1)return xyzprint(foo(), id(foo), foo.__defaults__)  # [1] 4374282304 ([1], 'abc', 123)del foo# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 19, in <module>
#     print(foo(), id(foo), foo.__defaults__)
#           ^^^
# NameError: name 'foo' is not defined
print(foo(), id(foo), foo.__defaults__)
  • 全局函数销毁
    重新定义同名函数
    del 语句删除函数对象
    程序结束时

局部函数

  • 局部函数销毁
    重新在上级作用域定义同名函数
    del 语句删除函数对象
    上级作用域销毁时
def foo(xyz=[], u='abc', z=123):xyz.append(1)def inner(a=10):passprint('1'*30,inner)  # <function foo.<locals>.inner at 0x103108040>def inner(a=100):print('3'*30,xyz)print('2'*30,inner)  # <function foo.<locals>.inner at 0x1032979c0>return innerbar = foo()print('*' * 60)
print(id(foo), id(bar), foo.__defaults__, bar.__defaults__)     # 4368516256 4370856384 ([1], 'abc', 123) (100,)del barprint('~' * 60)
# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 25, in <module>
#     print(id(foo), id(bar), foo.__defaults__, bar.__defaults__)
#                       ^^^
# NameError: name 'bar' is not defined
print(id(foo), id(bar), foo.__defaults__, bar.__defaults__)

递归函数

函数执行流程

  • 全局帧中生成foo1、foo2、foo3、main函数对象口
  • main函数调用
  • main中查找内建函数print压栈,将常量字符串压栈,调用函数,弹出栈顶
  • main中全局查找函数foo1压栈,将常量100、101压栈,调用函数foo1,创建栈帧。Print函数压栈,字符串和变量b、b1 压栈,调用函数,弹出栈顶,返回值。
  • main中全局查找foo2函数压栈,将常量200压栈,调用foo2, 创建栈帧。foo3函数压栈,变量c引用压栈,调用foo3,创建栈帧。foo3完成print函数调用后返回。foo2恢复调用,执行print后,返回值。main中foo2调用结束弹出栈顶人的a继续执行print函数调用,弹出栈顶。main函数返回
# http://pythontutor.com/visualize.html#mode=edit def foo 1(b, b1=3):def foo1(b, b1=3):print("foo1 called", b, b1)def foo2(c):foo3(c)print("foo2 called ", c)![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/7bffecd6e98a48c2b3dd5091fddd937c.jpeg)def foo3(d):print("foo3 called ", d)def main():print("main called")foo1(100, 101)foo2(200)print("main ending")main()
输出:
main called
foo1 called 100 101
foo3 called  200
foo2 called  200
main ending

函数执行流程

# http://pythontutor.com/visualize.html#mode=edit def foo 1(b, b1=3):def foo1(b, b1=3):print("foo1 called", b, b1)def foo2(c):pass

在这里插入图片描述

递归Recursion

  • 函数直接或者间接调用自身就是递归

  • 递归需要有边界条件、递归前进段、递归返回段口递归一定要有边界条件

  • 当边界条件不满足的时候,递归前进口当边界条件满足的时候,递归返回

在这里插入图片描述

  • 斐波那契数列Fibonacci number: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89,144,
  • 如果设F(n)为该数列的第n项(n E N*),那么这句话可以写成如下形式::F(n)=F(n-1)+F(n-2)
  • F(0)=0 , F(1)=1, F(n)= F(n-1) + F(n-2)
pre = 0cur = 1  # No1print(pre, cur, end='') # 0 1print('~'*30)n = 4# 100pfor i in range(n - 1):pre, cur = cur, pre + curprint(cur, end=' ')     # 1 2 3
  • F(0)=0 , F(1)=1, F(n)= F(n-1)+ F(n-2)
def fib(n):return 1 if n < 2 else fib(n - 1) + fib(n - 2)for i in range(5):print(fib(i), end='')  # 11235解析:fib(3) + fib(2)
fib(3)调用 fib(3), fib(2), fib(1)
fib(2)调用fib(2)、fib(1) 
fib(1)是边界

递归要求

递归一定要有退出条件,递归调用一定要执行到这个退出条件。没有退出条件的递归调用,就是无限调用、

递归调用的深度不宜过深
                
Python对递归调用的深度做了限制,以保护解释器
                
超过递归深度限制,抛出RecursionError: maxinum recursion depth exceeded 超出最大深度
                
sys.getrecursionlimit()

递归的性能

for 循环
import datetimestart = datetime.datetime.now()
pre = 0cur = 1  # No1print(pre, cur, end=' ')n = 35for i in range(n - 1):pre, cur = cur, pre + curprint(cur, end=' ')print()delta = (datetime.datetime.now() - start).total_seconds()print('time=',delta)
输出:
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 
time= 5.7e-05
递归
import datetimen = 35start = datetime.datetime.now()def fib(n):return 1 if n < 2 else fib(n - 1) + fib(n - 2)for i in range(n):print(fib(i), end=' ')print()delta = (datetime.datetime.now() - start).total_seconds()print('time=',delta)输出:
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 
time= 1.562472
  • 循环稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果

  • fib函数代码极简易懂,但是只能获取到最外层的函数调用,内部递归结果都是中间结果。而且给定一个n 都要进行近2n次递归,深度越深,效率越低。为了获取斐波那契数列需要外面在套一个n次的循环,效率就更低了

  • 递归还有深度限制,如果递归复杂,函数反复压栈,栈内存很快就溢出了

  • 思考:这个极简的递归代码能否提高性能呢?

  • 斐波那契数列的改进

pre = 0cur = 1  # No1print(pre, cur, end=' ')def fib(n, pre=0, cur=1):  # recursionpre, cur = cur, pre + curprint(cur, end=' ')if n == 2:returnfib(n - 1, pre, cur)fib(35)
输出:
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465
  • 改进

  • 左边的fib函数和循环的思想类似

  • 参数n是边界条件,用n来计数

  • 上一次的计算结果直接作为函数的实参口效率很高

  • 和循环比较,性能相近。所以并不是说递归一定效率低下。但是递归有深度限制

  • 对比一下三个fib函数的性能

def foo1():foo2()def foo2():foo1()foo1()

间接递归,是通过别的函数调用了函数自身
但是,如果构成了循环递归调用是非常危险的,但是往往这种情况在代码复杂的情况下,还是可能发生这种调用。要用代码的规范来避免这种递归调用的发生

递归总结

  • 递归是一种很自然的表达,符合逻辑思维

  • 递归相对运行效率低,每一次调用函数都要开辟栈帧

  • 递归有深度限制,如果递归层次太深,函数反复压栈,栈内存很快就溢出了

  • 如果是有限次数的递归,可以使用递归调用,或者使用循环代替,循环代码稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果

  • 绝大多数递归,都可以使用循环实现

  • 即使递归代码很简洁,但是能不用则不用递归

递归调用对比
import datetime# Fib Seq
start = datetime.datetime.now()
pre = 0
cur = 1  # No1
print(pre, cur, end=' ')
n = 35
# loop
for i in range(n - 1):pre, cur = cur, pre + curprint(cur, end=' ')
delta = (datetime.datetime.now() - start).total_seconds()
print(delta)# Fib Seq
start = datetime.datetime.now()
pre = 0
cur = 1  # No1
print(pre, cur, end=' ')# recursion
def fib1(n, pre=0, cur=1):pre, cur = cur, pre + curprint(cur, end=' ')if n == 2:returnfib1(n - 1, pre, cur)fib1(n)
delta = (datetime.datetime.now() - start).total_seconds()print(delta)start = datetime.datetime.now()def fib2(n):if n < 2:return 1return fib2(n - 1) + fib2(n - 2)for i in range(n):print(fib2(i), end=' ')
delta = (datetime.datetime.now() - start).total_seconds()
print(delta)输出:
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 4.5e-05
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 3.6e-05
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025 121393 196418 317811 514229 832040 1346269 2178309 3524578 5702887 9227465 1.467969

练习

求n的阶乘

def fac(n):if n == 1:return 1return n * fac(n - 1)def fac1(n, p=1):if n == 1:return pp *= nprint(p)fac1(n - 1, p)return pdef fac2(n, p=None):if p is None:p = [1]if n == 1:return p[0]p[0] *= nprint(p[0])fac2(n - 1, p)return pn = 10print('1' * 30, fac(n))print('2' * 30, fac1(n))print('3' * 30, fac2(n))输出:
111111111111111111111111111111 3628800
10
90
720
5040
30240
151200
604800
1814400
3628800
222222222222222222222222222222 10
10
90
720
5040
30240
151200
604800
1814400
3628800
333333333333333333333333333333 [3628800]

fac函数性能最好,因为时间复杂度是相同的,fac最简单

将一个数逆序放入列表中,例如1234=>[4,3,2,1]
  • 方法一
data = str(1234)def reversal(x):if x == -1:return ''return data[x] + reversal(x - 1)print(reversal(len(data) - 1))
  • 方法二
def revert(n, lst=None):if lst is None:lst = []x, y = divmod(n, 10)lst.append(y)if x == 0:return lstreturn revert(x, lst)print(revert(12345))
  • 方法三
num = 1234def revert(num, target=[]):if num:target.append(num[len(num) - 1])  # target.append(num[-1:])revert(num[: len(num) - 1])return targetprint(revert(str(num)))
解决猴子吃桃问题
  • 猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了一个。第二天早上又将剩下的桃子吃掉一半,又多吃了一个。以后每天早上都吃了前一天剩下的一半零一个。到第10天早上想吃时,只剩下一个桃子了。求第一天共摘多少个桃子
def peach(days=10):if days == 1:return 1return (peach(days - 1) + 1) * 2print(peach())

注意这里必须是10,因为return (peach(days-1)+1)*2立即拿不到结果,必须通过再一次进入函数时判断是不是到了最后一天。也就是当前使用的值是由下一次函数调用得到,所以要执行10次函数调用换种方式表达

def peach(days=1):if days == 10:return 1return (peach(days + 1) + 1) * 2print(peach())

匿名函数

  • 匿名,即没有名字

  • 匿名函数,即没有名字的函数
    没有名字如何定义
    没有名字如何调用
    如果能调用,如何使用

  • Python借助Lambda表达式构建匿名函数

  • 格式
    lambda 参数列表:表达式

lambda x: x ** 2(lambda x: x ** 2)(4)  # 调用foo = lambda x, y: (x + y) ** 2  # 不推荐这么用print(foo(2, 1))  # 9 def foo(x, y):  # 建议使用普通函数return (x + y) ** 2print(foo(2, 1))  # 9
  • 使用lambda关键字来定义匿名函数

  • 参数列表不需要小括号

  • 冒号是用来分割参数列表和表达式的薪职业学院口不需要使用return,表达式的值,就是匿名函数返回值口 lambda表达式(匿名函数)只能写在一行上,被称为单行函数

  • 用途
    在高阶函数传参时,使用lam bda表达式,往往能简化代码

print((lambda: 0)())                                        # 0print((lambda x, y=3: x + y)(5))                #8print((lambda x, y=3: x + y)(5, 6))     #11print((lambda x, *, y=30: x + y)(5))                    #35print((lambda x, *, y=30: x + y)(5, y=10))              # 15print((lambda *args: (x for x in args))(*range(5)))             #<generator object <lambda>.<locals>.<genexpr> at 0x1046cda80>print((lambda *args: [x + 1 for x in args])(*range(5)))     #[1, 2, 3, 4, 5]print((lambda *args: {x + 2 for x in args})(*range(5)))     # {2, 3, 4, 5, 6}print([x for x in (lambda *args: map(lambda x: x + 1, args))(*range(5))])       # [1, 2, 3, 4, 5]print([x for x in (lambda *args: map(lambda x: (x + 1, args), args))(*range(5))]) # [(1, (0, 1, 2, 3, 4)), (2, (0, 1, 2, 3, 4)), (3, (0, 1, 2, 3, 4)), (4, (0, 1, 2, 3, 4)), (5, (0, 1, 2, 3, 4))]

生成器***

  • 生成器generator

  • 生成器指的是生成器对象,可以由生成器表达式得到,也可以使用yield关键字得到一个生成器

  • 生成器函数
    函数体中包含yield语句的函数,返回生成器对象
    生成器对象,是一个可迭代对象,是一个迭代器
    生成器对象,是延迟计算、惰性求值的

生成器

举例一

def inc():for i in range(5):yield iprint(type(inc))        # <class 'function'>
print(type(inc()))      # <class 'generator'>x = inc()print(type(x))          # <class 'generator'>print(next(x))          # 0print('*'*30)
for m in x:print(m, 1 * 1)# 输出:
# 1 1
# 2 1
# 3 1
# 4 1print('-------------------------')for m in x:print(m, '**')
  • 普通的函数调用fn(),函数会立即执行完毕,但是生成器函数可以使用next函数多次执行
  • 生成器函数等价于生成器表达式,只不过生成器函数可以更加的复杂
y = (i for i in range(5))
print(type(y))          # <class 'generator'>print(next(y))          # 0 print(next(y))          # 1 
举例
def gen():print('line 1')yield 1print('line 2')yield 2print('line 3')return 3next(gen())  # line 1print('-'*30)
next(gen())  # line 1g = gen()print('*'*30)
print(next(g))  # line 1print(next(g))  # line 2
print('~'*30)print(next(g))  # StopIteration# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/jk/zhushi/test2.py", line 27, in <module>
#     print(next(g))  # StopIteration
#           ~~~~^^^
# StopIteration: 3print(next(g, 'End'))  # 没有元素给个缺省值
  • 在生成器函数中,使用多个yield语句,执行一次后会暂停执行,把yield表达式的值返回口再次执行会执行到下一个yield语句

  • return语句依然可以终止函数运行,但return语句的返回值不能被获取到

  • return会导致无法继续获取下一个值,抛出 Stoplteration异常

  • 如果函数没有显示的return语句,如果生成器函数执行到结尾,一样会抛出Stoplteration异常

生成器函数

  • 包含yield语句的生成器函数生成生成器对象的时候,生成器函数的函数体不会立即执行口 next(generator) 会从函数的当前位置向后执行到之后碰到的第一个yield语句,会弹出值,并暂停函数执行

  • 再次调用next函数,和上一条一样的处理过程冬创

  • 没有多余的yield语句能被执行,继续调用next函数,会抛出StopIteration异常

  • 无限循环

def counter():i = 0while True:i += 1yield idef inc(c):return next(c)c = counter()print(inc(c))               # 1print(inc(c))               # 2print('end')
def counter():i = 0while True:i += 1yield idef inc():c = counter()return next(c)print(inc())  # 1
print(inc())  # 1
print(inc())  # 1

生成器应用

  • 计算器
def inc():def counter():i = 0while True:i += 1yield ic = counter()return lambda: next(c)foo = incprint(foo())         # <function inc.<locals>.<lambda> at 0x10114c040>print(foo())        # <function inc.<locals>.<lambda> at 0x1011084a0>
  • lambda表达式是匿名函数
  • return返回的是一个匿名函数
  • 等价于下面的代码
def inc():def counter():i = 0while True:i += 1yield ic = counter()def _inc():return next(c)return _incfoo = inc()print(foo())                # 1print(foo())                # 2 print(foo())                # 3
  • 处理递归问题
def fib():x = 0y = 1while True:yield yx, y = y, x + yfoo = fib()print('-'*30)
for _ in range(5):print(next(foo))print('*'*30)for _ in range(100):next(foo)print('~'*30)print(next(foo))
输出:
------------------------------
1
1
2
3
5
******************************
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6356306993006846248183
  • 等价于下面的代码
pre = 0cur = 1  # Nolprint(pre, cur, end=' ')# récursiondef fib1(n, pre=0, cur=1):pre, cur = cur, pre + curprint(cur, end=' ')if n == 2:returnfib1(n - 1, pre, cur)

生成器应用

  • 协程coroutine
    生成器的高级用法
    比进程、线程轻量级
    是在用户空间调度函数的一种实现市职业学院
    Python3 asyncio就是协程实现,已经加入到标准库
    Python3.5 使用async、await关键字直接原生支持协程
    协程调度器实现思路
            有2个生成器A、B
            next(A)后,A执行到了yield语句暂停,然后去执行next(B),B执行到yield语句也暂停,然后再次调用next(A),再调用next(B)在,周而复始,就实现了调度的效果
            可以引入调度的策略来实现切换的方式
            协程是一种非抢占式调度

yield from

  • 举例
def inc():for x in range(1000):yield xfoo = inc()print(next(foo))        # 0print(next(foo))        # 1print(next(foo))        # 2 
  • 等价于下面的代码
def inc():yield from range(1000)foo = inc()print(next(foo))  # 0 print(next(foo))  # 1 print(next(foo))  # 2
  • yield from是Python 3.3出现的新的语法

  • yield from iterable 是 for item in iterable: yield item 形式的语法糖口从可迭代对象中一个个拿元素

def counter(n):  # 生成器,迭代器for x in range(n):yield xdef inc(n):yield from counter(n)foo = inc(10)print(next(foo))  # 0 print(next(foo))  # 1

把一个子典扁平化

源字典 {‘a’:{‘b’:1,‘c’:2},‘d’:{‘e’:3,f:{‘g’:4}}}
目标字典:{‘a.c’:2,‘d.e’:3,‘d.f.g’:4,‘a.b’:1}

source = {'a': {'b': 1, 'c': 2}, 'd': {'e': 3, 'f': {'g': 4}}}
target = {}# recursiondef flatmap(src, prefix=''):for k, v in src.items():if isinstance(v, (list, tuple, set, dict)):flatmap(v, prefix=prefix + k + '.')  # 递归调用else:target[prefix + k] = vflatmap(source)print(target)# 输出
# {'a.b': 1, 'a.c': 2, 'd.e': 3, 'd.f.g': 4}

一般这种函数都会生成一个新的字典,因此改造一下 dest字典可以由内部创建,也可以有外部提供

source = {'a': {'b': 1, 'c': 2}, 'd': {'e': 3, 'f': {'g': 4}}}# recursiondef flatmap(src, dest=None, prefix=''):if dest == None:dest = {}for k, v in src.items():if isinstance(v, (list, tuple, set, dict)):flatmap(v, dest, prefix=prefix + k + '.')  # 递归调用else:dest[prefix + k] = vreturn destprint(flatmap(source))# 输出:
# {'a.b': 1, 'a.c': 2, 'd.e': 3, 'd.f.g': 4}

能否不暴露给外界内部的字典呢?

能否函数就提供一个参数源字典,返回一个新的扁平化字典呢?

递归的时候要把目标字典的引用传递多层,怎么处理?

source = {'a': {'b': 1, 'c': 2}, 'd': {'e': 3, 'f': {'g': 4}}}# recursiondef flatmap(src):def _flatmap(src, dest=None, prefix=''):for k, v in src.items():key = prefix + kif isinstance(v, (list, tuple, set, dict)):_flatmap(v, dest, key + '.')  # 递归调用else:dest[key] = vdest = {}_flatmap(src, dest)return destprint(flatmap(source))# 输出:
# {'a.b': 1, 'a.c': 2, 'd.e': 3, 'd.f.g': 4}

实现Base64编码

要求自己实现算法,不用库

在这里插入图片描述

将输入每3个字节断开,拿出一个3个字节,每6个bit断开成4段。

2**6=64,因此有了base64的编码表。
每一段当做一个8bit看它的值,这个值就是Base64编码表的索引值,找到对应字符。

再取3个字节,同样处理,直到最后。

举例:

abc对应的ASCII码为:0×61 0×62 0×63

01100001 01100010 01100011 # abc

011000 010110 001001 100011

00011000 00010110 00001001 00100011 #每6位补齐为8位

24 22 9 35

末尾的处理?

1、正好3个字节,处理方式同上

2、剩1个字节或2个字节,用0补满3个字节。

3、补0的字节用=表示

#  自己实现对一段字符串进行base64编码
#  自己实现对一段字符串进行base64编码alphabet = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijkImnopqrstuvwxyz0123456789+/"teststr = "abcd"teststr = "ManMa"def base64(src):ret = bytearray()length = len(src)# r记录补O的个数r = 0for offset in range(0, length, 3):if offset + 3 <= length:triple = src[offset:offset + 3]else:triple = src[offset:]r = 3 - len(triple)triple = triple + '\x00' * r  # 补几个0# print(triple, r)# 将3个字节看成一个整体转成字节bytes,大端模式# abc => 0x616263b = int.from_bytes(triple.encode(), 'big')  # /#‘littleprint(hex(b))# 01100001 01100010 01100011 # abc# 011000 010110 001001 100011 # 每6位断开for i in range(18, -1, -6):if i == 18:index = b >> ielse:index = b >> i & 0x3F  # 0b0011 1111ret.append(alphabet[index])  # 得到base64编码的列表# 策略是不管是不是补零,都填满,只有最后一次可能出现补零的# 在最后替换掉就是了,代码清晰,而且替换至多2次# 在上一个循环中判断r!=Q,效率可能会高些for i in range(1, r + 1):  # 1到r,补几个e替换几个=ret[-i] = 0x3Dreturn retprint(base64(teststr))  # bytearray(b'TWFuTWE=')# base64实现import base64print(base64.b64encode(teststr.encode()))  # b'TWFuTWE='
求2个字符串的最长公共子串

思考:

s1 = ‘abcdefg‘

s2 = ‘defabcd‘

方法一:矩阵算法

让s2的每一个元素,去分别和81的每一个元素比较,相同就是1,不同就是0,有下面的矩阵
在这里插入图片描述

上面都是s1的索引。

看与斜对角线平行的线,这个线是穿过1的,那么最长的就是最长子串。

print(s1[3:3+3])

print(s1[0:0+4]) 最长

矩阵求法还需要一个字符扫描最长子串的过程,扫描的过程就是len(S1) len(S2)次,O(n m)。

有办法一遍循环就找出最长的子串吗?

0001000第一行,索引为3,0。

第二行的时候如果4,1是1,就判断3,0是否为1,为1就把3,0加1。

第二行的时候如果5,2是1,就判断4,1是否为1,是1就加1,再就判断3,0是否为1,为1就把3,叻1。

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/66cc96b55af540679f26c1be079d5d9c.png

上面的方法是个递归问题,不好。

最后在矩阵中找到最大的元素,从它开始就能写出最长的子串了。

但是这个不好算,因为是逆推的,改为顺推。

在这里插入图片描述

顺推的意思,就是如果找到一个就看前一个的数字是几,然后在它的基础上加1。

s1 = 'abcdefg's2 = 'defabcd's2 = 'defabcdoabcdeftw's3 = '1234a's4 = "5678"s5 = 'abcdd'def findit(str1, str2):matrix = []# 从x轴或者y轴取都可以,选择x轴,xmax和xindexxmax = 0xindex = 0for i, x in enumerate(str2):matrix.append([])for j, y in enumerate(str1):if x != y:  # 若两个字符不相等,matrix[i].append(0)else:if i == 0 or j == 0:  # 两个字符相等,有字符在边上的matrix[i].append(1)else:  # 不在边上matrix[i].append(matrix[i - 1][j - 1] + 1)if matrix[i][j] > xmax:  # 判断当前加入的值和记录的最大值比较xmax = matrix[i][j]  # 记录最大值,用于下次比较xindex = j  # 记录当前值的x轴偏移量,和str2[xindex+1-xmax:xindex+1]匹配xindex += 1  # 只是为了计算的需要才+1,和str1[xindex - xmax:xindex]匹配# return str1[xindex+1-xmax:xindex+1]return str1[xindex - xmax: xindex]print(findit(s1, s2))  # abcdef
print(findit(s1, s3))  # a
print(findit(s1, s4))  # 空串
print(findit(s1, s5))  # abcd
s1 = ' abcdefg 's5 = '304abcdd'print(findit(s1, s5))  # abcd

方法二:

可不可以这样思考?

字符串都是连续的字符,所以才有了下面的思路。

思路一:

第—轮

从s1中依次取1个字符,在s2查找,看是否能够找到子串。

如果没有一个字符在$2中找到,说明就没有公共子串,直接退出。如果找到了至少一个公共子串,则很有可能还有更长的公共子串,可以进入下一轮。

第二轮

然后从s1中取连续的2个字符,在s2中查找,看看能否找到公共的子串。如果没找到,说明最大公共子串就是上一轮的随便的哪一个就行了。如果找到至少一个,则说明公共子串可能还可以再长一些。可以进入下一轮。

改进,其实只要找到第一轮的公共子串的索引,最长公共子串也是从它开始的,所以以后的轮次都从这些索引位置开始,可以減少比较的次数。

思路二:

既然是求最大子串,我先看s1全长作为子串。

在s2中搜索,是否返回正常的index,正常就找到了最长子串。

没有找到,把s1按照length-1取多个子串。

在$2中搜索,是否能返回正常的index。

注意:

不要一次把s1的所有子串生成,用不了,也不要从最短开始,因为题目要最长的。

但是也要注意,万一他们的公共子串就只有一个字符,或者很少字符的,思路一就会占优势。

s1 = 'abcdefg's2 = 'defabcdoabcdeftw's3 = '1234a'def findit(str1, str2):count = 0  # 看看效率,计数length = len(str1)for sublen in range(length, 0, -1):for start in range(0, length - sublen + 1):substr = str1[start:start + sublen]count += 1if str2.find(substr) > -1:  # foundprint("count={}, substrlen={}".format(count, sublen))return substrprint(findit(s1, s2))print('*'*30)
print(findit(s1, s3))
print('~'*30)
输出:
count=2, substrlen=6
abcdef
******************************
count=22, substrlen=1
a
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
http://www.dtcms.com/a/267396.html

相关文章:

  • 从数据洞察到设计创新:UI前端如何利用数字孪生提升用户体验?
  • 【算法笔记】4.LeetCode-Hot100-数组专项
  • 操作系统---I/O核心子系统与磁盘
  • Linux操作系统之文件(四):文件系统(上)
  • pyspark大规模数据加解密优化实践
  • NVMe高速传输之摆脱XDMA设计13:PCIe初始化状态机设计
  • 2025 Centos 安装PostgreSQL
  • Java类变量(静态变量)
  • LangChain:向量存储和检索器(入门篇三)
  • 【Qt】qml组件对象怎么传递给c++
  • appnium-巨量测试
  • LVGL移植(外部SRAM)
  • ESP32-S3开发板播放wav音频
  • 应急响应靶机-linux1-知攻善防实验室
  • 介绍electron
  • 若依学习笔记1-validated
  • Qt工具栏设计
  • Tensorboard无法显示图片(已解决)
  • 编程中的英语
  • CHAIN(GAN的一种)训练自己的数据集
  • Ubuntu基础(监控重启和查找程序)
  • 【Elasticsearch】深度分页及其替代方案
  • 基于 Python Django 和 Spark 的电力能耗数据分析系统设计与实现7000字论文实现
  • .NET9 实现排序算法(MergeSortTest 和 QuickSortTest)性能测试
  • Redis--黑马点评--基于stream消息队列的秒杀优化业务详解
  • 升级到MySQL 8.4,MySQL启动报错:io_setup() failed with EAGAIN
  • 每日算法刷题Day42 7.5:leetcode前缀和3道题,用时2h
  • Node.js worker_threads:并发 vs 并行
  • 洛谷刷题9
  • 如何在idea里快速地切换Windows CMD、git bash、powershell