pathlib 路径和 py.test LocalPath
pathlib Path and py.test LocalPath
我已经开始使用 pathlib.Path
一段时间了,我喜欢使用它。现在习惯了,马虎了,忘记给str
.
投参了
这经常发生在使用 tox
+ py.test
和基于 tmpdir
的临时目录(这是一个 py._path.local.LocalPath
)时:
from pathlib import Path
import pytest
def test_tmpdir(tmpdir):
p = Path(tmpdir) / 'testfile.csv'
我不是每次都插入 str()
,而是考虑更一般地解决这个问题,但没能成功。
首先,我尝试创建自己的路径 class,该路径具有经过改编的 _parse_args
:
import pytest
from py._path.local import LocalPath
from pathlib import Path, PurePath
def Path(Path):
@classmethod
def _parse_args(cls, args):
parts = []
for a in args:
if isinstance(a, PurePath):
parts += a._parts
elif isinstance(a, str):
# Force-cast str subclasses to str (issue #21127)
parts.append(str(a))
elif isinstance(a, LocalPath):
parts.append(str(a))
else:
raise TypeError(
"argument should be a path or str object, not %r"
% type(a))
return cls._flavour.parse_parts(parts)
def test_subclass(tmpdir):
p = Path(tmpdir) / 'testfile.csv'
这会抛出一个 TypeError: unsupported operand type(s) for /: 'NoneType' and 'str'
(也尝试使用 PosixPath
,结果相同,但不希望是 Linux 特定的)。
我试过猴子补丁 Path
:
import pytest
from pathlib import Path
def add_tmpdir():
from py._path.local import LocalPath
org_attr = '_parse_args'
stow_attr = '_org_parse_args'
def parse_args_localpath(cls, args):
args = list(args)
for idx, a in enumerate(args):
if isinstance(a, LocalPath):
args[idx] = str(a)
return getattr(cls, stow_attr)(args)
if hasattr(Path, stow_attr):
return # already done
setattr(Path, stow_attr, getattr(Path, org_attr))
setattr(Path, org_attr, parse_args_localpath)
add_tmpdir()
def test_monkeypatch_path(tmpdir):
p = Path(tmpdir) / 'testfile.csv'
这会抛出 AttributeError: type object 'Path' has no attribute '_flavour'
(在猴子修补 PurePath 时)。
最后我尝试只包装 Path
:
import pytest
import pathlib
def Path(*args):
from py._path.local import LocalPath
args = list(args)
for idx, a in enumerate(args):
if isinstance(a, LocalPath):
args[idx] = str(a)
return pathlib.Path(*args)
def test_tmpdir_path(tmpdir):
p = Path(tmpdir) / 'testfile.csv'
这也给出了 AttributeError: type object 'Path' has no attribute '_flavour'
我认为最后一个在某些时候有效,但我无法重现。
我做错了什么吗?为什么这么难?
最后一个(包装)应该有效,我怀疑你实际上在一个 py.test
/tox
运行 中测试了所有这些并且那个猴子补丁仍然有效(这可能解释了为什么它在某些时候起作用,如果你开始改变全局 类).
上的东西,测试文件的顺序等很重要
这很难,因为 Path
本质上是一个生成器,它会即时决定你是在 Windows 还是 Linux,并创建一个 WindowsPath
分别。 PosixPath
相应地。
BDFL Guido van Rossum 在 2015 年 5 月已经 indicated:
It does sound like subclassing Path should be made easier.
但什么也没发生。其他标准库中 3.6 中对 pathlib
的支持有所增加,但 pathlib 本身仍然存在同样的问题。
以防其他人正在研究 pytest 的 tmpdir
路径是否与 pathlib.Path
兼容:
使用 python 3.6.5
和 pytest 3.2.1
,问题中发布的代码在没有显式转换为 str
:
的情况下工作得很好
from pathlib import Path
def test_tmpdir(tmpdir):
p = Path(tmpdir) / 'testfile.csv'
为方便起见,您可以创建一个夹具来自动进行包装:
@pytest.fixture
def tmppath(tmpdir):
return Path(tmpdir)
那么您可以在测试中使用 tmppath
而不是 tmpdir
。
我已经开始使用 pathlib.Path
一段时间了,我喜欢使用它。现在习惯了,马虎了,忘记给str
.
这经常发生在使用 tox
+ py.test
和基于 tmpdir
的临时目录(这是一个 py._path.local.LocalPath
)时:
from pathlib import Path
import pytest
def test_tmpdir(tmpdir):
p = Path(tmpdir) / 'testfile.csv'
我不是每次都插入 str()
,而是考虑更一般地解决这个问题,但没能成功。
首先,我尝试创建自己的路径 class,该路径具有经过改编的 _parse_args
:
import pytest
from py._path.local import LocalPath
from pathlib import Path, PurePath
def Path(Path):
@classmethod
def _parse_args(cls, args):
parts = []
for a in args:
if isinstance(a, PurePath):
parts += a._parts
elif isinstance(a, str):
# Force-cast str subclasses to str (issue #21127)
parts.append(str(a))
elif isinstance(a, LocalPath):
parts.append(str(a))
else:
raise TypeError(
"argument should be a path or str object, not %r"
% type(a))
return cls._flavour.parse_parts(parts)
def test_subclass(tmpdir):
p = Path(tmpdir) / 'testfile.csv'
这会抛出一个 TypeError: unsupported operand type(s) for /: 'NoneType' and 'str'
(也尝试使用 PosixPath
,结果相同,但不希望是 Linux 特定的)。
我试过猴子补丁 Path
:
import pytest
from pathlib import Path
def add_tmpdir():
from py._path.local import LocalPath
org_attr = '_parse_args'
stow_attr = '_org_parse_args'
def parse_args_localpath(cls, args):
args = list(args)
for idx, a in enumerate(args):
if isinstance(a, LocalPath):
args[idx] = str(a)
return getattr(cls, stow_attr)(args)
if hasattr(Path, stow_attr):
return # already done
setattr(Path, stow_attr, getattr(Path, org_attr))
setattr(Path, org_attr, parse_args_localpath)
add_tmpdir()
def test_monkeypatch_path(tmpdir):
p = Path(tmpdir) / 'testfile.csv'
这会抛出 AttributeError: type object 'Path' has no attribute '_flavour'
(在猴子修补 PurePath 时)。
最后我尝试只包装 Path
:
import pytest
import pathlib
def Path(*args):
from py._path.local import LocalPath
args = list(args)
for idx, a in enumerate(args):
if isinstance(a, LocalPath):
args[idx] = str(a)
return pathlib.Path(*args)
def test_tmpdir_path(tmpdir):
p = Path(tmpdir) / 'testfile.csv'
这也给出了 AttributeError: type object 'Path' has no attribute '_flavour'
我认为最后一个在某些时候有效,但我无法重现。
我做错了什么吗?为什么这么难?
最后一个(包装)应该有效,我怀疑你实际上在一个 py.test
/tox
运行 中测试了所有这些并且那个猴子补丁仍然有效(这可能解释了为什么它在某些时候起作用,如果你开始改变全局 类).
这很难,因为 Path
本质上是一个生成器,它会即时决定你是在 Windows 还是 Linux,并创建一个 WindowsPath
分别。 PosixPath
相应地。
BDFL Guido van Rossum 在 2015 年 5 月已经 indicated:
It does sound like subclassing Path should be made easier.
但什么也没发生。其他标准库中 3.6 中对 pathlib
的支持有所增加,但 pathlib 本身仍然存在同样的问题。
以防其他人正在研究 pytest 的 tmpdir
路径是否与 pathlib.Path
兼容:
使用 python 3.6.5
和 pytest 3.2.1
,问题中发布的代码在没有显式转换为 str
:
from pathlib import Path
def test_tmpdir(tmpdir):
p = Path(tmpdir) / 'testfile.csv'
为方便起见,您可以创建一个夹具来自动进行包装:
@pytest.fixture
def tmppath(tmpdir):
return Path(tmpdir)
那么您可以在测试中使用 tmppath
而不是 tmpdir
。