【问题标题】:Subclass `pathlib.Path` fails子类`pathlib.Path`失败
【发布时间】:2015-04-24 14:57:27
【问题描述】:

我想增强课程pathlib.Path,但上面的简单示例不起作用。

from pathlib import Path

class PPath(Path):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

test = PPath("dir", "test.txt")

这是我收到的错误消息。

Traceback (most recent call last):
  File "/Users/projetmbc/test.py", line 14, in <module>
    test = PPath("dir", "test.txt")
  File "/anaconda/lib/python3.4/pathlib.py", line 907, in __new__
    self = cls._from_parts(args, init=False)
  File "/anaconda/lib/python3.4/pathlib.py", line 589, in _from_parts
    drv, root, parts = self._parse_args(args)
  File "/anaconda/lib/python3.4/pathlib.py", line 582, in _parse_args
    return cls._flavour.parse_parts(parts)
AttributeError: type object 'PPath' has no attribute '_flavour'

我做错了什么?

【问题讨论】:

  • CodeReview 概述了该问题的可能解决方案。可以关注issue的讨论。也许有一天他们会发布我们尚未遇到的解决方案。
  • 非常感谢!

标签: oop python-3.4


【解决方案1】:

您可以对具体实现进行子类化,这样就可以了:

class Path(type(pathlib.Path())):

我是这样做的:

import pathlib

class Path(type(pathlib.Path())):
    def open(self, mode='r', buffering=-1, encoding=None, errors=None, newline=None):
        if encoding is None and 'b' not in mode:
            encoding = 'utf-8'
        return super().open(mode, buffering, encoding, errors, newline)

Path('/tmp/a.txt').write_text("я")

【讨论】:

  • 谢谢。它很丑,但远不如我以前的方法。
  • 不幸的是 mypy 不允许动态基类,所以你不能输入检查这个
  • 调用Path() 会创建WindowsPathPosixPath 对象,因此这样做的唯一“魔术”是确定使用哪种风格,然后获取该类型进行继承。见the docs
【解决方案2】:

HerePath 类的定义。它做了一些相当聪明的事情。它不是直接从其__new__() 返回Path 的实例,而是返回子类的实例,但如果它被直接调用为Path()(而不是作为子类)。

否则,它预计会通过WindowsPath()PosixPath() 调用,它们都通过多重继承提供_flavour 类属性。子类化时还必须提供此属性。您可能需要实例化和/或子类化 _Flavour 类来执行此操作。这不是 API 的受支持部分,因此您的代码可能会在 Python 的未来版本中中断。

TL;DRThis idea is fraught with peril, and I fear that my answers to your questions will be interpreted as approval rather than reluctant assistance.

【讨论】:

  • 对于传统的 Python API,这是多么奇怪的选择。我认为我必须按照你的建议去做。这需要阅读源码。不是问题,只是有点无聊。
  • @projetmbc:恕我直言,这是糟糕的设计;超类不应显示其子类的知识,更不用说以这种方式依赖它们了。 Path() 应该是一个工厂函数。可能值得一个错误报告,但由于这是一个主观意见问题,我不确定错误跟踪器是最好的起点。您可能会在 Python-Dev 或其他邮件列表上进行更有成效的对话。
  • 我和你的想法一样,我已经在 Python-dev 邮件列表上发布了一条消息。另一方面,我的回答中的挑战是一件有趣的事情。
  • 我不会说这很聪明
【解决方案3】:

根据您想要扩展 Path(或 PosixPath 或 WindowsPath)的原因,您或许可以简化您的生活。就我而言,我想实现一个包含 Path 的所有方法以及其他一些方法的 File 类。但是,我实际上并不关心 isinstance(File(), Path)。

委派工作非常出色:

class File:

    def __init__(self, path):
        self.path = pathlib.Path(path)
        ...

    def __getattr__(self, attr):
        return getattr(self.path, attr)

    def foobar(self):
        ...

现在,如果file = File('/a/b/c'),我可以对file使用整个Path接口,也可以使用file.foobar()。

【讨论】:

  • 如果您要使用file = File("a/b/c") 进行实例化,您是否不需要self.path = pathlib.Path(path)__init__ 中,即str 参数到__init__path 参数?
  • 当然,self.path 必须是 pathlib.Path。
  • 好的,你应该在你的答案中调整它(现在不起作用)。
  • 好的,我修复了 init
【解决方案4】:

我也一直在努力解决这个问题。

这就是我所做的,从 pathlib 模块学习。 在我看来这是更简洁的方法,但如果 pathlib 模块更改了它的实现,它可能不会成立。

from pathlib import Path
import os
import pathlib

class PPath(Path):

    _flavour = pathlib._windows_flavour if os.name == 'nt' else pathlib._posix_flavour

    def __new__(cls, *args):
        return super(PPath, cls).__new__(cls, *args)

    def __init__(self, *args):
        super().__init__() #Path.__init__ does not take any arg (all is done in new)
        self._some_instance_ppath_value = self.exists() #Path method

    def some_ppath_method(self, *args):
        pass

test = PPath("dir", "test.txt")

【讨论】:

    【解决方案5】:

    结合之前的一些答案,你也可以写:

    class MyPath(pathlib.Path):
        _flavour = type(pathlib.Path())._flavour
    

    【讨论】:

    • 这对我来说似乎是最优雅的解决方案
    【解决方案6】:

    注意

    在对 Python 开发人员进行了一些讨论后,我打开了a bug track here。列表。

    临时解决办法

    很抱歉这个双重答案,但这是实现我想要的一种方法。感谢 Kevin 指出pathlib 的来源以及我们这里有构造函数的事实。

    import pathlib
    import os
    
    def _extramethod(cls, n):
        print("=== "*n)
    
    class PathPlus(pathlib.Path):
        def __new__(cls, *args):
            if cls is PathPlus:
                cls = pathlib.WindowsPath if os.name == 'nt' else pathlib.PosixPath
    
            setattr(cls, "extramethod", _extramethod)
    
            return cls._from_parts(args)
    
    test = PathPlus("C:", "Users", "projetmbc", "onefile.ext")
    
    print("File ?", test.is_file())
    print("Dir  ?", test.is_dir())
    print("New name:", test.with_name("new.name"))
    print("Drive ?", test.drive)
    
    test.extramethod(4)
    

    这将打印以下行。

    File ? False
    Dir  ? False
    New name: C:/Users/projetmbc/new.name
    Drive ? 
    === === === === 
    

    【讨论】:

    • 我不确定这是个好主意。您基本上是将额外的方法直接附加到WindowsPathPosixPath 类,这意味着它将可用于这些类的所有实例,而不仅仅是通过PathPlus 创建的实例。
    • 我的想法和你一样,但目前我使用的是这个不太完美的解决方案。为什么 ?如果我使用类 Path 的增强版本,我不关心 Windowsäth 或 PosixPath。事实上,在 Python-dev 邮件列表中,Guido 自己说 Path 类的子类化必须比现在更容易做到。作为结论,我的解决方案是在 Path 变得更加 Python 之前的临时补丁。
    【解决方案7】:

    为了从pathlib.Path 继承,您需要指定您所代表的操作系统或“风味”。您需要做的就是通过从pathlib.PosixPathpathlib.WindowsPath 继承来指定您使用的是Windows 还是Unix(根据您的回溯,似乎是Unix)。

    import pathlib
    
    class PPath(pathlib.PosixPath):
        pass
    
    test = PPath("dir", "test.txt")
    print(test)
    

    哪些输出:

    dir\test.txt
    

    按照this answer 中的建议使用type(pathlib.Path()) 与直接从pathlib.PosixPathpathlib.WindowsPath 继承完全相同,因为实例化pathlib.Path“创建PosixPath 或WindowsPath”(pathlib documentation)。

    如果您知道您的应用程序不会跨平台,则直接从代表您的操作系统的风味路径继承会更简单。

    【讨论】:

      【解决方案8】:

      这里有一个简单的方法来处理凯文的观察。

      class PPath():
          def __init__(self, *args, **kwargs):
              self.path = Path(*args, **kwargs)
      

      然后我需要使用一个技巧来自动将所有 Path 的方法绑定到我的 PPpath 类。我认为这样做会很有趣。

      【讨论】:

      • 重新自动绑定:尝试覆盖__getattr__。这不会捕获魔术方法,因此您还需要覆盖 __div__ 和(可能)__rdiv__ 以获得 foo / bar 语法。
      • __div__ 不是 3.x 中的东西。我的意思是说__truediv__,也许是__rtruediv__
      • 我已经更新了我的代码,以便自动拥有 de div 语法。我的主要问题现在变成了找到一种自动方法将Path 的所有公共方法(如is_file)绑定到我的班级。
      • 再次,使用__getattr__ 将属性访问重定向到Path 对象。这也会影响正常的方法。
      • 我会在接下来的一周尝试这个。如果我发现有用的东西,我会把它放在这里。
      【解决方案9】:

      这也是一种工作。

      from pathlib import Path
      
      class SystemConfigPath(type(Path())):
          def __new__(cls, **kwargs):
              path = cls._std_etc()
              return super().__new__(cls, path, **kwargs)
      
          @staticmethod
          def _std_etc():
              return '/etc'
      
      name = SystemConfigPath()
      name = name / 'apt'
      print(name)
      

      印刷:

      /etc/apt
      

      @staticmethod 可以替换为@classmethod

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-04-08
        • 1970-01-01
        • 2015-01-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-25
        相关资源
        最近更新 更多