Python工作目录与文件目录详解

总结

  1. open函数中的相对路径是以工作目录为基准的
  2. import导入package时,相对路径是以当前执行import的文件路径为基准的
  3. 由于python规定顶层模块不能作为package,因此import只能导入当前文件所在的目录以及子路下的package,无法导入上层目录的pakcage,例如 import ..xxx是不行的,只能是import x 或者import x.y
  4. 想要导入上层目录的package,则只能使用绝对路径导入,通过项目结构定位到想要引入的目录,然后使用sys.path将目录添加到python的搜索路径中
  5. sys.path的相对路径是相对于工作目录的

工作目录

文件目录:文件所在的目录
工作目录:执行python命令所在的目录

D:.
|   main.py
|   
+---data
|       data.txt
|       
+---model
|   |   model.py
|   |   train.py
|   |   __init__.py
|   |   
|   +---nlp
|   |   |   bert.py   
|           
\---util
    |   view.py
    |   __init__.py
            

我们以上图为例,很容易知道main.py的文件目录是D:/test/main.py,而train.py的文件目录是D:/test/model/train.py
但是工作目录是哪个呢?我们打开vscode,可以看到terminal中也显示了一个目录D:\test,也就是我们项目的目录,我们点一下运行,执行了一个python脚本,输出一个PS D:\test> python -u "d:/test/model/train.py"
在哪里执行的这个脚本呢?D:\test,这就是我们的【工作目录】

工作目录可以是任何目录,只要能通过python运行就行,例如我们打开一个终端执行我们的train.py脚本PS C:\Users\xxx> python D:\test\model\train.py,那么工作目录就变成了C:\Users\xxx

读文件

python执行的时候,open函数中相对路径的会以当前的工作目录为基准。例如我们再vscode运行脚本,工作目录是D:\test,那么就可以直接访问test目录下的所有文件以及所有子目录和子目录下的文件。
如果我们想在main.py访问data中的data.txt文件,很自然可以写出如下代码

with open('data/data.txt') as f:
    res = f.read()
    print(res)

但如果我们想在train.py中访问data.txt目录怎么办呢?你可能会用相对路径写出如下的代码

with open('../data/data.txt') as f:
    res = f.read()
    print(res)

但是会报如下错误

Traceback (most recent call last):
File “d:/test/model/train.py”, line 1, in
with open(‘…/data/data.txt’) as f:
FileNotFoundError: [Errno 2] No such file or directory: ‘…/data/data.txt’

这是因为我们执行train.py文件的目录D:\testpython会以工作目录为基准目录,即D:\test,相对路径是相对于工作目录的,因此实际访问的就是D:\test/../data/data.txt。当然就找不到了。正确的写法就是

with open('data/data.txt') as f:
    res = f.read()
    print(res)

写文件

我们训练了一个模型,想要把最终的结果保存在model目录中,该怎么办呢?工作目录依然是D:\test。下面这种写法依然是错误的

with open('tf.pb','w') as f:
	f.write('hello world')

一定要明白相对路径是相对于工作目录来说的,我们执行命令的路径是D:\test,因此最终写入的地方就是D:\test\tf.pb,而不是model文件夹,正确的写法如下

with open('model/tf.pb','w') as f:
	f.write('hello world')

切换工作目录

如果我们把工作目录换掉了,上面的路径就都不对了,例如在C:\users\xxx目录下执行python命令,那么上面的路径就就变成了C:\users\xxx\data\data.txt或者C:\users\xxx\model\tf.pb了,非常的不好用。

  • 查看当前工作目录os.getcwd()
  • 切换工作目录 os.chdir(path)
  • 修改train.py和main.py为

    import os
    cur_dir = os.getcwd()
    print(cur_dir)
    

    然后我们打开个终端执行两个脚本

    PS C:\Users\xxx> python d:/test/model/train.py
    train.py work dir = C:\Users\xxx
    PS C:\Users\xxx> python d:/test/main.py
    main.py work dir = C:\Users\xxx

    发现他们的工作目录都是一样的,重新修改main.py和train.py,并且切换工作目录

    # mian.py
    import os
    cur_dir = os.getcwd()
    print("old work dir = ", cur_dir)
    os.chdir(r'D:\\test')
    cur_dir = os.getcwd()
    print("new work dir = ", cur_dir)
    with open('data/data.txt') as f:
        res = f.read()
        print(res)
    
    # train.py
    import os
    cur_dir = os.getcwd()
    print("old work dir = ", cur_dir)
    os.chdir(r'D:\\test')
    cur_dir = os.getcwd()
    print("new work dir = ", cur_dir)
    with open('model/tf.pb','w') as f:
    	f.write('hello world')
    

    PS C:\Users\xxx> python d:/test/main.py
    main.py old work dir = C:\Users\xxx
    main.py new work dir = D:\test
    hello world

    绝对路径

    我个人喜欢以执行文件的路径作为相对目录的基准。例如执行main.py文件的时候,喜欢以main.py所在的目录D:\test为基准,执行train.py的时候,喜欢以train.py所在的目录D:\test\model为基准,这个时候我们可以先获取文件的绝对路径,然后根据这个路径拼接出我们想要的路径来, os.path.abspath(__file__)可以获取文件的绝对路径,

    # main.py
    import os
    file_path = os.path.abspath(__file__)
    file_dir = os.path.dirname(file_path)
    print('file_dir=',file_dir)
    
    print("cur work dir = ", os.getcwd())
    
    data_path = os.path.join(file_dir, 'data/data.txt')
    print('data_path=',data_path)
    with open(data_path,'r') as f:
        res = f.read()
        print(res)
    
    

    同样

    # train.py
    import os
    file_path = os.path.abspath(__file__)
    file_dir = os.path.dirname(file_path)
    print('file_dir=',file_dir)
    
    print("cur work dir = ", os.getcwd())
    # 
    model_path = os.path.join(file_dir, 'tf.pb')
    print('model_path=',model_path)
    with open(model_path,'w') as f:
        f.write('hello world')
    

    PS C:\Users\xxx> python d:/test/model/train.py
    file_dir= d:\test\model
    cur work dir = C:\Users\xxx
    model_path= d:\test\model\tf.pb

    很直观了,我们根据文件的目录拼接出我们想要的目录,然后执行读写操作。

    导入自定义包/文件

    我们util目录中有一个view.py的文件,里面包含了一些工具类和方法

    # view.py
    class View:
    	def __init__(self, x, y):
    		self.x = x
    		self.y = y
    

    我们想要在train.py中调用该怎么办呢?

    import util.view import View 
    

    我们以D:\test为工作目录,执行train.py文件,嘿嘿

    Traceback (most recent call last):
    File “d:/test/model/train.py”, line 1, in
    from util.view import View
    ModuleNotFoundError: No module named ‘util’

    这是因为python在导入包的时候import xxx是以文件路径为基准的,即以train.py为基准,发现train.py的目录中并没有util子目录,因此导入失败。聪明的你做了一个简单的修改

    import ..util.view import View 
    

    发现报了另一个错误

    Traceback (most recent call last):
    File “d:/test/model/train.py”, line 1, in
    from …util.view import View
    ValueError: attempted relative import beyond top-level package

    这是因为python规定顶层模块不能作为package,我们执行的是train.py文件,那么train.py所在的目录就是顶层模块了,train.py相对导入的的目录不在超出了train.py所在目录,因此导入失败。换句话说就是相对导入只能导入同一个package下的子package或module。

    # main.py
    import model.train
    
    #train.py
    import util.view import View 
    

    这种方式依然会报错,虽然我们执行的时main.py文件,但是导入model.train的时候依然会执行train.py文件。
    总结一下:

    1. 相对路径导入时是以当前文件为基准的,当前文件所在的目录就是顶层模块目录
    2. 相对导入模块不能超出顶层目录,意味者相对导入只能导入当前文件所在目录下的模块或者子package

    非常的疑惑,什么时候可以使用..

    包查询路径

    相对路径只能导入同一package下的module。要导入不同package下的module可以使用绝对路径。这就非常奇怪了,我们安装的三方库为什么可以在任意文件导入呢?这就不得不说python的库查询机制,python回去默认的一些地方查找安装的package,具体可以通过sys.path来查看

    import sys
    
    for path in sys.path:
        print(path)
    

    d:\test\model
    D:\soft\python3\python37.zip
    D:\soft\python3\DLLs
    D:\soft\python3\lib
    D:\soft\python3
    C:\Users\xxx\AppData\Roaming\Python\Python37\site-packages
    C:\Users\xxx\AppData\Roaming\Python\Python37\site-packages\win32
    C:\Users\x\xxAppData\Roaming\Python\Python37\site-packages\win32\lib
    C:\Users\x\xxAppData\Roaming\Python\Python37\site-packages\Pythonwin
    D:\soft\python3\lib\site-packages

    可以看到除了第一条路径,其余都是跟python的默认安装路径有关。python会按顺序从这些路径里面查找我们安装的package,越靠前的优先级越高。例如我们导入import os这个库优先从项目路径d:\test查找os的库,没有找到就接着从第二个路径查找,一直找到D:\soft\python3\Lib发现有os的库,然后导入。如果我们自己的项目中有一个os的库,就会优先使用项目中的,替换掉lib里面的。

    sys.path绝对路径导入自己的包

    我们把自己的package也加入到sys.path中,python就可以找到了,一种简单的方法就是

    #train.py
    import os
    import sys
    cur_dir = os.path.dirname(os.path.abspath(__file__))
    
    # 直接导入会找不到模块
    #import util.view
    
    # util模块在test目录下,我们把test目录添加到sys.path中
    sys.path.append(os.path.dirname(cur_dir))
    
    for path in sys.path:
        print(path)
    
    import util.view
    
    # nlp在model下面,sys.path的第一个路径就是d:\test\model
    # 可以在d:\test\model下面发现有一个nlp的package,导入成功
    from nlp.bert import BERT
    

    sys.path相对路径导入自己的包

    sys.path添加相对路径的时候,以哪个目录为基准目录呢?答案是【工作目录】,我们是在d:\test目录下执行的命令train.py,所以工作目录就是d:\test,而util包就是在d:\test下面的,所以我们

    #train.py
    import sys
    sys.path.append(".")
    for path in sys.path:
    	print(path)
    import util.view
    

    忽略python自己的目录,我们可以看到当前有sys.path有两个目录,第一个是d:\test\model,也就是【文件路径】,python会自动把执行的文件的路径加进来,所以文件目录下的所有package和module都可以通过相对目录的方式找到。第二个是一个.,这个就是工作目录。因为我们使用的是相对路径导入,而相对路径的基准目录就是d:\test.代表的就是基准目录。

    d:\test\model
    .

    使用绝对路径打印更直观一些

    # train.py
    import sys
    import os
    sys.path.append(".")
    for path in sys.path:
    	print(os.path.abspath(path))
    import util.view
    

    d:\test\model
    d:\test

    d:\test添加到了sys.path中,因此d:\test下面的package就都可以访问到了。很明显相对路径很不好用,当我们把工作目录切换到C:\users\xxx的时候,就又找不到了

    PS C:\Users\xxx> python d:/test/model/train.py
    d:\test\model
    D:\soft\python3\python37.zip
    D:\soft\python3\DLLs
    D:\soft\python3\lib
    D:\soft\python3
    C:\Users\xxx\AppData\Roaming\Python\Python37\site-packages
    C:\Users\xxx\AppData\Roaming\Python\Python37\site-packages\win32
    C:\Users\xxx\AppData\Roaming\Python\Python37\site-packages\win32\lib
    C:\Users\xxx\AppData\Roaming\Python\Python37\site-packages\Pythonwin
    D:\soft\python3\lib\site-packages
    C:\Users\xxx

    Traceback (most recent call last):
    File “d:/test/model/train.py”, line 6, in
    import util.view
    ModuleNotFoundError: No module named ‘util’

    所以我们最好使用绝对路径来添加

    小结

    核心诉求就是找到package,为什么会找不到我们自己定义的package呢?因为没有把路径添加到python的包搜索路径里面,怎么添加呢?使用sys.path.append添加。定位到package的绝对路径,然后添加进去。

    作者:我家大宝最可爱

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python工作目录与文件目录详解

    发表回复