Error with simple subclassing of pathlib.Path: no _flavour attribute
Question:
I’m trying to sublclass Path from pathlib, but I failed with following error at instantiation
from pathlib import Path
class Pl(Path):
def __init__(self, *pathsegments: str):
super().__init__(*pathsegments)
Error at instantiation
AttributeError: type object 'Pl' has no attribute '_flavour'
Update:
I’m inheriting from WindowsPath
still doesn’t work.
TypeError: object.__init__() takes exactly one argument (the instance to initialize)
Answers:
Part of the problem is that the Path
class implements some conditional logic in __new__
that doesn’t really lend itself to subclassing. Specifically:
def __new__(cls, *args, **kwargs):
if cls is Path:
cls = WindowsPath if os.name == 'nt' else PosixPath
This sets the type of the object you get back from Path(...)
to either PosixPath
or WindowsPath
, but only if cls is Path
, which will never be true for a subclass of Path
.
That means within the __new__
function, cls won't have the
_flavourattribute (which is set explicitly for the
*WindowsPath and *PosixPath
classes), because your Pl
class doesn’t have a _flavour
attribute.
I think you would be better off explicitly subclassing one of the other classes, such as PosixPath
or WindowsPath
.
Path
is somewhat of an abstract class that is actually instantiated as on of two possible subclasses depending on the OS:
PosixPath
or WindowsPath
https://docs.python.org/3/library/pathlib.html
This base class looks for an internal (private) class variable to determine what type of object it actually is, and this variable is called _flavour
You have two choices:
- Derive your class from one of the concrete subclasses.
This is the best choice as it will save you dealing with undocumented library internals and guarantee your code will not break on different versions of the library.
You will need to define your class differently based on the OS if you want your code to be cross-platform.
EDIT: Following dank8’s comment, the original sample code had an issue: Path
does not take __init__
parameters, but __new__
parameters instead.
Also, there is a better way to determine concrete subclass at runtime.
The code will look like this:
from pathlib import Path
class P1(type(Path())):
def __new__(cls, *pathsegments):
return super().__new__(cls, *pathsegments)
- Alternatively fill in the class variable yourself.
This is not recommended as you will be using undocumented elements that could break at any time with any change to the library, but it will allow you to inherit from Path
directly.
Note, there may be other issues if you decide to use this method!
import os
from pathlib import _PosixFlavour
from pathlib import _WindowsFlavour
class P1(Path):
_flavour = _PosixFlavour() if os.name == 'posix' else _WindowsFlavour()
def __new__(cls, *pathsegments):
return super().__new__(cls, *pathsegments)
I solved it. Mokey patching is the way to go.
define functions just like this
def method1(self, other):
blah
Path.method1 = method1
The fastest, easiest, most convenient solution, zero downsides. Autosuggest in Pycharm works well.
UPDATE:
I got THE solution (works with linter and auto suggestor):
class MyPath(type(Path()), Path):
pass
I’m trying to sublclass Path from pathlib, but I failed with following error at instantiation
from pathlib import Path
class Pl(Path):
def __init__(self, *pathsegments: str):
super().__init__(*pathsegments)
Error at instantiation
AttributeError: type object 'Pl' has no attribute '_flavour'
Update:
I’m inheriting from WindowsPath
still doesn’t work.
TypeError: object.__init__() takes exactly one argument (the instance to initialize)
Part of the problem is that the Path
class implements some conditional logic in __new__
that doesn’t really lend itself to subclassing. Specifically:
def __new__(cls, *args, **kwargs):
if cls is Path:
cls = WindowsPath if os.name == 'nt' else PosixPath
This sets the type of the object you get back from Path(...)
to either PosixPath
or WindowsPath
, but only if cls is Path
, which will never be true for a subclass of Path
.
That means within the __new__
function, cls won't have the
_flavourattribute (which is set explicitly for the
*WindowsPath and *PosixPath
classes), because your Pl
class doesn’t have a _flavour
attribute.
I think you would be better off explicitly subclassing one of the other classes, such as PosixPath
or WindowsPath
.
Path
is somewhat of an abstract class that is actually instantiated as on of two possible subclasses depending on the OS:
PosixPath
or WindowsPath
https://docs.python.org/3/library/pathlib.html
This base class looks for an internal (private) class variable to determine what type of object it actually is, and this variable is called _flavour
You have two choices:
- Derive your class from one of the concrete subclasses.
This is the best choice as it will save you dealing with undocumented library internals and guarantee your code will not break on different versions of the library.
You will need to define your class differently based on the OS if you want your code to be cross-platform.
EDIT: Following dank8’s comment, the original sample code had an issue: Path
does not take __init__
parameters, but __new__
parameters instead.
Also, there is a better way to determine concrete subclass at runtime.
The code will look like this:
from pathlib import Path
class P1(type(Path())):
def __new__(cls, *pathsegments):
return super().__new__(cls, *pathsegments)
- Alternatively fill in the class variable yourself.
This is not recommended as you will be using undocumented elements that could break at any time with any change to the library, but it will allow you to inherit fromPath
directly.
Note, there may be other issues if you decide to use this method!
import os
from pathlib import _PosixFlavour
from pathlib import _WindowsFlavour
class P1(Path):
_flavour = _PosixFlavour() if os.name == 'posix' else _WindowsFlavour()
def __new__(cls, *pathsegments):
return super().__new__(cls, *pathsegments)
I solved it. Mokey patching is the way to go.
define functions just like this
def method1(self, other):
blah
Path.method1 = method1
The fastest, easiest, most convenient solution, zero downsides. Autosuggest in Pycharm works well.
UPDATE:
I got THE solution (works with linter and auto suggestor):
class MyPath(type(Path()), Path):
pass