【问题标题】:Python open filename from custom PATHPython从自定义PATH打开文件名
【发布时间】:2015-07-19 17:14:11
【问题描述】:

与系统路径类似,我想在我的代码中提供一些便利,允许用户指定可能位于少数路径之一中的文件名。

假设我有两个或多个配置路径

['~/.foo-config/', '/usr/local/myapp/foo-config/']

我的用户想打开bar,(又名bar.baz

  1. 是否有一种方便的构建方式让open('bar')open('bar.baz') 按 LTR 优先顺序自动搜索该文件的这些路径?例如,将我的sys.path 临时调整为仅这些目录为我这样做吗?

  2. 否则,您如何建议实施类似 PATH 的搜索打开包装器?

【问题讨论】:

  • 不是答案,但仅供参考:sys.pathopen() 内置函数无关。 sys.path 是关于 import 声明。

标签: python file search system-paths


【解决方案1】:

环境变量

假设你的应用被命名为 foo ... 在自述文件中告诉用户使用 FOO_PATH 环境变量来指定额外的路径

然后在您的应用中执行类似的操作

 for path in os.environ.get("FOO_PATH",".").split(";"):
     lookfor(os.path.join(path,"somefile.txt"))

你可以把它包装成一个通用函数

def open_foo(fname):
   for path in os.environ.get("FOO_PATH",".").split(";"):
       path_to_test = os.path.join(path,"somefile.txt")
       if os.path.exists(path_to_test):
              return open(path_to_test)
   raise Exception("No File Found On FOOPATH")

那么你就可以像正常打开一样使用它了

with open_foo("my_config.txt") as f:
     print f.read()

【讨论】:

  • 这会处理多条路径吗?作为那个 FOO_PATH 就像/usr/local/foo/:.?
  • 哎呀忘了拆分它:P ...现在它应该拆分 ; 如果你想拆分 : 只需更改拆分字符
【解决方案2】:

open 没有进入那种逻辑。如果需要,编写一个包装函数,使用os.path.joinsys.path 的每个成员连接到参数文件名,并尝试按顺序打开它们,处理找不到此类文件时发生的错误。

我要补充一点,正如另一位用户所说,这是对sys.path 的滥用,但此功能适用于任何路径列表。事实上,也许最好的选择是使用另一个用户建议的环境变量来指定一个以冒号分隔的配置目录列表,然后您可以在搜索功能中对其进行解析和使用。

【讨论】:

    【解决方案3】:

    正如其他人已经提到的:sys.path影响模块搜索路径,即它与导入 Python 模块相关,但与 open() 完全无关。

    我建议将搜索路径的逻辑按优先顺序分开并打开文件,因为这样更容易测试读取

    我会这样做:

    import os
    
    PATHS = ['~/.foo-config/', '/usr/local/myapp/foo-config/']
    
    
    def find_first(filename, paths):
        for directory in paths:
            full_path = os.path.join(directory, filename)
            if os.path.isfile(full_path):
                return full_path
    
    
    def main():
        filename = 'file.txt'
        path = find_first(filename, PATHS)
        if path:
            with open(path) as f:
                print f
        else:
            print "File {} not found in any of the directories".format(filename)
    
    
    if __name__ == '__main__':
        main()
    

    【讨论】:

    • 你偷看了我对可测试性的兴趣,是否可以对扫描文件路径的方法进行单元测试?这不需要准备一组真实目录+文件的单元测试吗?您对干净利落地进行单元测试有什么建议吗?
    • 是的,让您的测试设置创建实际的目录结构是一种方法。实际上并没有太多,在setUp() 中创建结构并在tearDown() 中再次删除它。另一种方法是模拟 os.path.isfile() - 你可以使用类似 unittest.mock.patch()` 的东西来替换 os.path.isfile() 用一个返回你告诉它的值的模拟,并允许你断言是否已经被调用与否。
    • 第三种方法是完全从find_first() 中提取出os.path.isfile()。因此,您可以将第三个参数 isfile_function 传递给 find_first() - 在生产中,这将是原始的 os.path.isfile,但在测试中它可能是一个无操作函数,只记录对它的调用并返回一个预定义的值(这与Strategy Pattern 没有什么不同)。
    【解决方案4】:

    open built-in function 的 Python 标准库文档中提取:

    open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)

    ...file 是一个字符串或字节对象,给出要打开的文件的路径名(绝对或相对于当前工作目录)...

    明确地说,open 不会带来任何自动查找文件的功能:如果路径不是绝对路径,则只会在当前目录中搜索。

    因此,您必须为此使用自定义函数或自定义类。例如:

    class path_opener(object):
        def __init__(self, path = [.]):
            self.path = path
        def set(self, path):
            self.path = path
        def append(self, path):
            self.path.append(path)
        def extent(self, path):
            self.path.extend(path)
        def find(self, file):
            for folder in self.path:
                path = os.path.join(folder, file)
                if os.path.isfile(path):
                    return path
            raise FileNotFoundError()
        def open(self, file, *args, **kwargs):
            return open(self.find(file), *args, **kwargs)
    

    这意味着文件打开器将保留自己的路径,默认情况下将使用当前路径进行初始化,将具有设置、附加或扩展其路径的方法,并且通常会引发 FileNotFoundError在其路径中列出的任何目录中都找不到文件。

    用法:

    o = path_opener(['~/.foo-config/', '/usr/local/myapp/foo-config/'])
    with o.open('foo') as fd:
        ...
    

    【讨论】:

      猜你喜欢
      • 2019-08-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-04
      相关资源
      最近更新 更多