Ross R
Ross R

Reputation: 9403

How to subclass a class that has a __new__ and relies on the value of cls?

My specific use case is I'm trying to subclass pathlib.Path. I want to be able to add or override some functionality, but I also want to inherit all of Path. Path has a __new__ and in it has:

if cls is Path:
    cls = WindowsPath if os.name == 'nt' else PosixPath

In other words, Path requires passing the proper class to it. The problem is I don't know how to both create MY class and call Path.__new__ with cls == Path.

I've tried many things and each one gives me a different problem. This one gives me AttributeError: type object 'RPath' has no attribute '_flavour' because I'm trying to override the parent class.

Python3:

class RPath(Path):
    def __new__(cls, basedir, *args, **kwargs):
         return Path.__new__(cls, *args, **kwargs)

    def __init__(self, basedir, *pathsegs):
        super().__init__()
        self.basedir = basedir

    def newfunction(self):
        print('Something new')

And this one gives returns a Path object, and hence doesn't allow me to do my overrides.

def __new__(cls, basedir, *args, **kwargs):
    return Path.__new__(Path, *args, **kwargs)

I also tried various usages of super(), to no avail.

This seems like it should be pretty easy. What am I missing?

Update: What am I trying to accomplish? Specifically, I want to make class RPath(basedir, *pathsegments):

rpath=RPath('\root\dir', 'relpath\path2\file.ext)
assert rpath.basedir == '\root\dir' # True
rpath.rebase('\new_basedir')
assert rpath.basedir === '\newbasedir' # True
# And while I'm at it
assert rpath.str == str(rpath)  # make str a property == __str__(self)

Upvotes: 6

Views: 540

Answers (3)

o11c
o11c

Reputation: 16056

Remember that you are under absolutely no obligation to actually call your superclass's __new__ or __init__. The only constraint is that you ultimately call object.__new__(cls) in order to allocate an object of type cls.

So, simply copy the relevant logic out of your superclass's __new__ into your own.

Upvotes: 0

Ross R
Ross R

Reputation: 9403

Here is a solution, though it doesn't use inheritance. I still suspect that there is a straight-up, simple inheritance way to do it (though see BrenBarn's response for the other side).

This method uses composition. The key is using __getattr__ and getattr() to automatically delegate all requests not found in the wrapper class to the wrapped class. Here is an example:

class RPath(object):
    def __init__(self, basedir, *pathsegs):
        self.p = Path(*pathsegs)
        self.basedir = basedir

    # Override existing behavior
    def __eq__(self, other):
        return type(other) is RPath and self.basedir == other.basedir and self.p == other.p      

    # Add new behavior
    def cp(self):
        return self.basedir / self.p      

    # Everything not found here, automatically delegate to Path
    def __getattr__(self, attr):
        return getattr(self.cp, attr)

Upvotes: 1

BrenBarn
BrenBarn

Reputation: 251373

I don't think that's going to be possible in the usual way. But even if you could do it, it wouldn't work, because what Path is doing is not returning a plain Path either, it's returning some subclass (WindowsPath or PosixPath). So your overrides to Path won't take effect, because if you were able to inherit Path.__new__, it would still return, say, a WindowsPath, and WindowsPath inherits from Path, not your custom path subclass.

It seems pathlib.Path has an idiosyncratic class structure, and you'll have to do some special work to duplicate it. At a first guess, you would need to make your own subclasses of WindowsPath and PosixPath, and then make a Path subclass that delegates to instantiate one of those instead of itself.

Upvotes: 2

Related Questions