29期第十七周笔记

Week 17

本周学习主要内容包括Linux操作系统(剩余部分)、用户权限系统、文本处理、坏境变量、模块化

Linux操作系统

inode

  • 磁盘中每个分区可以使用不同的文件系统格式(如FAT、ext2),文件系统格式化后,一般会将扇区分组,在ext中这种分组称为块(block)

  • 文件系统格式化后还要保留一部分区域放元数据,这个区域就成为inode table

  • 一般inode表会占用磁盘空间的1%,所以inode记录数有限制,如果文件太多,inode表空间用完,但分区还有剩余磁盘空间。因此!!小文件多意味着inode记录增加,要适当增加inode空间

  • 新建文件会在元数据区增加一个元数据条目inode entry在这里插入图片描述

  • inode条目中保存了除文件名以外的属性信息,元数据中还有文件的块指针

    • 假设1 block = 4KB
    • 12个直接块(direct blocks)地址条目,一个条目一个指针,总共12个指针(地址值),12*4 = 48KB
    • L1一个地址条目:条目指向一个块4KB,这4KB用来记录其他的块地址,4KB一个块地址,总共1K个,4KB * 1K = 4MB
    • L2一个地址条目:1K * 4MB = 4GB
    • L3一个地址条目:1K * 4GB = 4TB
  • 每一个文件包括目录,都是文件,都有inode entry,都有唯一的inode号

  • 目录也是文件,在目录文件本身的数据块上存储着其下文件的文件名到inode号映射信息

  • block bitmap(块位图):记录哪些block空闲或已使用

  • inode bitmap:记录哪些inode空闲或已使用

  • inode不跨分区

  • 查看inode可以用ls -i

文件查找

  • / 的inode在ext文件系统中是保留的,inode是2
  • 假设需要查找/d1/f1,先找到inode表中2对应的block。/ 是目录,所以块信息中存储的是他里面的子目录或文件的inode号和文件名,匹配d1文件名后就知道他的inode号
  • 再去inode’表中找匹配到d1的inode号对应的inode entry中块信息,然后读取;由于d1也是目录,从中找出f1和f1的inode
  • 使用f1的inode去inode表中找到f1的块信息,就可以读取f1的数据
  • 注:文件名是目录的数据,也就是说存在目录的块上

硬链接

  • 如果两个文件没有跨分区,且两个文件指向同一个inode,就是硬链接,及文件的引用计数+1
  • 即,使用了不同文件名指向同一个inode,就指向同一个文件的块,也就是一个物理的磁盘数据有了访问的路径
  • ln test test1 来创建文件test1,硬链接数+1,如果硬连接数为0,则文件被清除
  • 符号链接和硬链接完全没有关系:符号链接因为记录路径字符串,所以可以写任意合法路径字符串

进程的状态返回值

  • 每个进程可以有输入和输出,但是每一个进程还可以有一个状态返回值来表示进程是否成功执行
  • 0在绝大多数操作系统中都表示成功
  • 非0表示进程一定没有成功执行,但非0数具体表示什么需要查看不同程序的帮助文档或编程手册
  • 查看状态返回值:echo $?

文件描述符(File Descriptor)

一切皆文件,每一个打开的文件分配一个文件描述符,非负整数,文件描述符是有限的资源

文件描述符名称常用缩写默认值
0标准输入stdin键盘
1标准输出stdout屏幕
2标准错误输出stderr屏幕
  • 每一个进程系统都会为该进程维护一个文件描述符表,该表都是从0开始
  • 进程没打开一个文件就会有一个文件描述符和对应记录
  • 同一个进程对同一个文件的打开也会获得不同描述符
  • 不同进程各自的文件描述符表是独立的,没有关系(例:两个不同进程描述符表中都有描述符4,不能说明什么)
  • 文件描述符是一个非负整数,本质就是文件描述表的索引,每一条描述表表项指向内核为所有进程所维护的打开文件的文件记录表。打开文件表每条表项记录着打开文件的信息(文件偏移量、打开模式mode、i-node表指针等),i-node指向i-node表文件

管道

使用 |:是一个程序的标准输出通过管道送给下一个命令的标准输入

重定向

重定向就是改变程序的输入或输出

  • :输出重定向

  • :追加输出重定向

  • <:输入重定向

  • <<:Here Document
    输出重定向和输出追加重定向

    echo abc > test

    cat test

    abc

    echo 123 >> test

    cat test

    abc
    123

    echo xyz > /dev/null

    ls > 2 把ls的输出结果重定向到文件2中

    ls >&2 把ls的输出结果重定向标准错误输出

    ls -l > files 2>&1

    id aabb 没有aabb用户,报错,走stderr

    id aabb > files 标准输出重定向 files文件

    cat files 有没有结果?

    id aabb > files 2>&1

    cat files 有没有结果?

    id aabb 2>&1 > files

    cat files

  • files 2>&1 表示1先指向文件,2指向1,就指向该文件

  • 2>&1 > files 表示2指向1即stdout,然后1重新指向一个文件

输入重定向
# ll | wc -l
# ll > files
# wc -l files
# wc -l < files

Here Document
# cat > newtxt << EOF
> line1
> line2
> EOF
# cat newtxt

用户权限系统

用户和组

*nix是多用户系统

  • 用户
    • 管理员
      • root用户是必须有的管理员账户,特权用户,无所不能,慎用!!
      • uid为0
      • 家目录在/root
    • 系统用户
      • 普通授权用户,不用登录,一般不需要家目录
      • uid 1-499,CentOS7 1-999
    • 普通用户
      • 普通授权用户,交互式登录,需要家目录在 /home/
      • uid 500+,CentOS 1000+
    • 用户id为2字节无符号整数,0是root,其他用户使用1-65535
    • 用户属于不同的组
    • 管理员组
      • 名为root,gid为0
    • 系统组
      • 普通组,gid为1-499,CentOS7 1-999
    • 普通组
      • 普通组,gid为500+,CentOS 1000+
  • 用户的组
    • 主要组、基本组primary group
      • 每一个用户都仅能属于一个组,这个组称为主组,创建用户是指定
    • 附加组supplementary group
      • 一个用户可以属于零个或多个附加组

主要配置文件

  • /etc/passwd:保存用户及id、主组id等属性信息
    • 登录名:密码(shadow中):uid:gid 主组:注释:家目录:shell
  • /etc/shadow:保存用户密码及相关属性,该文件权限为0
  • /etc/group:组及属性信息
    • 组名:组密码(gshadow中):gid:以当前组为附加组的用户列表逗号分隔

用户和组命令

  • 用户、组
    • id
      • id:当前用户
      • id 617:指定用户信息
      • -u:用户id;-g:主组;-G:主组和附加组;-n:使用名字
  • useradd
    • -r:创建系统用户,默认没有家目录

    • -u:UID指定uid

    • -g:GID指定主组,如果没有指定,则创建与用户名相同的同名主组

    • -m:系统用户默认不创建家目录,使用该选项创建家目录

    • -M:非系统用户默认创建家目录,使用该选项则不创建
      - -d:指定家目录
      - -s:指定shell,查看 /etc/shells
      - -G:g1[,g2,…] 指定用户的附加组,附加组必须存在

        # useradd -r redis								创建redis组和redis系统用户
        # useradd -r -s /sbin/nologin redis		创建系统不可登录用户redis
        # userdel redis
      
        # useradd 617										创建617组和617普通用户,创建家目录
      
    • su:切换用户

      • su -617:常用!!!!登陆式切换,读取该用户的配置文件,并切换到该用户的家目录
      • su 617:用得少,非登陆式切换,不读取用户配置文件,也不改变当前工作目录
        • su 617 -c “id”:常用,只是为了使用617用户启动一个程序,没必要登录

      • 当前root用户,su切换到其他任何用户是,无需密码,但其实用户切换都需要密码
    • passwd

      • passwod:当前用户修改密码
      • echo 617 | passwd 617 --stdin:为617用户设置密码

      • 密码应该足够强或者有过期
      • -d:删除密码;-l:锁定用户;-u:解锁用户
      • -x maxdays:密码最大使用期限,超过宽限期后用户将不能自己修改密码
      • -w warndays:提前多少天警告密码要到期了;-i inactivedays:超期后修改密码的宽限期

usermod修改用户、userdel删除用户
groupadd增加组、groupmod修改组、groupdel删除组

进程的安全上下文

  • 每一个进程启动,都有一个用护身符,这个身份决定着进程能访问什么资源
  • 文件的元数据中定义了权限mode,三部分为属主、属组、其他。
  • 当一个进程访问一个资源是,首先进行属主权限匹配,未匹配再进行属组、附加组权限匹配
  • 如果都没匹配到,那么就是other其他用户,就匹配mode中的最后部分

文件权限含义

mode文件目录
r查看文件内容ls列目录内容
w修改文件内容目录中增删文件
x执行该文件启动一个进程ls -l 列目录内容,cd进入该目录

权限命令

  • chmod
    • -R:目录递归设置,不推荐
    • chmod [OPTION]… OCTAL-MODE FILE… 使用3位数八进制数修改文件mode
    • chmod [OPTION]… MODE[,MODE]… FILE…
      • 修改某一组3位权限u、g、o、a
        • u=、g=、o=、a=、ug=、uo=、go=
      • 修改组内3位中的某一位
        • u+、g+、o+、a+、+
        • u-、g-、o-、a-、-
  • umask
    • 目录一般有x才能cd进入,文件一般不要随便给x权限
    • 默认情况下,管理员的umask是022;普通用户002
    • 文件是666-022=644
    • 目录是777-022=755
    • umask 022修改掩码,非永久修改

文本处理

文本命令

  • vi、vim

  • grep支持BRE、ERE

    • man grep有BRE、ERE说明
    • -E 等价于egrep命令
    • -v 反转结果
  • sed文本处理工具

    • -n 只显示匹配的行
  • awk自成体系可编程的文本处理工具

    • 基本格式,语句要用大括号抱起来,多条语句用分号分割

    $ awk [选项] ‘{语句}’ [文件]

    • BEGIN {语句} 开始时执行一次
    • END {语句} 结束时执行一次
    • -F:指定字段分隔符,默认相当于 \s+
    • -v:通过选项定义变量
    • NF:字段数;NR:行序号
    • OFS:输出字段分隔符
    • 假设NF为7,$NF是取第七列
  • wc -l

  • cut

    • 使用逗号切显示所有列
    • –output-delimiter:改变输出分隔符
  • sort、uniq

    • sort -r:降序排列
    • sort -u:uniq去重
    • sort -nr:数字降序

vim使用

3种模式:命令模式、输入模式、底线模式

命令模式

启动vim后就进入命令模式,此时不能编辑,接受键盘键入命令

  • 删除:
    • x或delete删除当前字符、前删X
    • d0删除到行首
    • d$或D删除到行尾
    • dd删除当前行,ndd从当前行开始删n行,dG从当前行删除到末尾
  • u撤销
  • gg首行,G末行,20G或者20gg跳至第20行
  • 0本行行首,$本行倒数第一个字符
  • 回车向下移动一行,3回车向下移动三行
  • 空格向右移动1个字符,3空格向右移动三个字符
  • 复制
    • yy复制当前行,nyy从当前行开始复制n行
    • y$复制到行尾
    • yG复制到末尾
  • p光标后粘贴
  • ?/ 切换到底线进入搜索模式,输入搜索词并回车,N或n移动到上一个或下一个
  • :切换到底线模式
  • 进入编辑模式
    • i当前光标是,I行首
    • a当前光标后,A行尾
    • o当前行下一行,O上一行

编辑模式

底线上出现“–insert–”,说明进入插入模式,即可编辑模式,这是可以在文本中进行编辑,接收键盘敲击的字母都是文本字符,此时vim就是普通的文本编辑器。ESC退出编辑模式

底线模式

命令模式中输入:,光标来到最下面一行就可以输入命令

  • w写文件

  • q退出vim,q!强制退出不保存

  • x等价于wq(写入并退出)

  • n跳到指定第n行

  • . 表示当前行

  • .,5d 表示从当前行到第五行删除,.,$d 从当前行到EOF删除,%d 全文删除

  • ESC键推出底线模式

  • :set nu 加行号;:set nonu 去除行号

  • :nohls 临时取消高亮搜索显示

  • [range]s/patten/string/flags

    s/ /#/ 单行当前行替换空格为#
    s/ /#/gi 替换当前行全部空格为#,忽略大小写
    50,60s/ /#/g 50到60行每行全部替换空格为#
    %s/ /\n/g 所有行每行全部替换空格为换行符

  • $ vim test + 5打开vim并直接定位到指定行

环境变量

分类

作用域

  • 系统环境变量:当前系统所有用户都可以访问的环境变量
  • 用户环境变量:当前用户可以访问的环境变量

生命周期

  • 临时环境变量:使用export k1=v1 命令执行后创建的环境变量,当前shell有效,shell进程消亡史失效
  • 永久环境变量:配置文件中配置的环境变量

配置文件

  • 全局配置
    • 环境变量可以卸载/etc/profile中,推荐写在/etc/profile.d/xxx.sh中,这是全局配置
    • /etc/bashrc也是全局配置,一般配置别名和系统函数
  • 用户配置
    • ~/.bashrc,配置别名或函数
    • ~/.bash_profile,一般配置环境变量
  • 如果需要立即执行配置文件使用source命令或 . 点号
  • 交互式登录(直接登录或su - python),先读取/etc/profile,在读取~/.bash_profile,但是在.bash_profile会source ~/.bashrc,而在 ~/.bashrc中,会source /etc/bashrc。退出登录,还会读取 ~/.bash_logout
  • su python 只读取 ~/.bashrc,其中会source /etc/bashrc

PATH路径

PATH是一个全局环境变量,可以全局修改配置,也可以当前用户修改配置。路径间隔使用冒号。外部命令搜索路径,从前向后逐个查找PATH中的路径,找到立即执行,找不到报命令未找到

环境变量相关命令

  • export:查看或临时导出环境变量
  • env:显示所有环境变量
  • printenv:i暗示所有环境变量
  • set:显示本地定义的所有变量,包括环境变量
  • unset:移除变量
  • 反引号和$()都是用来取命令返回的正确结果

其他命令

  • tar:打包或解压缩包
  • rzsz:
    • rz :接收文件
    • sz :发送文件

模块化

Python中只有一种模块对象类型,但为了模块化组织模块的便利,提供了“包”的概念

  • 模块module:指的是Python得源代码文件
  • 包package:指的是模块组织在一起的和包同名的目录及其相关文件

导入语句

语句含义
import 模块1[,模块2,…]完全导入
import … as …模块别名
  • import语句:
    1. 找到指定的模块,加载和初始化它,生成模块对象;找不到抛出异常
    2. 在import所有的作用域得局部明明空间中,增加名称和上一步创建的对象关联
# import os  #os 导入os,加载os
# print(os.path) #模块类型

# import os.path 
# print(os.path) #模块类型

import os.path as osp
print(osp)
print(osp.exists)

print(dir())

总结

  • 导入顶级模块,其名称会加入到本地名词空间中,并绑定到其模块对象
  • 导入非顶级模块,之将其顶级模块名称加入到本地名词空间中。导入的模块必须使用完全限定名称来访问
  • 如果使用了as,as后的名称直接绑定到导入的模块对象,并将该名称加入到本地名词空间中
  • import之后只能是模块类型
语句含义
from … import …部分导入
from … import … as …别名
# from functools import wraps #模块加载了吗?模块没有从磁盘加载哪里来的函数对象?

# from os import path #from...import 后,可以是谁?os和path都是模块,wraps函数,Path类
# from pathlib import Path

from os.path import exists #from ... import,from后面必须是模块

# print(exists('c:/temp'))

## 五种方法获得同一个对象exists
print(dir()) #t1
print('-'*30)
print(1,exists)
import os
print(2,os.path.exists)
import os.path
print(3,os.path.exists)
print(4,os.path.__dict__['exists']) #字符串
print(5,getattr(os.path,'exists')) #字符串

总结

  • 找到from子句中指定的模块,加载并初始化他(注意不是导入)
  • 对于import子句后的名称
    1. 先查from字句导入的模块是否具有该名称的属性
    2. 如果不是,则尝试导入该名称的子模块
    3. 还没有找到,则抛出ImportError异常
    4. 这个名称保存到本地名词空间中,如果有as子句,则使用as子句后的名称

自定义模块

.py文件就是一个模块

命名规范

  1. 模块名就是文件名
  2. 模块名必须符合标识符的要求,是非数字开头的字母、数字和下划线组合。test-module.py这种就不符合要求不能作为模块名,也不能使用中文
  3. 不要使用系统模块名来避免冲突,除非知道这个模块名的用途
  4. 通常模块名为全小写,下划线来分割

模块搜索顺序

sys.path查看搜索顺序

import sys

print(*sys.path,sep='\n')
---------------------
## 运行结果
C:\Users\98669\PycharmProjects\week18\venv\Scripts\python.exe C:/Users/98669/PycharmProjects/week18/t3.py
C:\Users\98669\PycharmProjects\week18
C:\Users\98669\PycharmProjects\week18
C:\Python\Python37\python37.zip
C:\Python\Python37\DLLs
C:\Python\Python37\lib
C:\Python\Python37
C:\Users\98669\PycharmProjects\week18\venv
C:\Users\98669\PycharmProjects\week18\venv\lib\site-packages
C:\Users\98669\PycharmProjects\week18\venv\lib\site-packages\setuptools-40.8.0-py3.7.egg
C:\Users\98669\PycharmProjects\week18\venv\lib\site-packages\pip-19.0.3-py3.7.egg
  • 显示结果为python模块的路径搜索顺序
  • 但加载一个模块的时候,需要从这些搜索路径中从前向后依次查找,并不搜索这些目录的子目录,搜索到模块就加载,搜索不到就抛异常
  • 路径也可为字典、zip文件、egg文件
  • .egg文件,由setuptools库创建的包,第三方库常用的各式,添加了元数据(版本号、依赖项等)信息的zip文件
  • 路径顺序为:
    1. 程序主目录,程序运行的主程序脚本所在的目录
    2. PYTHONPATH目录,环境变量PYTHONPATH设置的目录也是搜索
    3. 标准库目录,python中自带的库模块所在的目录
  • sys.path可以被修改,增加新的目录

模块的重复导入

  • 不会产生重复导入的现象
  • 所有加载的模块都会记录在sys.modules中,sys.modules是存储已经加载过的所有模块的字典
  • 打印sys.modules可以看到builtins、os、os.path、sys等模块都已经加载了
import sys
print(*sys.modules.items(),sep='\n')
------------------------------------------
#运行结果
('sys', <module 'sys' (built-in)>)
('builtins', <module 'builtins' (built-in)>)
('_frozen_importlib', <module '_frozen_importlib' (frozen)>)
('_imp', <module '_imp' (built-in)>)
('_thread', <module '_thread' (built-in)>)
('_warnings', <module '_warnings' (built-in)>)
('_weakref', <module '_weakref' (built-in)>)
('zipimport', <module 'zipimport' (built-in)>)
('_frozen_importlib_external', <module '_frozen_importlib_external' (frozen)>)
('_io', <module 'io' (built-in)>)
('marshal', <module 'marshal' (built-in)>)
('nt', <module 'nt' (built-in)>)
('winreg', <module 'winreg' (built-in)>)
('encodings', <module 'encodings' from 'C:\\Python\\Python37\\lib\\encodings\\__init__.py'>)
('codecs', <module 'codecs' from 'C:\\Python\\Python37\\lib\\codecs.py'>)
('_codecs', <module '_codecs' (built-in)>)
('encodings.aliases', <module 'encodings.aliases' from 'C:\\Python\\Python37\\lib\\encodings\\aliases.py'>)
('encodings.utf_8', <module 'encodings.utf_8' from 'C:\\Python\\Python37\\lib\\encodings\\utf_8.py'>)
('_signal', <module '_signal' (built-in)>)
('__main__', <module '__main__' from 'C:/Users/98669/PycharmProjects/week18/t3.py'>)
('encodings.latin_1', <module 'encodings.latin_1' from 'C:\\Python\\Python37\\lib\\encodings\\latin_1.py'>)
('io', <module 'io' from 'C:\\Python\\Python37\\lib\\io.py'>)
('abc', <module 'abc' from 'C:\\Python\\Python37\\lib\\abc.py'>)
('_abc', <module '_abc' (built-in)>)
('site', <module 'site' from 'C:\\Python\\Python37\\lib\\site.py'>)
('os', <module 'os' from 'C:\\Python\\Python37\\lib\\os.py'>)
('stat', <module 'stat' from 'C:\\Python\\Python37\\lib\\stat.py'>)
('_stat', <module '_stat' (built-in)>)
('_collections_abc', <module '_collections_abc' from 'C:\\Python\\Python37\\lib\\_collections_abc.py'>)
('ntpath', <module 'ntpath' from 'C:\\Python\\Python37\\lib\\ntpath.py'>)
('genericpath', <module 'genericpath' from 'C:\\Python\\Python37\\lib\\genericpath.py'>)
('os.path', <module 'ntpath' from 'C:\\Python\\Python37\\lib\\ntpath.py'>)
('_sitebuiltins', <module '_sitebuiltins' from 'C:\\Python\\Python37\\lib\\_sitebuiltins.py'>)
('_bootlocale', <module '_bootlocale' from 'C:\\Python\\Python37\\lib\\_bootlocale.py'>)
('_locale', <module '_locale' (built-in)>)
('encodings.cp1252', <module 'encodings.cp1252' from 'C:\\Python\\Python37\\lib\\encodings\\cp1252.py'>)
('encodings.cp437', <module 'encodings.cp437' from 'C:\\Python\\Python37\\lib\\encodings\\cp437.py'>)

模块运行

  • __name__:不建议更改,每个模块都有定义一个__name__,不指定则默认为源代码文件名,如果是包则有限定名
  • 解释器初始化时会初始化sys.modules字典(保存已加载过的模块),加载builtins(全局函数、常量)模块、__main__模块、sys模块及初始化模块搜索路径sys.path
  • Python是脚本语言,任何一个脚本都可以直接执行,也可以作为模块被导入
  • 当从标准输入(命令行方式)、脚本($ python test.py)或交互式读取的时候,会将模块的__name__设置为__main__,模块的顶层代码就在__main__作用域中执行
  • 顶层代码:模块中缩进最外层的代码
  • 如果是import导入的,其__name__默认就是模块名

if __name__ == '__main__':用途

  1. 本模块的功能测试
    对于肥猪模块,测试本模块内的函数、类
  2. 避免主模块变更的副作用
    顶层代码,没有封装,主模块使用是没有问题;但一旦有了新的主模块,老的主模块成了被导入模块,由于原代码没有封装,一并执行。

模块的属性

属性含义
__file__字符串,源文件路径
__cached__字符串,编译后的字节码文件路径
__spec__显示模块的规范
__name__模块名
__package__当模块是包,同__name__;否则可以设置为顶级模块的空字符串

  • 即特殊模块
  • PyCharm中船舰Directory(目录)和船舰Python package(包)不同,前者是创建普通的目录,后者是创建一个带有__init__.py 文件的目录(即包)
  • Python中,目录可以作为模块,这就是包,不过代码需要卸载该目录下__init__.py
  • 包的__file__.就指向__init__.py这个文件
# import m #m/__init__.py执行,m的子包、子模块没有执行,未加载#m.m1访问模块m下的m1模块
import m.m1 #import os.path dir

print('-'*30)
print(1,*filter(lambda x:x.startswith('m'),dir()))

# 加载了吗?缓存
import sys
print(2,*filter(lambda x:x[0].startswith('m'),sys.modules.items()),sep='\n')

print('='*30)
print(m.A)
print(m.m1)

# 运行结果
------------------------------
1 m
2
('marshal', <module 'marshal' (built-in)>)
('m', <module 'm' from 'C:\\Users\\98669\\PycharmProjects\\week18\\m\\__init__.py'>)
('m.m1', <module 'm.m1' from 'C:\\Users\\98669\\PycharmProjects\\week18\\m\\m1\\__init__.py'>)
==============================
<class 'm.A'>
<module 'm.m1' from 'C:\\Users\\98669\\PycharmProjects\\week18\\m\\m1\\__init__.py'>

子模块

  • 包目录下的py文件、子目录都是其子模块
    在这里插入图片描述
  • 如上建立子模块目录和文件,所有py文件中就写一句话print(__name__)
  • 删除__init__.py不影响导入,但最好保留
# import m.m1 # import os.path
# import m.m1 as mm
import m.m2.m21

print('-'*30)
print(1,*filter(lambda x:x.startswith('m'),dir())) #?m

# 加载了吗?缓存
import sys
print(2,*filter(lambda x:x[0] is 'm' or x[0].startswith('m'),sys.modules.items()),sep='\n') #?

print('='*30)
# print(m.A)
# print(m.m1) ? s
# print(mm.exists)
print(m.m2.m21.test)
# print(m.m2.m22) #m22是模块不是变量,必须先加载,所以不能用

# 运行结果
m.m2.m21
------------------------------
1 m
2
('marshal', <module 'marshal' (built-in)>)
('m', <module 'm' from 'C:\\Users\\98669\\PycharmProjects\\week18\\m\\__init__.py'>)
('m.m2', <module 'm.m2' from 'C:\\Users\\98669\\PycharmProjects\\week18\\m\\m2\\__init__.py'>)
('m.m2.m21', <module 'm.m2.m21' from 'C:\\Users\\98669\\PycharmProjects\\week18\\m\\m2\\m21\\__init__.py'>)
==============================
1111

模块、包总结

  • 包能更好的组织模块,尤其是大模块,其代码行数多,可以把它拆分成很多子模块,便于使用某些功能就加载相应的子模块
  • 包目录中__init__.py在包第一次导入时就会执行,内容可以为空,也可以用于该报初始化工作的代码,最好不要删除
  • 导入子模块一定会加载夫模块,但导入父模块不一定回导入子模块
  • 包目录之间只能使用 . 点作为间隔符,表示层级关系
  • 模块也是封装,如同类、函数,不过他能封装变量、类、函数
  • 模块就是命名空间,其内部的顶层标识符都是他的属性,可以通过__dict__或module查看。
  • 包也是模块,但模块不一定是包,包是特殊模块,是一种组织方式,包含__path__属性

绝对导入、相对导入

绝对导入

  • 在import语句或from导入模块,模块名称最前面不是以 . 开头的
  • 绝对导入总是去模块搜索路径中找,当然会查看该模块是否已经加载

相对导入

  • 只能在from语句中
  • 使用 . 点号,表示当前目录内
  • … 表示上一级目录
  • … 表示上上级
  • 只在包内使用,一般不要在顶层模块中使用相对导入
  • 一旦一个模块中使用相对导入,就不可以作主模块运行
  • 相对导入更像目录操作
from m import A #部分导入,减少名词污染 #from模块 子模块子包;变量(函数、类、全局变量)

print('-'*30)
print(1,*filter(lambda x: not x.startswith('_'),dir())) #?A

# 加载了吗?缓存
import sys
print(2,*filter(lambda x:x[0] is 'm' or x[0].startswith('m.'),sys.modules.items()),sep='\n') #m

print('='*30)
print(A)

#运行结果
m ~~~~
------------------------------
1 A
2
('m', <module 'm' from 'C:\\Users\\98669\\PycharmProjects\\week18\\m\\__init__.py'>)
==============================
<class 'm.A'>
from m import m1

print('-'*30)
print(1,*filter(lambda x: not x.startswith('_'),dir())) #?结果显示m

# 加载了吗?缓存
import sys
print(2,*filter(lambda x:x[0] is 'm' or x[0].startswith('m.'),sys.modules.items()),sep='\n') #结果显示m,m.m1 

print('='*30)
print(m1)
print(m1.exists)
import m #不会重复加载
print(m.m1)
print(m1 is m.m1) #True

#运行结果
m ~~~~
m.m1 ++++
------------------------------
1 m1
2
('m', <module 'm' from 'C:\\Users\\98669\\PycharmProjects\\week18\\m\\__init__.py'>)
('m.m1', <module 'm.m1' from 'C:\\Users\\98669\\PycharmProjects\\week18\\m\\m1.py'>)
==============================
<module 'm.m1' from 'C:\\Users\\98669\\PycharmProjects\\week18\\m\\m1.py'>
<function exists at 0x0000026B0CC37A68>
<module 'm.m1' from 'C:\\Users\\98669\\PycharmProjects\\week18\\m\\m1.py'>
True

访问控制

下划线开头的模块名

一道或两道下划线开头的模块都可以被成功导入,都是合法标识符,可以用作模块名

模块内的标识符

#t2:
A = 100
_B = 200
__C = 300

__my__ = 400
# import t2
# print(t2.A,t2.__C,t2.__my__,t2._B) #全部显示

# from t2 import _B,A,__C,__my__
# print(locals()) #全部显示

# from t2 import * #只导入非下划线开头
# print(dir())

from … impoer * 和__all__

#t2:
__all__ = ['_B']
A = 100
_B = 200
__C = 300

__my__ = 400
# import t2
# print(t2.A,t2.__C,t2.__my__,t2._B) #全部显示

# from t2 import _B,A,__C,__my__
# print(locals()) #全部显示

from t2 import * #在没有__all__ = [],只导入非下划线开头;有了__all__ = [],他说了算 -- 此处只显示_B
print(dir())
包和子模块
  • __init__.py中有什么变量,则使用from m import * 加载什么变量,这依然符合模块的访问控制
#想要访问m1下的y:

# import m.m1
# print(m.m1.y)

# from m import m1
# print(m1.y)

# from m.m1 import y
# print(y)
# import m
# from m import m1
# print(m1.y)

import m #我想访问m1下的 y
#可以在m.__init__.py中添加from . import m1
print(m.m1.y)
  • 特殊情况:
#m.__init__.py:
print(__name__,'~~~~')
from .m1 import y #m1 y
# from . import m1
x = 1
print(dir(),'~~~~')
import m #我想访问m1下的 y
#可以在m.__init__.py中添加from . import m1
print(m.m1.y)
print(m.y)

#运行结果:
m ~~~~
m.m1 ++++
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'm1', 'x', 'y'] ~~~~
5
5
总结
1. 使用from xyz import * 导入
  • 如果模块没有__all__,from xyz import * 只导入非下划线开头的该模块的变量。如果是包,子模块也不会导入,除非在__all__中设置,或者__init__.py中导入
  • 如果模块有__all__,from xyz import * 只导入__all__列表中指定的名称,哪怕这个名词是下划线开头或子模块
  • from xyz import * 方式导入,使用简单,但副作用是导入大量不需要使用的变量(名词污染),甚至有可能造成名称冲突,而__all__可以控制被导入模块在这种导入方式下能提供的变量名称,就是为了阻止from xyz import * 导入过多的模块变量,从而避免冲突。因此编写模块是应该尽量加入__all__
2. from module import name1, name2导入
  • 这种方式的导入明确,哪怕导入子模块,或者导入下划线开头的名称
  • 可以有控制的导入名称和其对应对象

模块变量的修改

  • 模块对象是同一个,因此模块的变量也是同一个,对模块变量的秀发i,会影响所有使用者
  • 除非万不得已或知道自己在做什么,否则不要修改模块的变量
  • 之前学习的猴子补丁也可以通过打补丁的方式,修改模块的变量、类、函数等内容
#t1:
A = 100

#t2:
print(__name__)
import t1
print('in t2',t1.A)

#t3:
import t1

print(t1.A)

t1.A = 2000
print('-'*30)
import t2

#运行结果
100
------------------------------
t2
in t2 2000
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值