This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: pathlib.Path._from_parsed_parts should call cls.__new__(cls)
Type: behavior Stage:
Components: Library (Lib) Versions: Python 3.6
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: pablogsal, pitrou, qb-cea
Priority: normal Keywords:

Created on 2018-01-25 12:27 by qb-cea, last changed 2022-04-11 14:58 by admin.

Files
File name Uploaded Description Edit
reproducer.py qb-cea, 2018-01-25 12:27 reproducer (tested with python3.4)
Messages (5)
msg310671 - (view) Author: (qb-cea) Date: 2018-01-25 12:27
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.
msg310758 - (view) Author: (qb-cea) Date: 2018-01-26 12:02
Typo in the code of the previous comment:

- __slots__ = ("new_attr",)
+ __slots__ = ("_new_attr",)
msg310888 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2018-01-27 21:34
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>
msg310889 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2018-01-27 21:35
Sorry, the exception of object.__new__(cls) is AttributeError: _drv
msg310890 - (view) Author: Pablo Galindo Salgado (pablogsal) * (Python committer) Date: 2018-01-27 21:38
Antoine, is the reason of calling object.__new__(cls) in `_from_parsed_parts` that the code does not raise on creation?
History
Date User Action Args
2022-04-11 14:58:57adminsetgithub: 76846
2018-01-27 21:38:03pablogsalsetmessages: + msg310890
2018-01-27 21:35:11pablogsalsetmessages: + msg310889
2018-01-27 21:34:12pablogsalsetnosy: + pablogsal
messages: + msg310888
2018-01-26 12:02:58qb-ceasettype: behavior
messages: + msg310758
2018-01-25 12:27:25qb-ceacreate