语言基础篇14——Python源文件组织结构,模块与包、命名空间与作用域的奥秘

news2025/1/22 12:22:17

源文件组织结构

模块与包

模块,module

一个文件,包含Python源码,以.py为后缀

包,package

结构化模块,一个文件夹,包含__init__.py文件、若干.py文件和若干文件夹

module对象

模块和包被导入后为module对象,module名称可以通过__name__属性获取

模块与包导入的优先级

详见另一篇文章:Python同名包和模块如何处理

自定义包

详见另一篇文章:如何写一个Python三方包供别人使用

命名空间与作用域

TLDR

  • Python中根据模块、类、函数或方法划分命名空间和作用域(从动态角度看每个对象都会形成一个命名空间)。
  • 命名空间是标识符到对象的映射,作用域是命名空间中标识符可以作用生效的一段Python代码区域。
  • 命名空间可以分为内置命名空间、全局命名空间、局部命名空间,作用域粗略可以分为全局作用域、局部作用域,详细可以分为内置作用域、全局作用域、嵌套作用域、局部作用域。
  • 命名空间之间不交叉且是嵌套结构,作用域之间交叉且是线性结构。
  • 查找某个作用域使用的标识符时,以模块、类、函数或方法为界限自内而外(从小到大)一层层查找即可。

命名空间

命名空间是一个标识符集合,同一命名空间标识符名称必须不同,不同命名空间标识符名称可以相同,命名空间用于解决标识符命名冲突问题。函数builtins.open()与函数os.open()就是通过命名空间进行区分。

在Python中,命名空间以字典的形式实现,是标识符到对象的映射。

内置命名空间,built-in namespace

内置命名空间即builtins模块中所有定义的标识符组成的集合,在解释器启动时创建,在解释器退出时销毁。

全局命名空间,global namespace

全局命名空间即某个模块中所有定义的标识符组成的集合,在模块导入时创建,在解释器退出时销毁。

局部命名空间,local namespace

局部命名空间即函数中或类中所有定义的标识符组成的集合,在调用函数或使用类时创建,在调用完毕或使用完毕时销毁。

命名空间示例解析

以person.py文件为例:

  • 各个命名空间之间是不交叉的。
  • 内置命名空间由bulitins模块中定义的标识符构成。
  • 全局命名空间person由下方紫色区域中定义的标识符构成,特别注意,if __name__ == '__main__'块中定义的标识符属于全局命名空间person,习惯上,会将if __name__ == '__main__'块下逻辑抽出为main函数以将全局标识符转换为局部标识符来减少命名空间污染。
  • person模块中使用了datetime中定义的的标识符。
  • Person类构成局部命名空间,由黄色部分定义的标识符组成。
  • Person类中__init__方法构成局部命名空间,由红色部分定义的标识符组成。
  • get_a_person函数构成局部命名空间,由蓝色部分定义的标识符组成。
  • get_a_name函数构成局部命名空间,由灰色部分定义的标识符组成。
  • mian函数构成局部命名空间,由绿色部分定义的标识符构成。
  • 特别注意,上述定义的标识符指一般本地标识符的定义,例如蓝色部分second_name或绿色部分p,红色部分中self.name并非局部命名空间的标识符,但函数参数name属于该局部命名空间。
  • 图中以代码文本展示命名空间中标识符源于何处,谨记命名空间是标识符集合。

在这里插入图片描述

作用域

A scope is a textual region of a Python program where a namespace is directly accessible.

作用域是可以直接访问命名空间的一段Python程序文本区域,亦即命名空间中标识符可以作用生效的一段Python代码区域。

注:python-scopes-and-namespaces中描述了四个层次的作用域,官方并未命名,本文擅自命名。

内置作用域

内置作用域即解释器一次执行期间所有加到的py文件。

全局作用域

全局作用域即某个模块所有代码区域,对应于一整个py文件。

嵌套作用域

嵌套作用域即类或函数多层嵌套结构形成的代码区域(不包含局部作用域),嵌套作用域介于全局作用域和局部作用域之间,嵌套作用域是相对于类或函数的。

局部作用域

局部作用域即函数或类本身结构形成的代码区域,局部作用域是相对于类或函数的。

作用域示例解析

以person.py文件为例:

  • 各个作用域之间是交叉的。
  • 内置作用域,person.py文件中所有文本区域均可以访问内置命名空间的标识符,对应图中红色扫过的行,但内置作用域不仅仅是person.py文件,而是包含一次解释器启动期间所有加载到的py文件。
  • 全局作用域,person模块对应的person.py中所有文件区域均可以访问内置命名空间的标识符和全局命名空间的标识符,对应图中的紫色扫过的行。
  • 嵌套作用域,get_a_name为嵌套在get_a_person内部的函数,则get_a_name函数向外一层为最近的一个嵌套作用域,对应蓝色扫过的行,同理,绿色扫过的行构成的作用域为函数__init__的嵌套作用域,嵌套作用域可以访问内置命名空间的标识符、全局命名空间的标识符和嵌套作用域更上层嵌套作用域可以访问的局部命名空间中的标识符(例如最近嵌套作用域的最近嵌套作用域,或者再嵌套若干层),当讨论的局部作用域确定,则其与全局作用域之间的作用域均为嵌套作用域。
  • 局部作用域,绿色、黑色、蓝色、橙色和黄色扫过的行均为局部作用域,可以访问局部命名空间的标识符、全局命名空间的标识符、内置命名空间的标识符,局部作用域是相对的,讨论黑色扫过的行中的标识符,以黑色扫过的行为局部作用域,则嵌套作用域为绿色扫过的行,讨论绿色扫过的行中的标识符,以绿色扫过的行为局部作用域,无嵌套作用域,向外一层即为全局作用域。
  • 讨论范围是默认以当前模块为全局,内置作用域和全局作用域是绝对的,嵌套作用域和局部作用域是相对的,当讨论的函数或类确定时,局部作用域和嵌套作用域才能确定。
  • 标识符查找顺序为:由内而外,从小到大,局部作用域 -> 嵌套作用域 -> 全局作用域 -> 内置作用域,如get_a_name函数中的second_name,首先get_a_name所在局部作用域决定可以访问局部、全局和内置命名空间,依次从命名空间查找标识符,然后有该标识符命名空间对应的代码中定义了该标识符,或者由内而外从作用域也可查找标识符定义。

在这里插入图片描述

示例

一般情况下变量由内而外依次从作用域中查找,globalnonlocal

  1. 先在局部作用域查找变量。

    a = 1
    def print_local_num():
        a = 9  # 定义局部变量a
        print(a)  # 9
        
    print_local_num()
    print(f"{a=}")  # a=1 print_local_num改变的不是全局变量a
    
  2. 本地找不到向上层查找直至全局作用域,对变量只读。

    a = 1
    def print_global_num():
        print(a)
    
    print_global_num()  # 1
    
  3. 本地找不到上层可以找到,但在本地希望修改变量的情况下只会在本地查找,无则UnboundLocalError。

    a = 1
    def get_global_num():
        print(a)  # UnboundLocalError: cannot access local variable 'a' where it is not associated with a value
        a += 9
        return a
    
    get_global_num()
    
  4. 本地找不到全局可以找到,本地希望修改可以在本地声明变量为global。

    a = 1
    def print_global_num():
        # print(a)  # SyntaxError: name 'a' is used prior to global declaration
        global a  # 声明a为全局变量,不能在声明之前使用变量a
        a = 9
        print(a)  # 9
    
    
    print_global_num()  # 9
    print(f"{a=}")  # a=9 print_global_num改变的是全局变量a
    
  5. 无论嵌套多少层,全局就是全局。

    a = 1
    
    
    def func1():
        a = 0
    
        def func2():
            global a  
            a = 9
    
        print(a)  # 0
        func2()
        print(a)  # 0
    
    
    func1()
    print(f"{a=}")  # a=9
    
  6. 如果希望使用并修改嵌套作用域中定义的变量,使用nonlocal进行声明,表示非局部(也非全局)。

    a = 1
    
    
    def func1():
        a = 0
    
        def func2():
            nonlocal a  
            a = 9
    
        print(a)  # 0
        func2()
        print(a)  # 9
    
    
    func1()
    print(f"{a=}")  # a=1
    
  7. nonlocal逐层向上寻找变量的定义,直到全局作用域之前。

    a = 1
    
    
    def func1():
        global a
        print(a)
    
        # a = 1  # 即使在此处打开该语句注释下面也会报错,因为此处并非变量定义的地方
    
        def func2():
            # nonlocal会在最近上层寻找变量定义,此处上层仅仅声明使用全局变量a,并未定义,global a语句注释掉同理
            nonlocal a  # SyntaxError: no binding for nonlocal 'a' found
            a = 9
    
        func2()
        print(a)
    
  8. 多层嵌套,所有nonlocal声明的a均会查找到a=1处定义的a。

    a = 0
    def func1():
        # nonlocal a  # SyntaxError: no binding for nonlocal 'a' found
        a = 1
        def func2():
            nonlocal a
            print(a)  # 1
            a = 2
            def func3():
                nonlocal a
                print(a)  # 2
                a = 3
                def func4():
                    nonlocal a
                    print(a)  # 3
                    a = 4
                    def func5():
                        nonlocal a
                        print(a)  # 4
                        a = 5
                        print(a)  # 5
                        ...
                    func5()
                    print(a)  # 5
                func4()
                print(a)  # 5
            func3()
            print(a)  # 5
        func2()
        print(a)  # 5
    func1()
    print(a)  # 0
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/991011.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

保姆级-微信小程序开发教程

一,注册微信小程序 如果你还没有微信公众平台的账号,请先进入微信公众平台首页,点击 “立即注册” 按钮进行注册。注册的账号类型可以是订阅号、服务号、小程序以及企业微信,我们选择 “小程序” 即可。 接着填写账号信息&#x…

洗衣洗鞋小程序干洗店洗衣店上门取衣门店管理系统开发定制

校园洗衣洗鞋软件是一款非常优质的校园洗护服务软件,软件功能非常强大,学生们可以通过软件预约洗衣服务,支持上门取送,还能够浏览洗衣产品商城,操作非常便捷,感兴趣的朋友快来体验吧! 后台管理…

开发板插入sd/tf卡后自动挂载

测试平台-hisi-dv500 要在Linux系统上实现TF卡(Micro SD卡)插入后自动挂载,类似于SD卡/TF卡插入也会触发内核事件,你可以使用udev工具来监控并处理这些事件,创建一个udev规则文件来捕获TF卡插入事件. 1:创建一个udev规则文件,例…

飞书即时消息无需API开发连接Cohere,打造飞书AI智能问答助手

飞书即时消息用户使用场景: 许多企业都在使用飞书系统进行协同办公,而现在有了Cohere大语言模型技术,能够根据用户的提问来自动产生回答,无需人为干预。对于企业负责人来说,他们认为如果将Cohere技术融入到飞书机器人中…

SpringBoot+jSerialComm实现Java串口通信 读取串口数据以及发送数据

记录一下使用SpringBootjSerialComm实现Java串口通信,使用Java语言开发串口,对串口进行读写操作,在win和linux系统都是可以的,有一点好处是不需要导入额外的文件。 案例demo源码:SpringBootjSerialComm实现Java串口通信 读取串口…

easypoi导入数值精度丢失

记录一下easypoi导入数值,精度丢失的解决方案 1.导入的excel字段如图 2.easypoi解析CellValueService部分源码: 这个方法拿到的原始数据如图: 解决方法: 1.统一处理方式:在解析的时候使用DecimaFormat进行数据格式化 //格式化为6为小数 De…

Day57|leetcode 647. 回文子串、516.最长回文子序列

leetcode 647. 回文子串 题目链接:647. 回文子串 - 力扣(LeetCode) 视频链接:动态规划,字符串性质决定了DP数组的定义 | LeetCode:647.回文子串_哔哩哔哩_bilibili 题目概述 给你一个字符串 s ,…

使用动态住宅代理还能带来哪些好处?

一、什么是动态住宅代理ip 动态住宅代理是一种代理技术,它利用代理服务器中转用户和目标服务器之间的网络流量,实现用户真实位置的屏蔽。代理提供商会有自己的ip大池子,当你通过代理服务器向网站发送请求时,服务器会从池子中选中…

Spring系列文章:Spring6集成MyBatis3.5

1、引入依赖 <dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.2</version></dependency><dependency><groupId>org.mybatis</groupId><artif…

jsp页面出现“String cannot be resolved to a type”错误解决办法

篇首语&#xff1a;小编为大家整理&#xff0c;主要介绍了jsp页面出现“String cannot be resolved to a type”错误解决办法相关的知识&#xff0c;希望对你有一定的参考价值。 jsp页面出现“String cannot be resolved to a type”错误解决办法 解决办法&#xff1a; 右键项目…

软考高级架构师下篇-14面向服务架构设计理论

目录 1. 引言2. SOA的相关概念3. SOA的发展历史4. SOA的参考架构5. SOA 主要协议和规范6. SOA设计的标准要求7. SOA的作用与设计原则8. SOA的设计模式9. SOA构建与实施10. 前文回顾1. 引言 在面向服务的体系结构(Service-Oriented Architecture,SOA)中,服务的概念有了延伸…

详解:API开发【电商API封装商品数据SKU接口的开发接入】

电商API开发8.1 RESTful API的设计8.2 API的路由和控制器8.3 API的认证和授权 RESTful API的设计 RESTful API是一种通过HTTP协议发送和接收数据的API设计风格。它基于一些简单的原则&#xff0c;如使用HTTP动词来操作资源、使用URI来标识资源、使用HTTP状态码来表示操作结果等…

Java多线程(四)锁策略(CAS,死锁)和多线程对集合类的使用

锁策略&#xff08;CAS&#xff0c;死锁&#xff09;和多线程对集合类的使用 锁策略 1.乐观锁VS悲观锁 2.轻量级锁VS重量级锁 3.自旋锁VS挂起等待锁 4.互斥锁VS读写锁 5.可重入锁vs不可重入锁 死锁的第一种情况 死锁的第二种情况 死锁的第三种情况 CAS 1.实现原子类 …

苹果发布会:iPhone15系列

苹果将在北京时间9月13日凌晨1点召开发布会&#xff0c;本次发布会的主角是iPhone 15系列&#xff0c;包含四款机型&#xff1a;iPhone 15、iPhone 15 Plus、iPhone 15 Pro 以及 iPhone 15 Pro Max&#xff0c;本次发布会快科技全程视频直播&#xff0c;有关产品的细节也会在新…

四川百幕晟科技:抖音新店怎么快速起店?

抖音作为全球最大的短视频平台&#xff0c;拥有庞大的用户基础和强大的影响力&#xff0c;成为众多商家宣传产品、增加销量的理想选择。那么&#xff0c;如何快速开店并成功运营呢&#xff1f;下面描述了一些关键步骤。 1、如何快速开新店&#xff1f; 1、确定产品定位&#x…

系列一、前言

本系列文章是参考B站尚硅谷老师讲的 "尚硅谷Nginx教程由浅入深&#xff08;一套打通丨初学者也可掌握&#xff09;"系列课程&#xff0c;然后结合自己真实的操作而总结的系列文章。我也把自己学习、实操过程中的详细笔记以脑图的形式分享了出去&#xff0c;发现大家对…

SpringMVC实战crud增删改查

一.公共页面的跳转 1.编写页面跳转控制类 package com.YU.web;import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping;/*** author YU* create …

Apache Linki 1.3.1+DataSphereStudio+正常启动+微服务+端口号

我使用的是一键部署容器化版本&#xff0c;官方文章 默认会启动6个 Linkis 微服务&#xff0c;其中下图linkis-cg-engineconn服务为运行任务才会启动,一共七个 LINKIS-CG-ENGINECONN:38681 LINKIS-CG-ENGINECONNMANAGER:9102 引擎管理服务 LINKIS-CG-ENTRANCE:9104 计算治理入…

Linux 中的 chpasswd 命令及示例

chpasswd命令用于更改密码,尽管passwd命令也可以执行相同的操作。但它一次更改一个用户的密码,因此对于多个用户,使用chpasswd 。下图显示了passwd命令的使用。使用passwd我们正在更改来宾用户的密码。首先,您必须输入当前签名用户的密码,然后更改任何其他用户的密码。必须…

文本识别 (OCR)引擎之Tesseract的使用

Tesseract OCR Tesseract概述常见OCR识别平台下载安装配置命令使用语法测试验证 Tesseract的使用安装python库基本使用可能的异常更换语言字体库识别 Tesseract的训练 Tesseract 概述 Tesseract是一个开源文本识别 (OCR)引擎&#xff0c;是目前公认最优秀、最精确的开源OCR系统…