Subclass `pathlib.Path` fails

The name of the pictureThe name of the pictureThe name of the pictureClash 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 ?





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






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.

Popular posts from this blog

Firebase Auth - with Email and Password - Check user already registered

Dynamically update html content plain JS

How to determine optimal route across keyboard