python基础(5)

函数的作用域

作用域:指的是标识符作用域,即变量的可见范围

def fn():
    x = 10
    print(x)


fn()
print(x)
  • 上面的print(x)就会报NameError: name 'x' is not defined的错误,因为x的作用域仅在fn函数内,可以理解为在fn函数中开辟一个作用域,仅对作用域可见

作用域的分类

  • 全局作用域:整个程序中可见,称为全部变量

  • 局部作用域:仅在函数中可见,称为局部变量

# 局部变量
def fn1():
    x = 1  # 局部作用域,x为局部变量,使用范围在fn1内


def fn2():
    print(x) 


print(x)
x = 5 # 全局变量,也在函数外定义
def foo():
    print(x) 
foo()
  • 运行上面代码可知:局部变量无法在函数外访问,全局变量一般可以在函数内部访问

函数嵌套

def out():
    def innner():
        print(111)
        return 111

    return innner


out()
inner()
  • 运行后会报NameError: name 'inner' is not defined的错误,原因在于:函数内部的函数名也是一个标识符,在函数外部不能访问

嵌套函数的作用域

def outer():
    o = 100

    def inner():
        print(o)

    inner()
    print(o)


def outer2():
    o = 100

    def inner():
        o = 10
        print(o)

    inner()
    print(o)
  • 从结果上来看内部函数可以引用外部定义的变量,如果内部函数定义了一个和外部函数变量名相同的变量,会优先使用函数内部的变量,不会覆盖外部原有的变量

易错点

def fn1():
    x = 10

    def inner():
        print(x)

    return inner

a= fn1()
a()
  • 正确
def fn1():
    x = 10

    def inner():
        x += 1
        print(x)

    return inner

a= fn1()
a()
  • 错误
def fn1():
    x = 10

    def inner():
        y =x + 1
        print(y)

    return inner

a= fn1()
a()
  • 正确
x=10
def fn():
    x += 1
    print(x)

fn()
  • 错误

第二个和第四个报错的原因是相同的:UnboundLocalError: local variable 'x' referenced before assignment

  • x+=1使用的是局部变量中的x,而内部x并没有定义

  • 第二个情况下,在inner()中加上global x 或nonlocal x都可以,global x使用的是全局变量,nonlocal只有函数嵌套的情况下使用,x使用的是外部函数定义的变量x

# x = 100
def fn1():
    x = 10

    def inner():
        # global x 在定义全局变量x的情况下可以使用
        nonlocal x # 使用fn1函数下定义的x
        x += 1
        print(x)

    return inner

a= fn1()
a()
  • 第四个情况只能使用global x,因为没有函数嵌套的情况
x=10
def fn():
   global x 
   x += 1
   print(x)

fn()

闭包

在函数嵌套的情况下,内部函数引用外部函数定义的变量(也称为自由变量)

def outer():
   c = [0]

   def inner():
       c[0] += 1
       return c[0]

   return inner
  • 上面不会报错,个人理解:c[0] += 1,c[0]无法作为标识符,所以不会引起UnboundLocalError的错误,所以先找到标识符c,局部变量没有,会找到外部函数的变量c,修改c[0]的值
def outer():
    c = 0

    def inner():
        c += 1
        return c

    return inner
  • 这个就会引起UnboundLocalError错误,需要加上nonlocal c
a = 5


def fn():
    nonlocal a
    a += 1
    print(a)


fn()
  • 上面写法是错误的,因为nonlocal只在函数嵌套中引用外部变量使用

函数的销毁

  • 定义一个函数就是生成一个函数对象,函数名指向的就是函数对象。
  • 可以使用del语句删除函数,使其引用计数减1。
  • 可以使用同名标识符覆盖原有定义,本质上也是使其引用计数减1。
  • Python程序结束时,所有对象销毁。
  • 函数也是对象,也不例外,是否销毁,还是看引用计数是否减为0。

变量名LEGB

变量查找顺序

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

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

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

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

image.png

高阶函数

数学概念 y = f(g(x)),在python中:1.至少一个函数作为参数 2.返回一个函数

def counter():
    a = 0

    def inner():
        nonlocal a
        a += 1
        return a

    return inner


f = counter()
print(f())
print(f())
print(f())
  • f1=counter()和f2=counter()并不相等,但是f1()==f2(),因为是根据return的值比较的

柯里化

原来接受2个参数的函数变成接受一个参数并返回另一个函数,返回的函数接受第二个参数,比如fn(x,y)转换成fn1(x)(y)

def fn(x,y):
  return x+y

# 转换

def f1(x):
  def fn2(y):
    return x+y
  return fn2  
  

装饰器

参考:www.liaoxuefeng.com/wiki/101695… 个人理解是对方法的动态增强,并没有侵入业务代码

无参装饰器

import datetime


def logger(fn):
    def wrapper(*args, **kwargs):
        print(f"{datetime.datetime.now()} 执行了{fn.__name__}方法")
        return fn(*args, **kwargs)

    return wrapper


@logger
def add(x, y):
    return x + y


print(add(1, 5))
  • @logger的意思是add=logger(add) (柯里化),对原来的函数名add重新赋值了,现在add=wrapper函数了,wrapper函数返回原来add(闭包得到)的执行结果,不过执行前print了一条语句增强函数。

有参构造器

import datetime


def logger(do):
    def inner(fn):
        def wrapper(*args, **kwargs):
            print(f"在{datetime.datetime.now()} {do}方法{fn.__name__}")
            return fn(*args, **kwargs)

        return wrapper

    return inner


@logger("execute")
def add(x, y):
    return x + y


print(add(1, 9))
  • 此时@logger的意思是add=logger("execute")(add)

@wraps装饰器

当函数被装饰器修饰后,一些函数属性发生了变化,比如函数名、函数文档等。这时候可以借助functools中wraps装饰器

import datetime
from functools import wraps


def executeTime(fn):
    print("executeTime")

    @wraps(fn)
    def wrapper(*args, **kwargs):
        """execute wrapper"""
        print(f"---{datetime.datetime.now()}")
        return fn(*args, **kwargs)

    return wrapper


@executeTime
def add(x, y):
    """add doc"""
    return x + y


print(add(1, 9), add.__name__, add.__doc__)


image.png

  • @wraps的实质是返回一个偏函数,调用的是update_wrapper方法,在update_wrapper方法中完成原来函数属性复制

image.png

image.png

多装饰器修饰一个函数

import datetime
from functools import wraps


def executeTime(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        """execute wrapper"""
        print(f"---{datetime.datetime.now()}")
        return fn(*args, **kwargs)

    return wrapper


def logger(do):
    def inner(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            """inner wrapper """
            print(f"在{datetime.datetime.now()} {do}方法{fn.__name__}")
            return fn(*args, **kwargs)

        return wrapper

    return inner


@logger("execute")
@executeTime
def add(x, y):
    """add doc"""
    return x + y


print(add(1, 9), add.__name__, add.__doc__)


  • 从上图得到多个装饰器装饰一个函数,从上向下执行

  • 无论@logger、@executeTime 这2个装饰器先后顺序如何,最后add函数的属性都是没有被@wraps(fn)装饰的函数属性。原因:coolshell.cn/articles/11… 总结来说:多个装饰器修饰一个函数,只要有一个函数没有被@wraps装饰,最终的结果就是原函数的属性,多个装饰器没有@wraps,就要看先后顺序了

执行流程

from functools import wraps


def logger(fn):
    print("enter logger")

    @wraps(fn)
    def wrapper(*args, **kwargs):
        """wrapper's doc"""
        print("enter wrapper ")
        return fn(*args, **kwargs)
    print(wrapper)
    return wrapper


@logger
def add(x, y):
    """add"""


@logger
def sub(x, y):
    """sub"""
  • 当函数被@logger装饰,就相当于执行add=logger(add),已经执行了logger函数,在logger函数中遇到@wraps(fn),相当于执行wrapper=wraps(fn)(wrapper),源码wraps(fn)返回一个偏函数。

  • 函数调用是单独执行,也就是说add的wrapper函数和sub的wrapper函数并不是同一个

  • 如果把@wraps(fn)删掉,add和sub函数的函数属性都会和wrapper函数一样

image.png

匿名函数

  • lambda 参数列表: 返回值

  • 参数列表可以有多个,但是返回值只能一个,如果需要返回多个值,用容器包装起来

print((lambda x: 0)(1))
# 加法匿名函数,带缺省值
print((lambda x, y=3: x + y)(5))
print((lambda x, y=3: x + y)(5, 6))
# keyword-only参数
print((lambda x, *, y=30: x + y)(5))
print((lambda x, *, y=30: x + y)(5, y=10))
# 可变参数
print((lambda *args: (x for x in args))(*range(5)))  # 生成器
print((lambda *args: [x + 1 for x in args])(*range(5)))  # 列表
print((lambda *args: {x % 2 for x in args})(*range(5)))  # 集合
print((lambda *args: {str(x): x for x in args})(*range(5)))  # 字典
print(dict(map(lambda x: (chr(65 + x), 10 - x), range(5))))  # 高阶函数,构建字典
d = dict(map(lambda x: (chr(65 + x), 10 - x), range(5)))  # 高阶函数
sorted(d.items(), key=lambda x: x[1])

内部高阶函数

sorted函数

  • sorted(iterable, *, key=None, reverse=False) iterable是可迭代对象,key必须函数或callable,reverse是定义正序还是倒叙排序,默认正序排序

image.png

l = [5, 4, 3, 2, 1]
print(sorted(l)) # sorted会产生新的列表
print(l) # 还是原来的列表
l.sort()
print(l.sort()) # 列表方法sort()不会返回新列表,所以输出None
print(l) # 因为排序过,l已经是排序后的列表了
  • 列表方法sort和sorted用法相同,不过sorted会返回新的列表,sort是修改原来的列表

  • sorted函数不仅可以对列表排序,只要是可迭代对象都可以排序

l1 = (5, 4, 3, 2, 1)
l2 = {5, 4, 3, 2, 1}
print(sorted(l1))
print(sorted(l2))
a = {'c': 1, 'b': 3, 'a': 2}
print(sorted(a))
print(sorted(a.keys()))
print(sorted(a.values()))
print(sorted(a.items()))
print(dict(sorted(a.items(), key=lambda x: x[1]))) # 对value进行排序,返回key value元祖
  • 对字典的排序默认是对字典所有的key排序,字典的items排序也是对key排序,返回一个列表,列表元素是key和value的元祖

filter函数

  • filter(function, iterable) function必须返回True或False,如果function定义为None,那么iterable中的元素为False将会过滤

  • filter返回一个迭代器

a = filter(lambda x: x % 3 == 0, range(5))
print(a)
print(next(a))
print(next(a))
print(next(a)) # 将会报错

map函数

  • map(function, iterable, ...) 第一个参数是函数对象,函数对象的参数和后面传入可迭代对象的个数对应,处理是并行处理,当最短的那个可迭代对象用完,终止

  • 返回可迭代对象

image.png

print(list(map(str, range(5))))
print(dict(map(lambda x, y: (y, x), range(5), ['a', 'b', 'c', 'd'])))

zip函数

  • zip(*iterables) 将多个可迭代对象中的元素一一组成元祖

  • 返回可迭代对象

image.png

print(list(zip([1, 2, 3], ['a', 'b', 'c'], 'xyz')))
print(dict(zip([1, 2, 3], ['a', 'b', 'c'])))

image.png