Python 检查 Path 子类上的空列表返回 'AttributeError' 对象没有属性

Python checking for empty list on a Path subclass is returning 'AttributeError' object has no attribute

这是我的 Path 子类,它采用递归 os.walk(),并将其映射到我的自定义路径。

需要注意的重要一点是,此代码中存在一个错误,其中目录和文件未正确传递。

但是,我的问题是:考虑到这个错误和缺失的 dirs/files 属性,即使我在 init 中为两个属性都指定了默认值,它实际上会抛出一个 AttributeError 当我尝试引用 self.dirsself.files 时出错,说明

AttributeError: 'WalkPath' object has no attribute 'dirs'

这是怎么回事?为什么它不采用我的默认值?

#!/usr/bin/env python
import os
import pathlib
from pathlib import Path
from typing import Union

class WalkPath(type(pathlib.Path())):
    def __new__(cls, *args, **kwargs):
        return super(WalkPath, cls).__new__(cls, *args, **kwargs)
    
    def __init__(self, *args, dirs: []=[], files: []=[]):
        """Initialize WalkPath object.

        Args:
            dirs (list):        Dirs provided by os.walk(), defauls to []
                                         
            files (list):       Files provided by os.walk(), defaults to []
        """
        
        super().__init__()
        self.dirs: [WalkPath] = [WalkPath(d) for d in dirs]
        self.files: [WalkPath] = [WalkPath(f) for f in files]
    
    @property
    def is_terminus(self):
        return self.is_file() or not self.dirs
    
    @property
    def dirs_abs(self):
        return [self.joinpath(d) for d in self.dirs]
    
    @property
    def files_abs(self):
        return [self.joinpath(f) for f in self.files]
        
    class Utils:

        @staticmethod
        def find_deep(path: Union[str, Path, 'WalkPath']) -> ['WalkPath']:
            """Deeply search the specified dir and return all files and subdirs.
            If path passed is a file, return a list with that single file.

            Args:
                path (str or Path): Root path to search for files.
                
            Returns:
                A filtered list of files or an empty list.
            """

            _path = WalkPath(path) # Coerce to WalkPath
            # print(_path)
            
            if _path.is_file():
                return [_path]

            for root, dirs, files in os.walk(path):
                wp = WalkPath(root, dirs=dirs, files=files)
                for d in wp.dirs_abs:
                    yield d
                    WalkPath.Utils.find_deep(d)

                for f in wp.files_abs:
                    yield WalkPath(f)

paths = WalkPath.Utils.find_deep(Path('/tests/files').resolve())
paths = sorted(paths, key=lambda p: str(p).lower())

for p in paths:
    print(p.parent, p.dirs)

绝对是正确的类型,并且已正确初始化,因为如果我添加:

@property
def test(self):
    return True

然后:

print(all([p.test and type(p) is WalkPath for p in paths]))
# prints True

但无论如何,我无法访问 self.dirsself.files

当你这样做时会发生:

for d in wp.dirs_abs:
    yield d

dir_abs returns

return [self.joinpath(d) for d in self.dirs]

self.joinpath 调用 Path.joinpath,其中 returns 是一个 Path 对象,而不是 WalkPath 对象。它还没有通过你的__init__。你需要把它包起来。

跟进

好的,所以这是 pathlib 中的一个怪癖。在像 joinpath 这样必须创建新对象的函数中,它调用 __new__ 但显然没有调用你的 __init__。相反,它调用一个名为 _init 的函数。值得在 pathlib.py 的源代码中对此进行追踪。如果您将此添加到您的 WalkPath,我觉得它很有效:

def _init(self):
    super()._init()
    self.dirs: [WalkPath] = []
    self.files: [WalkPath] = []

OP的解决方案

覆盖 joinpath 也有效,设置 _flavour 直接消除了调用 __new__.

的需要
class WalkPath(Path):
    
    _flavour = type(Path())._flavour
    
    def __init__(self, *args, dirs: []=[], files: []=[]):
        super().__init__()
        self.dirs: [WalkPath] = [WalkPath(d) for d in dirs]
        self.files: [WalkPath] = [WalkPath(f) for f in files]
    
    def joinpath(self, path) -> 'WalkPath':
        joined = WalkPath(super().joinpath(path))
        self.__dict__ = joined.__dict__.copy()
        return joined