• 【Python】记一次学习,Python 与 js 在实现闭包时的差异


    曾经用 Python 写过一个小东西

    然而最近里面的一个接口突然失效。
    我到那个网站上一看,人家直接把接口关掉了,功能被迁移到了微信公众号里。

    后来一个带佬同学凭着带佬的本能,直接找来一个叫wxpy的库

    pip install wxpy
    

    wxpy → 文档地址

    在原来的代码中,这个功能封装的很完美,所以他要做的就是简单地把封装好的接口替换掉,然后就可以继续快乐的使用了。
    然而没想到的是,试图用微信来实现 与之前直接封包 get 有着很大的差异。

    • 用爬虫直接调别人的接口:输入 → 接口内部处理 → 输出返回,不管是在哪一端,都是一个整体的过程

    • 用微信来实现:输入发送消息 → 接口内部处理(等待回复) → 输出返回,这就很不一样了,问题就出在等待回复那里。

    如果将 输入 → 处理 → 输出 作为一个封装好的功能,那么爬虫爬取就是一个同步过程,而现在用微信收发消息则是一个异步过程,我们必须等待对方回复。

    带佬同学解决不了,于是就来找我。
    我找来文档简单的读了读,听带佬同学讲了讲,我就立刻明白该怎么做了。

    msg_received = False # 是否收到消息的状态标志
    msg = '' # 消息
    
    @bot.register(chat_obj) # 这里相当于为一个聊天对象注册了一个回调函数,当收到来自它的消息时回调
    def getMsg(in_msg):
        msg_ = in_msg
        msg_received = True # 改变状态标志
    
    def query(question):
        chat_obj.send_msg(question)
        while msg_received != True:
            pass # 等待,直到状态标志改变
        msg_received = False
        return  msg
    

    这样写当然没问题,很自然的将割裂的部分缝在了一起,但是作为一个优雅的大学生,我怎么能够允许我的代码出现如此劣质的耦合。
    接口一定要优雅


    下面就要进入正题了

    于是我试图用闭包来实现:

    def initial(chat_obj):
    	msg_received = False # 是否收到消息的状态标志
    	msg = '' # 消息
    	
    	@bot.register(chat_obj) # 这里相当于为一个聊天对象注册了一个回调函数,当收到来自它的消息时回调
    	def getMsg(in_msg):
    	    msg_ = in_msg
    	    msg_received = True # 改变状态标志
    	
    	def query(question):
    	    chat_obj.send_msg(question)
    	    while msg_received != True:
    	        pass # 等待,直到状态标志改变
    	    msg_received = False
    	    return  msg
        return main_fun
    

    如果这段逻辑是用 js 实现的,那么将不会有任何问题。
    然而他是 Python,异常跳出来:

    NameError: name ‘msg_received’ is not defined

    主要原因还是我的 Python 基础太差,没有系统地学过,只是日常调调包之类的。
    如果出了问题,那就查一查,现学。
    甚至差到什么程度呢?就在不久之前,我才刚刚知道 try 是用来做什么的。

    回过头来看这个异常,他说 query 函数里的 msg_received 没有被定义。
    我满脑子问号,难道你不应该往外部作用域去找它吗?
    简单翻了翻脑子里残缺的 Python 基础语法,我想到了 global,于是我在函数开头用 global 声明了 msg 和 msg_received。
    结果当然还是出错,但是现在想来,我解决问题的思路是正确的,只不过语法知识不够。
    于是开始找资料来看,找着找着就翻这篇博客 → https://www.jianshu.com/p/17a9d8584530
    讲的很好,解决问题的方法也很简单,在函数开头用 nolocal 来声明变量是“非局部变量”就好了。
    这时候写惯了 js 的我才意识到,Python 的变量作用域机制与 js 有些许不同。


    Python 与 js 的闭包

    简单来说就是,如果什么也不做,那么 Python 会优先定义局部变量,而不是从外部作用域寻找,这与 js 刚好相反。

    看一段 Python 代码:

    def fun():
        b = 1
        def getb():
            return b
        return getb
    
    getb = fun()
    print(getb())
    

    输出:

    1

    非常正常,没错吧!

    那么我们再来看一段 Python 代码:

    def fun():
        b = 1
        def getb():
            b += 1
            return b
        return getb
    
    getb = fun()
    print(getb())
    

    出错了

    UnboundLocalError: local variable ‘b’ referenced before assignment

    他的意思是,我在使用 b 之前没有赋值。
    如同我在开头说的,Python 会优先定义局部变量,当 b 成为一个左值时,Python 的解释器会直接将其作为一个局部变量。
    所以我们在给 b 赋值的时候就不能在表达式中引用 b。


    再来看一段 Python 代码:

    def fun():
        b = 1
        def getb():
            b = 2
            return b
        return getb
    
    getb = fun()
    print(getb())
    

    输出:

    2

    举这个例子的目的是说明和提醒,这个输出的 2 并不是外部作用域中的 b,而是 getb 函数中的局部变量 b。


    所以,正确的写法应该是:
    def fun():
        b = 1
        def getb():
        	nonlocal b
            b += 1
            return b
        return getb
    
    getb = fun()
    print(getb())
    

    与 Python 不同,在 js 中,解释器会优先从外部作用域中寻找

    我感觉 Python 这种做法是在助长一种非常不好的编程习惯,任何情况下都不应该在闭包函数中声明一个与外部作用域名称相同的变量。
    在 js 中,如果我的闭包函数需要用到一个与外部作用域的变量意义相同的局部变量,那么我会在其前加上一个 local_ 前缀用于区分。


    就这样吧。

  • 相关阅读:
    python面试题收集
    servlet之小demo(四)
    servlet之ServletRequest与ServletResponse (三)
    servlet之servlet(二)
    hyperopt中文文档:Recipes
    hyperopt中文文档:Recipes
    hyperopt中文文档:Parallelizing-Evaluations-During-Search-via-MongoDB
    hyperopt中文文档:Parallelizing-Evaluations-During-Search-via-MongoDB
    hyperopt中文文档:Interfacing-With-Other-Languages(在其他语言中使用hyperopt)
    hyperopt中文文档:Interfacing-With-Other-Languages(在其他语言中使用hyperopt)
  • 原文地址:https://www.cnblogs.com/gaolihai/p/13149758.html
Copyright © 2020-2023  润新知