This arises from the SO Question subclassing pathlib.Path.
The pathlib
sources show that Path
, when invoked, selects one of its subclasses, WindowsPath
or 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
__new__
method fromPath
thepathlib
sources 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.
pathlib
andmetaclass
do not exist if you're a moderator perhaps we could include them ? \$\endgroup\$Path
, for either a preferred subclass,PosixPath
orWindowsPath
, based upon the operating system, the_flavour
attribute. You'd like the subclass to do the same. Though your comment has given me a better option to consider. I'll post it now. \$\endgroup\$pathlib
implementation employs a sneaky trick and this carries on to the user. \$\endgroup\$