第六周笔记 函数可变参数 返回值 作用域 递归 匿名函数 生成器

定义与参数

基本概念

  • 结构化编程对代码的最基本封装
  • 封装是为了复用 减少代码冗余
  • 函数可分为内建函数库函数

定义与调用

def语句定义

def 函数名(参数列表):
    函数体
    [return 返回值]
  • 函数名就是标识符
  • 不写return语句会默认返回None
  • 定义中的参数列表称为形参

调用

  • 先定义 后调用
  • 调用方法: 函数名加小括号 括号内写上参数
  • 调用时写入的参数称为实参
  • 函数是可调用对象 callable()

参数

位置参数

  • 实参与形参通过位置相匹配 需保持数量一致
  • 如 def f(x, y, z) f(1, 3, 5)

关键字参数

  • 实参与形参通过关键字相匹配 可打乱顺序 需保持数量一致
  • def f(x, y, z) f(y=3, z=5, x=1)
  • 位置参数必须在关键字参数之前传入
    因此 f(y=5, z=6, 2) 会报错

参数默认值

  • 定义时 在形参变量后写入默认值
def add(x=4, y=5):
    return x+y
  • 形参有默认值 不必保持传参时数量一致
  • 因为实参不够 会使用默认形参
  • 用于参数较多时 简化用户输入与函数调用
def login(host='127.0.0.1',port='8080',username='python',password='******'):
    print('{}:{}@{}/{}'.format(host, port, username, password))
login()#不写参数 全部使用默认参数
login(username='root')#加入关键字 指定某个参数 其余使用默认参数
login('localhost', port='80',password='com')#注意位置参数放在关键字参数前面

可变位置参数

def add(*nums):
    sum = 0
    print(type(nums))
    for x in nums:
        sum += x
    print(sum)

add(3, 6, 9)

    <class 'tuple'>
    18
  • 形参前加*为可变位置参数
  • 收集多个实参为一个元组

可变关键字参数

def showconfig(**kwargs):
    for k,v in kwargs.items():
        print('{} = {}'.format(k, v))

showconfig(host='127.0.0.1',port='22',username='python',password='******')

    host = 127.0.0.1
    port = 22
    username = python
    password = ******
  • 形参前加**为可变关键字参数
  • 收集多个实参为一个字典

可变参数总结

  • 可变位置参数在形参前加* 收集多个实参为一个元组
  • 可变关键字参数在形参前加** 收集多个实参为一个字典
  • 各类参数混合使用时 普通参数放最前 可变参数放最后
  • 可变位置参数放前 可变关键字参数放后
错误例子:
def showconfig(username, password, **kwargs, *args)

以上两条可简单理解为(越)复杂参数(越)往后放

  • 位置参数中已实用的变量 关键字参数中不可再次出现 否则会报multiple values的错误
错误例子:
def fn(x, y, *args, **kwargs):
    pass

fn(7,9,y=5,x=3,a=1,b='python') 
\#TypeError: fn() got multiple values for argument 'y'

Keyword-only参数

  • 可变位置参数后加入的”位置参数”
def fn(*args, x, y, **kwargs):
    print(x)
    print(y)
    print(args)
    print(kwargs)
fn(7,9,y=5,x=3,a=1,b='python')

    3
    5
    (7, 9)
    {'a': 1, 'b': 'python'}

可简写为
def fn(*, x,y):
    print(x,y)
fn(x=5,y=6)
  • 不可放可变关键字参数之后
直接报语法错误
    def fn(**kwargs, x):
                     ^
SyntaxError: invalid syntax
  • 必须用关键字方式传参 否则会报 missing required keyword-only arguments错误
def fn(*args, x, y, **kwargs):
    print(x)
    print(y)
    print(args)
    print(kwargs)
fn(7,9,x=3,a=1,b='python')
    TypeError: fn() missing 1 required keyword-only argument: 'y'

可变参数与默认参数

def fn(y, *args, x=5):
    print('x={}, y={}'.format(x, y))
    print(args)
  • 以上代码中
    • y为位置参数
    • agrs为可变位置参数
    • x为Keyword-only参数 且指定了默认值为5

因此在传参时 至少要传入y值 fn(3) 或者fn(y=3)都可以

  • 若要传入可变位置参数
    对y的传值不可用关键字传参法
    因为关键字参数必须放位置参数后面
    因此fn(y=17,2,3,x=10) 是错误的
    可改为 fn(17,2,3,x=10)
def fn(x=5, **kwargs):
    print('x={}'.format(x))
    print(kwargs)
  • 以上代码中
    • x为位置参数 且指定了默认值为5
    • kwagrs为可变关键字参数
  • 注意关键字x已被位置参数x占用 关键字中任然传入x会报multiple values的错误

  • 不同参数的一般顺序为: 普通位置 默认 可变位置 Keyword-only(可带默认值) 可变关键字

def fn(x, y, z=3, *arg, m=4, n, **kwargs):
    pass
  • 以上代码中
    • x y z 为位置参数 且z指定了默认值为3
    • agrs为可变位置参数
    • m n为Keyword-only参数 且m指定了默认值为4
    • kwargs为可变关键字参数

参数解构

lst=[1,2,3]
print(lst)--> [1,2,3]
print(*lst)---> 1 2 3
  • 给函数提供实参时 可在集合类型前使用*或** 把集合类型的数据解开 提取出所有元素作为函数的实参
  • 字典类型使用** 非字典类型使用*
def add(*iterable):
    result = 0
    for x in iterable:
        result += x
    return result
add(1,2,3)
add(*[1,2,3])
add(*range(10))

返回值

  • 用return语句返回”返回值”
  • 无return语句 则隐式调用 return None
  • return语句以后的内容不被执行
  • 函数只能返回一个值 多个值可存入元组字典等容器再返回

==作用域==

  • 作用域是指 标识符/变量的可见范围
  • 全局作用域: 在程序的整个运行环境都可见
x=5
def fn():
    print(x)
fn() --> 5
# x是全局变量 函数内可见
  • 局部作用域
    • 在函数 类 内部可见
    • 局部变量的使用范围不超过其所在的局部作用域
def outer2(): #
    o = 65
    def inner():
        o = 97
        print("inner {}".format(o))
        print(chr(o))
    print("outer {}".format(o))
    inner()
outer2()
  • 以上函数中 o97只在inner函数内起作用 不会影响inner函数之外的o65的值
  • 调用outer2()函数后
    • 执行outer2()内部的print语句 此作用域下o的值为65
    • 调用inner()函数
      • inner()函数执行两条打印语句 inner()内的作用域下 o的值为97 不受外层o65的影响
  • 外层变量在内层作用域可见
  • 内部变量在外层作用域不可见
x=5
def fn():
    x+=1
    print(x)
fn() --> UnboundLocalError: local variable 'x' referenced before assignment
# x+=1等价于x=x+1
# x=x+1表示(用x+1的结果)对x赋值 赋值则是定义x 既然内部作用域定义了x 就不去外部作用域找x
# 可简单理解为只要出现x=?? 就是定义x
# 等号右边 未定义x就使用x 报错

闭包

  • 自由变量 : 未在本地作用域定义的变量
  • 闭包 : 在嵌套函数中 内层函数引用了外层函数的自由变量
def counter():
    c=[0] #c是元组 
    def inc():
        c[0]+=1 #c[0]是c的内部元素 改变内部元素不是重新定义c元组 此时去外部访问c 
        return c[0]
    return inc #counter函数返回标识符inc inc实质是counter()函数的内部函数对象
foo=counter()#counter()函数返回inc函数对象 并赋给foo
print(foo(),foo()) --> 1 2#每执行一次foo()函数 c[0]就增加1
c=100 #函数内有c变量 不会读取函数外面的c
print(foo()) --> 3 
\###以上为python2中闭包的实现方式

nonlocal关键字

将变量标记位不在本地作用域定义 而在向上的某一局部作用域定义 但不在全局作用域定义

def counter():
    count=0
    def inc():
        nonlocal count
        count+=1
        return count 
    return inc 
foo=counter()
foo() --> 1
foo() --> 2
###python3 使用nonlocal关键字实现对普通变量闭包 从而实现直接修改外层局部变量

默认值的作用域

def foo(xyz=[]):
    xyz.append(1)
    print(xyz)
foo() --> [1]
foo() --> [1,1]
print(xyz) --> NameError: name 'xyz' is not defined
  • 为什么第二次调用foo()函数时输出的是[1,1]
    指定过默认值的参数可以看作函数的局部变量
  • foo.__defaults__ 可查看位置参数的默认值
  • foo.__kwdefaults__ 可查看Keyword-only参数的默认值
def foo(xyz=[], s=123, *,u='abc'):
    xyz.append(1)
    return xyz,s,u
print(foo.__defaults__) --> ([],123)
print(*foo(), id(foo)) --> [1] 123 abc 2344518331792
print(foo.__defaults__) --> ([1],123)
print(*foo(), id(foo)) --> [1, 1] 123 abc 2344518331792
print(foo.__kwdefaults__) --> {'u': 'abc'}
  • 两次调用函数地址未改变 则函数对象未改变
  • xyz是引用类型 列表内元素改变 列表未变
  • 但调用函数后默认值确实变了
  • __defaults__属性 __kwdefaults__ 分别使用元组 字典 保存位置参数 keyWord-only参数的默认值 默认值不会因重新赋值而改变

默认值是否可变

def foo(xyz=[], u='abc', z=123):
    xyz = xyz[:] #影子拷贝
    xyz.append(1)
    print(xyz)
foo() --> [1]
print(foo.__defaults__) -->  ([], 'abc', 123)
foo() --> [1]
print(foo.__defaults__) -->  ([], 'abc', 123)
foo([10]) --> [10,1]
print(foo.__defaults__) -->  ([], 'abc', 123)
foo([10,5]) --> [10,5,1]
print(foo.__defaults__) -->  ([], 'abc', 123)
  • 最后的返回值是原列表的拷贝
  • 永远不会修改默认值
def foo(xyz=None, u='abc', z=123):
    if xyz is None:
        xyz = []
    xyz.append(1)
    print(xyz)
foo() --> [1]
print(foo.__defaults__) --> (None, 'abc', 123)
foo() --> [1]
print(foo.__defaults__) --> (None, 'abc', 123)
foo([10])--> [10,1]
print(foo.__defaults__) --> (None, 'abc', 123)
foo([10,5])--> [10,5,1]
print(foo.__defaults__) --> (None, 'abc', 123)
  • 默认值为不可变类型 若使用默认值None就创建列表,以修改默认值
  • 通过传入值灵活判断是否修改默认值 推荐使用

变量名解析原则LEGB

  1. Local 本地作用域
  2. Enclosing 嵌套函数外部空间(闭包)
  3. Global 全局作用域
  4. Build-in 内置模块命名空间
  5. 本地->闭包->全局->内置模块(LEGB)
    LEGB

递归 Recursion

  • 函数直接或间接调用自身就是递归
  • 递归一定要有边界条件

斐波那契数列

  • 数学表达式: 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=' ')
  • 递归一定要有退出条件 否则就是无限调用的死循环
  • Python默认限制最大递归深度为1000
  • 可用sys.getrecursionlimit()与sys.setrecursionlimit()命令查看或修改最大递归深度
  • 纯递归效率太低 应该做优化
  • 绝大多数递归都可通过循环实现 因此尽量不使用递归
    斐波那契函数的优化:
pre = 0
cur = 1 
print(pre, cur, end=' ')
def fib(n, pre=0,cur=1):
    pre, cur = cur, pre + cur
    print(cur, end=' ')
    if n == 2:
        return
    fib(n-1, pre, cur)
fib(n)

匿名函数

Lambda表达式

  • 格式 lambda 参数列表:表达式
  • lambda x,y:x+y+1 等价于
def add1(x,y):
    return x+y+1
  • 调用 (lambda x,y:x+y+1)(4,5)
  • lambda表达式只能写在一行上 被称为单行函数
  • 在高阶函数传参时 lambda表达式可以简化代码
 [x for x in (lambda *args: map(lambda x: (x+1,args), args))(*range(5))]
 maps=map(lambda x: (x+1,args), args) #map函数用于映射 将agrs内的元素作为参数依次传入前面的函数 返回由函数结果(元素值+1与args组成的元组)组成的迭代器
 lams=(lambda *args:maps) #最外侧为生成器表达式 内侧将args参数解包 返回maps
 [x for x in lams] #最后一步用列表解析式将lams的元素依次加入列表
 输出结果为:
 [(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))]

生成器

生成器

  • 生成器是指生成器对象 可由生成器得到
  • 或使用yield关键字得到生成器函数 调用该函数得到生成器对象

生成器函数

  • 函数体中包含yield语句 返回生成器对象
  • 生成器对象 是一个可迭代对象 也是迭代器
  • 生成器对象 延迟计算 惰性求值
def inc():
    for i in range(5):
        yield i
print(type(inc)) --> <class 'function'>
print(type(inc())) --> <class 'generator'>
x = inc()
print(type(x)) --> <class 'generator'>
print(next(x)) --> 0
for m in x:
    print(m, '*') -->1 *
                     2 *
                     3 *
                     4 *
for m in x:
    print(m, '**') # 生成器只能遍历一次

def gen():
    print('line 1')
    yield 1
    print('line 2')
    yield 2
    print('line 3')
    return 3
next(gen()) --> line 1
next(gen()) --> line 1
g = gen() #g是gen的一个实例
print(next(g)) --> line 1
print(next(g)) --> line 2
print(next(g)) --> StopIteration
print(next(g, 'End')) --> 相当于设置了默认值 遍历完毕不可next 就返回end
  • 生成器函数中使用多个yield语句 每执行一次next就返回一个值(写在yield关键字后面的值 类似于遇到yield就暂停运行 并返回一个值)
  • return语句可用于终止函数 但return后面的值无法得到
  • 调用next 遇到return会报StopIteration异常
  • 可用next(g, ‘End’) 的方法为StopIteration异常指定默认返回值

生成器应用:

def counter():
    i = 0
    while True:
        i += 1
        yield i
def inc(c):
    return next(c)
c = counter()
print(inc(c)) --> 1
print(inc(c)) --> 2

修改版:

def inc(c):
    def counter():
        i = 0
        while True:
            i += 1
            yield i
    c = counter()  
    def _inc():
        return next(c)

foo=inc()
print(foo()) --> 1
print(foo()) --> 2

优化版:

def inc():
    def counter():
        i = 0
        while True:
            i += 1
            yield i
    c = counter()
    return lambda : next(c)
foo = inc()
print(foo())
print(foo())

斐波那契数列:

def fib():
    x = 0
    y = 1
    while True:
        yield y
        x, y = y, x+y
foo = fib()
for _ in range(5):
    print(next(foo))
for _ in range(100):
    next(foo)
print(next(foo))

yield from

def inc():
    for x in range(1000):
        yield x

等价于

def inc():
    yield from range(1000)
阅读更多

没有更多推荐了,返回首页