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.dirs
或 self.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.dirs
或 self.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
这是我的 Path 子类,它采用递归 os.walk(),并将其映射到我的自定义路径。
需要注意的重要一点是,此代码中存在一个错误,其中目录和文件未正确传递。
但是,我的问题是:考虑到这个错误和缺失的 dirs/files 属性,即使我在 init
中为两个属性都指定了默认值,它实际上会抛出一个 AttributeError
当我尝试引用 self.dirs
或 self.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.dirs
或 self.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