Hi,
I tried subclassing pathlib.Path and provide it with a new attribute (basically an accessor to an extended attribute). I am rather new to the concept of __slots__ and __new__() but here is how I pictured it should look:
from errno import ENODATA
from os import getxattr, setxattr
from pathlib import Path
class Path_(type(Path())):
__slots__ = ("new_attr",)
def __new__(cls, *args, new_attr=None, **kwargs):
self = super().__new__(cls, *args, **kwargs)
self._new_attr = new_attr
return self
@property
def new_attr(self):
if self._new_attr:
return self._new_attr
try:
new_attr = getxattr(self, "user.new_attr")
except OSError as exc:
if exc.errno != ENODATA:
raise exc
else:
self._new_attr = new_attr
return new_attr
new_attr = b"something_dynamic" # for example uuid4().bytes
setxattr(self, "user.new_attr", new_attr)
self._new_attr = new_attr
return new_attr
The issue I have is that although my class defines its own __new__() method, it is not always called by the methods of pathlib.Path. For example:
>>> Path_("/etc").parent.new_attr
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/path/to/reproducer.py", line 19, in new_attr
if self._new_attr:
AttributeError: _new_attr
The current workaround I use consists in redefining pathlib.Path's _from_parsed_parts() method in my class: instead of creating a new object using:
object.__new__(cls)
my implementation uses:
cls.__new__(cls)
This is the first time I play with the __new__() special method, so it is possible I missed something, if so, sorry for the noise.
This behaviour is because "parent" descriptor ends calling:
@classmethod
def _from_parsed_parts(cls, drv, root, parts, init=True):
self = object.__new__(cls)
self._drv = drv
self._root = root
self._parts = parts
if init:
self._init()
return self
and this calls object.__new__ and this call raises AttributeError: new_attr. Notice that object.__new__(cls) will not raise as this snippet shows:
>>>: class A:
...: def __new__(*args):
...: raise ZeroDivisionError()
...:
>>> A()
---------------------------------------------------------------------------
ZeroDivisionError Traceback (most recent call last)
<python> in <module>()
----> 1 A()
<python> in __new__(*args)
1 class A:
2 def __new__(*args):
----> 3 raise ZeroDivisionError()
4
ZeroDivisionError:
>>> object.__new__(A)
>>> <__main__.A at 0x7f6239c17860>