This arises from the SO Question subclassing pathlib.Path.
pathlib sources show that
Path, when invoked, selects one of its subclasses,
PosixPath, to invoke in place of itself, within it's
__new__ method. The following diagram shows a rough idea, with black lines indicating inheritance, red the constructor and green the instance returned.
Kevin points out that while this works for
Path itself it prevents any subclass from having the same functionality. Now I'm not entirely clear as to why this is and Kevin does not offer an explanation. Presumably the
__new__ method is called before the
MRO is setup and one can not rely on
super to do the right thing just yet, namely bumping up to the super classes
__new__ to select the appropriate subclass.
The answers provided for the question itself include the following alternatives and I'm curious as to which is the best method/better practice.
Since everyday should be Christmas let's Wrap the class (a.k.a. Encapsulation/Clojures). I find quite often this is actually the go to answer for a lot of inheritance questions which I feel kinda defeats the point of subclassing as one then has to manually expose the wrapped classes methods.
from pathlib import Path as _Path_ class Path(): def __init__(*args, **kvps): super().__init__(*args, **kvps) self._path_ = _Path_(*args, **kvps)
Instantiate the type of an instantiated class. This is a bit of trickery as you're invoking the class you plan to subclass. It selects the final class for you and you then determine the type it selected and subclass that.
from pathlib import Path class Path(type(pathlib.Path())): pass
Tack on the required features to the selected class. This is roughly what projetmbc did.
from pathlib import Path as _Path_, PosixPath, WindowsPath class Path(_Path_): def __new__(cls, *args): if cls is Path: cls = WindowsPath if os.name == 'nt' else PosixPath setattr(cls, "extramethod", _extramethod) return cls._from_parts(args)
The following is probably a simpler means of achieving the same goal.
Path.extramethod = _extramethod # Or possibly WindowsPath.extramethod = _extramethod PosixPath.extramethod = _extramethod
In the same vane we could simply copy the
pathlibsources but that just screams bad practice.
There is one solution missing from the suite of answers there that Kevin hints towards which is to specify the missing attribute reported in the original error message.
from pathlib import Path as _Path_, _windows_flavour, _posix_flavour import os class Path(_Path_): _flavour = _windows_flavour if os.name == 'nt' else _posix_flavour
BKSpurgeon prompted the following strategy. This sweeps the underlying API details away which is quite nice.
from pathlib import Path as _Path_, PosixPath as _PosixPath_, WindowsPath as _WindowsPath_ import os class Path(_Path_) : def __new__(cls, *args, **kvps): print("Path*") return super().__new__(WindowsPath if os.name == 'nt' else PosixPath, *args, **kvps) class WindowsPath(_WindowsPath_, Path) : pass class PosixPath(_PosixPath_, Path) : pass
The figure below illustrates how the interaction plays out in this case. Basically the constructor in Path* selects one of it's sub-classes, WindowsPath* or PosixPath*, whose MRO map back to Path* and Path and the respective intermediate subclass, WindowsPath or PosixPath.
I feel (1) is kind of cop out, (2) is a bit of a hack, (3) and (4) seem the better approaches but require peaking under the hood (5) is probably the best as it is quite clean.
If it's possible could one elaborate upon which method 1-5, or if applicable their sub-methods, should one really employ here. The real test is probably checking the MRO in each case and that the final instance includes all the subclasses methods and attributes. I'm quite convinced that
pathlib should really be using Metaclasses under the hood and would be keen to hear any commentary upon this aswell.