Subclass `pathlib.Path` fails
Clash Royale CLAN TAG#URR8PPP
Subclass `pathlib.Path` fails
I would like to enhance the class pathlib.Path
but the simple example above dose not work.
pathlib.Path
from pathlib import Path
class PPath(Path):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
test = PPath("dir", "test.txt")
Here is the error message I have.
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'
What I am doing wrong ?
Thanks a lot for this !
– projetmbc
Oct 23 '17 at 16:44
5 Answers
5
You can subclass the concrete implementation, so this works:
class Path(type(pathlib.Path())):
Here's what I did with this:
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("я")
Thanks. It is ugly but far less than my previous method.
– projetmbc
Dec 15 '15 at 8:40
Here is the definition of the Path
class. It does something rather clever. Rather than directly returning an instance of Path
from its __new__()
, it returns an instance of a subclass, but only if it's been invoked directly as Path()
(and not as a subclass).
Path
Path
__new__()
Path()
Otherwise, it expects to have been invoked via either WindowsPath()
or PosixPath()
, which both provide a _flavour
class attribute via multiple inheritance. You must also provide this attribute when subclassing. You'll probably need to instantiate and/or subclass the _Flavour
class to do this. This is not a supported part of the API, so your code might break in a future version of Python.
WindowsPath()
PosixPath()
_flavour
_Flavour
TL;DR: This idea is fraught with peril, and I fear that my answers to your questions will be interpreted as approval rather than reluctant assistance.
What a weird choice regarding to the traditional Python API. I think that I have to do what you suggest. This needs to read the source. Not a problem but just a little boring thing.
– projetmbc
Apr 24 '15 at 17:36
@projetmbc: IMHO this is poor design; a superclass should not display knowledge of its subclasses, much less depend on them in this fashion.
Path()
ought to be a factory function instead. It might be worth a bug report, but since it's a matter of subjective opinion, I'm not sure the bug tracker is the best place to start. You might have a more productive conversation on Python-Dev or another mailing list.– Kevin
Apr 24 '15 at 20:29
Path()
I think like you and I have posted a message on the Python-dev mailing list. On the other the challenge in my answer is a funny thing to do.
– projetmbc
Apr 24 '15 at 21:38
I have opened a bug track here after a little discussion on the Python dev. list.
Sorry for this double answer but here is a way to achieve what I want. Thanks to Kevin that points me to the source of pathlib
and the fact we have here constructors.
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)
This prints the following lines.
File ? False
Dir ? False
New name: C:/Users/projetmbc/new.name
Drive ?
=== === === ===
I'm not sure this is a great idea. You're basically attaching the extra method to the
WindowsPath
or PosixPath
classes directly, meaning it'll be available to all instances of those classes, not just the instances created via PathPlus
.– Kevin
May 4 '15 at 14:47
WindowsPath
PosixPath
PathPlus
I think like you but for the moment this not so perfect solution is the one I use. Why ? If I use my enhanced version of the class Path, I do not care of the Windowsäth or PosixPath. Indeed, on the Python-dev mailing list, Guido himself saids that the subclassing of the class Path must be easier to do that it is now. As a conclusion, my solution is very temporary patch before Path become more pythonic.
– projetmbc
May 5 '15 at 16:24
Here is a simple way to do things regarding to the observation made by Kevin.
class PPath():
def __init__(self, *args, **kwargs):
self.path = Path(*args, **kwargs)
Then I will need to use a trick so as to automatically bind all the Path's methods to my PPpath class. I think that will be funny to do.
Re automatic binding: Try overriding
__getattr__
. This won't catch magic methods, so you'll also need to override __div__
and (possibly) __rdiv__
to get the foo / bar
syntax.– Kevin
Apr 24 '15 at 20:24
__getattr__
__div__
__rdiv__
foo / bar
__div__
isn't a thing in 3.x. I meant to say __truediv__
and maybe __rtruediv__
.– Kevin
Apr 24 '15 at 20:36
__div__
__truediv__
__rtruediv__
I have updated my code so as to automatically have de
div
syntax. My main problem becomes now to find an automatic way to bind all the public method of Path
like is_file
to my class.– projetmbc
Apr 24 '15 at 21:28
div
Path
is_file
Again, use
__getattr__
to redirect attribute access to the Path
object. This will affect normal methods as well.– Kevin
Apr 24 '15 at 21:59
__getattr__
Path
I will try this during the coming week. If I find something useful, I'll put it here.
– projetmbc
Apr 26 '15 at 9:12
It's work too.
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)
Printed:
/etc/apt
@staticmethod can be replaced by @classmethod
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
Possible solutions to the problem have been outlined on CodeReview. You can follow the discussion of the issue. Maybe one day they will post a solution we haven't encountered yet.
– Charitoo
Oct 23 '17 at 10:52