Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

python

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)
like image 803
Ross R Avatar asked Jun 18 '15 19:06

Ross R


2 Answers

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.

like image 96
BrenBarn Avatar answered Oct 13 '22 14:10

BrenBarn


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)
like image 34
Ross R Avatar answered Oct 13 '22 12:10

Ross R