函数进阶

名称空间

名称空间,英文名name space,顾名思义就是存放名字的地方,其实就是存变量的地方。例如若变量x=11存放于内存中,那么x存在哪里呢?
名称空间正是存放名字x1绑定关系的地方。
名称空间一共有三种,分别是:

  • locals:是函数内的名称空间,包括局部变量和形参
  • globals:全局变量,函数定义所在模块的名称空间
  • builtins:内置模块的名称空间

不同变量的作用域不同就是由这个变量所在的命名空间决定的,

作用域即范围

  • 全局范围:全局存活,全局有效
  • 局部范围:临时存活,局部有效

查看作用域的方法:globals(), locals()

作用域的查找顺序

# -*- coding: utf-8 -*-

level = 'L0'
n = 11


def func():
    level = 'L2'
    n = 22
    print(locals())

    def outer():
        n = 33
        level = 'L2'
        print(locals(), n)

        def inner():
            level = 'L3'
            print(locals(), n)  # inner函数内没有定义n,因此会逐层的往上层函数找变量n,即上层函数outer有变量n=33。

        inner()

    outer()


func()

作用域的查找顺序rule: LEGB

LEGBlocals -> enclosing function -> golbals -> builtins

  • locals 是函数内的名称空间,包括局部变量和形参
  • enclosing function 外部嵌套函数的名称空间
  • globals 全局变量,函数定义所在模块的名称空间
  • builtins 内置模块的名称空间

闭包

关于闭包,即函数定义和函数表达式位于另一个函数的函数体内(嵌套函数)。而且,这些内部函数可以访问它们所在的外部函数中声明的所有局部变量、参数。当其中一个这样的内部函数在包含它们的外部函数之外被调用时,就会形成闭包。也就是谁,内部函数会在外部函数返回后被执行。而当这个内部函数执行时,它仍然必须访问其外部函数的局部变量、参数以及其他内部函数。这些局部变量、参数和函数声明(最初时)的值是外部函数返回时的值,但也会受到内部函数的影响。

# -*- coding: utf-8 -*-


def outer():
    name = 'hello world'

    def inner():
        print("在inner里打印外层函数的变量", name)

    return inner


f = outer()
f()

闭包的意义返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得,该函数无论在何处调用,优先使用自己外层包裹的作用域。

装饰器基础

有几个视频网站,网站有以下几个模块:

# -*- coding: utf-8 -*-


def home():
    print("---首页---")


def america():
    print("---欧美专区---")


def japan():
    print("---欧美专区---")


def henan():
    print("---欧美专区---")

需求:对欧美和河南专区进行认证登录后才能观看视频。

改写代码尝试一:在每个需要认证的专区代码中增加认证登录方法

# -*- coding: utf-8 -*-


user_status = False  # 用户登录了就把这个变量改为True


def login():
    _username = 'alex'  # 假装这里是DB里存的用户信息
    _password = '123'  # 假装着里是DB里存的用户信息
    global user_status
    if not user_status:  # if not user_status 等价于 if user_status == False
        username = input("user: ")
        password = input("password: ")
        if username == _username and password == _password:
            print("欢迎登录...")
            user_status = True
        else:
            print("用户名或密码错误!")
    if user_status:
        print("您已登录,请观看视频。")


def home():
    print("---首页---")


def henan():
    login()
    print("---欧美专区---")


def america():
    login()
    print("---欧美专区---")


def japan():
    print("---欧美专区---")


henan()
america()

# user: alex
# password: 123
# 欢迎登录...
# 您已登录,请观看视频。
# ---河南专区---
# 您已登录,请观看视频。
# ---欧美专区---

代码被打回代码虽然实现了功能,但是需要更改需要加认证的各个模块的代码,这直接违反了软件开发中的一个原则“开放-封闭”原则,简单来说,它规定已经实现的功能代码不允许被修改,但是可以被扩展,即

  • 封闭: 已实现的功能代码块不允许被修改
  • 扩展: 对现有功能的扩展开放

改写代码尝试二:使用高阶函数,在认证登录的方法中将专区模块的函数名当作参数传递进去。

# -*- coding: utf-8 -*-


user_status = False  # 用户登录了就把这个变量改为True


def login(func):
    _username = 'alex'  # 假装这里是DB里存的用户信息
    _password = '123'  # 假装着里是DB里存的用户信息
    global user_status
    if not user_status:  # if not user_status 等价于 if user_status == False
        username = input("user: ")
        password = input("password: ")
        if username == _username and password == _password:
            print("欢迎登录...")
            user_status = True
        else:
            print("用户名或密码错误!")
    if user_status:
        func()  # 只要验证通过了,就调用相应功能。


def home():
    print("---首页---")


def henan():
    print("---河南专区---")


def america():
    print("---欧美专区---")


def japan():
    print("---日本专区---")


login(henan)
login(america)

# user: alex
# password: 123
# 欢迎登录...
# ---河南专区---
# ---欧美专区---

代码又被打回: 虽然实现了功能,但是改变了调用方式。原本通过henan()america()调用,而现在则通过login(henan)login(america)调用。如果像原调用方式的函数模块有几千几万个,那么每个函数模块都要修改代码。这在软件开发中是不行了。

改下代码尝试三:

思路: lambda函数。

def plus(n):
    return n + 1


plus2 = lambda x: x + 1

calc = plus
calc(12)

同样的,对于第二种代码尝试,已经是修改了调用方式,那么稍微改下:

home()
henan = login(henan)  # 这里相当于把henan这个函数替换了
america = login(america) 

henan()  # 用户调用时依然写henan()。这就实现了调用方式不变。

问题在于,还不等用户调用,henan = login(henan) 就会先自己把henan执行了,而应该等用户调用的时候才执行henan()才对。
实现方法嵌套函数

想实现henan = login(henan) 不触发函数的执行,只需要在login里面再定义一层函数,第一次调用henan = login(henan)只调用外层login,这个login虽然会执行,但不会触发认证了,因为认证的所有代码被封装在login里层的新定义的函数里了,login只返回里层函数的函数名,这样下次再执行henan()时,就会调用里层函数了。

# -*- coding: utf-8 -*-

user_status = False


def login(func):  # 把要执行的函数名从这里传进来
    def inner():  # 再定义一层里层的函数
        _username = 'alex'  # 假装这里是DB里存的用户信息
        _password = '123'  # 假装着里是DB里存的用户信息
        global user_status
        if not user_status:  # if not user_status 等价于 if user_status == False
            username = input("user: ")
            password = input("password: ")
            if username == _username and password == _password:
                print("欢迎登录...")
                user_status = True
            else:
                print("用户名或密码错误!")
        if user_status:
            func()  # 只要验证通过了,就调用相应功能。

    return inner  # 用户调用login时,只会返回inner的内存地址,需要下次调用时加上()才会执行inner函数


def home():
    print("---首页---")


def henan():
    print("---河南专区---")


def america():
    print("---欧美专区---")


def japan():
    print("---日本专区---")


henan = login(henan)  # 这里相当于把henan这个函数替换了
america = login(america)

henan()
america()

# user: alex
# password: 123
# 欢迎登录...
# ---河南专区---
# ---欧美专区---

henan = login(henan)
可以用Python中的装饰器语法糖来实现,即在要装饰的函数上面加一行代码。如下:

# -*- coding: utf-8 -*-

user_status = False


def login(func):  # 把要执行的函数名从这里传进来
    def inner():  # 再定义一层里层的函数
        _username = 'alex'  # 假装这里是DB里存的用户信息
        _password = '123'  # 假装着里是DB里存的用户信息
        global user_status
        if not user_status:  # if not user_status 等价于 if user_status == False
            username = input("user: ")
            password = input("password: ")
            if username == _username and password == _password:
                print("欢迎登录...")
                user_status = True
            else:
                print("用户名或密码错误!")
        if user_status:
            func()  # 只要验证通过了,就调用相应功能。

    return inner  # 用户调用login时,只会返回inner的内存地址,需要下次调用时加上()才会执行inner函数


def home():
    print("---首页---")


@login  # 装饰器语法糖
def henan():
    print("---河南专区---")


@login  # 装饰器语法糖
def america():
    print("---欧美专区---")


def japan():
    print("---日本专区---")


henan()
america()

# user: alex
# password: 123
# 欢迎登录...
# ---河南专区---
# ---欧美专区---

手贱给你的“河南专区”版块 加了个参数,然后,结果 出错了。。。
arg-decorator-01

调用henan()时,其实是相当于调用的login,你的henan第一次调用时 henan = login(henan),login就返回了inner的内存地址,第二次用户自己调用henan("3p"),实际上相当于调用的是inner, 但是inner定义时并没有设置参数,但是你给它传了个参数,自然就报错了

arg-decorator-02

因此,带参数的装饰器完成了,完全遵循“封闭-开放“原则,以及调用方式不变。

# -*- coding: utf-8 -*-

user_status = False


def login(func):  # 把要执行的函数名从这里传进来
    def inner(*args, **kwargs):  # 使用非固定参数; 再定义一层里层的函数
        _username = 'alex'  # 假装这里是DB里存的用户信息
        _password = '123'  # 假装着里是DB里存的用户信息
        global user_status
        if not user_status:  # if not user_status 等价于 if user_status == False
            username = input("user: ")
            password = input("password: ")
            if username == _username and password == _password:
                print("欢迎登录...")
                user_status = True
            else:
                print("用户名或密码错误!")
        if user_status:
            func(*args, **kwargs)  # 使用非固定参数; 只要验证通过了,就调用相应功能。

    return inner  # 用户调用login时,只会返回inner的内存地址,需要下次调用时加上()才会执行inner函数


def home():
    print("---首页---")


@login  # 装饰器语法糖
def henan(style, age):  # 加上参数style;这里是两个参数
    print("---河南专区---", style, age)


@login  # 装饰器语法糖
def america(style):  # 加上参数style;这里是一个参数
    print("---欧美专区---", style)


@login
def japan():  # 这里没有参数
    print("---日本专区---")


# 以上三个专区分别是2个参数、1个参数以及没有参数。如果保证装饰器正常运行?
# 要用到非固定参数了:可以不传参数、也可以传一个或者多个参数。 

# henan = login(henan) 第一次执行时,返回的是inner的内存地址,但是inner并没有参数,而调用新henan时给了参数。
henan("3p", 23)
america("5p")
japan()

# user: alex
# password: 123
# 欢迎登录...
# ---河南专区--- 3p 23
# ---欧美专区--- 5p
# ---日本专区---

新需求:允许用户选择用qq|weixin|weibo等认证方式

带参数的装饰器

# -*- coding: utf-8 -*-

user_status = False


def login(auth_type):  # 把要执行的函数名从这里传进来
    def outer(func):
        def inner(*args, **kwargs):  # 使用非固定参数; 再定义一层里层的函数
            if auth_type == 'qq':
                _username = 'alex'  # 假装这里是DB里存的用户信息
                _password = '123'  # 假装着里是DB里存的用户信息
                global user_status
                if not user_status:  # if not user_status 等价于 if user_status == False
                    username = input("user: ")
                    password = input("password: ")
                    if username == _username and password == _password:
                        print("欢迎登录...")
                        user_status = True
                    else:
                        print("用户名或密码错误!")
                if user_status:
                    func(*args, **kwargs)  # 使用非固定参数; 只要验证通过了,就调用相应功能。
            if auth_type == 'weixin':
                print("暂时不支持微信登录。")

        return inner  # 用户调用login时,只会返回inner的内存地址,需要下次调用时加上()才会执行inner函数

    return outer


def home():
    print("---首页---")


# 首先执行 login(qq) -> 返回的outer的内存地址。此时是@outer作为装饰器,然后将函数名henan作为参数传给outer。
# 也就是说: 新henann = login('qq')(henan)
@login('qq')
def henan(style, age):  # 加上参数style;这里是两个参数
    print("---河南专区---", style, age)


@login('weixin')  # 装饰器语法糖
def america(style):  # 加上参数style;这里是一个参数
    print("---欧美专区---", style)


@login
def japan():  # 这里没有参数
    print("---日本专区---")


# 以上三个专区分别是2个参数、1个参数以及没有参数。如果保证装饰器正常运行?
# 要用到非固定参数了:可以不传参数、也可以传一个或者多个参数。

# henan = login(henan) 第一次执行时,返回的是inner的内存地址,但是inner并没有参数,而调用新henan时给了参数。
henan("3p", 23)
america("5p")
# japan()

# user: alex
# password: 123
# 欢迎登录...
# ---河南专区--- 3p 23
# 暂时不支持微信登录。

posted @ 2018-05-06 21:04  hehongjie  Views(109)  Comments(0Edit  收藏  举报