9

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)

Alex Deft
  • 2,531
  • 1
  • 19
  • 34

3 Answers3

7

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:

  1. 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)
  1. 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)
Lev M.
  • 6,088
  • 1
  • 10
  • 23
  • Could you please flesh out your second piece of code? `os.name ==` not `=`, also it is throwing an error saying `_WindowsFalvour()` not defined. Am I missing something? – Alex Deft May 08 '20 at 23:35
  • In your first solution, you're still inheriting from `Path`, so the problem persists – Alex Deft May 09 '20 at 00:00
  • @AlexDeft I fixed some errors in the code. The flavour classes need to be imported explicitly, and there was a type in the `if` and in one of the class names. Hope it works for you now. – Lev M. May 09 '20 at 06:54
  • @LevM. could you please confirm when to use `super().__init__(*pathsegments)`. Python 3 returned `TypeError: takes exactly one argument`. From what i can find, super is not required inside of `__init__()`. Source: (tom-aarsen, Jun 21, 2022)(https://stackoverflow.com/a/72691320/6345724) – dank8 Feb 20 '23 at 02:02
  • 1
    @dank8 You are suppose to use `super` if the superclass constructor requires arguments other than `self`. Both `PosixPath` and `WindowsPath` constructors can take a variable number of arguments to build a path from. I am not sure what is causing this error. I can reproduce it, but all documentation I can find points to the code being correct. It is different from the example you linked since these classes are not generics. I will look in to this, but if you find an answer before me, please comment here. – Lev M. Feb 23 '23 at 21:59
3

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.

larsks
  • 277,717
  • 41
  • 399
  • 399
  • If I want my code to be machine agnostic, how can I do it? – Alex Deft May 08 '20 at 23:36
  • Well, I guess you could inherit from `Path` and then reimplement the `__new__` method (it's only 7 lines in `pathlib.py`). It might help if you updated your question to show what you're actually trying to accomplish: there might be ways of doing that other than subclassing `Path`. – larsks May 09 '20 at 02:13
  • I really don't know how to reimplement `__new__`. What I'm trying to accomplish is simply extending the funcionality of `Path`, e.g. add method to glob without returning hidden files and folders, just like `glob`, also make it return a list instead of generator, basically customizing it and other stuff like that. – Alex Deft May 09 '20 at 04:38
1

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

Alex Deft
  • 2,531
  • 1
  • 19
  • 34