GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — subclasses of pathlib.PurePosixPath never call __init__ or __new__ · Issue #85281 · python/cpython

subclasses of pathlib.PurePosixPath never call __init__ or __new__ #85281

New issue
Copy link
New issue
Copy link
Closed
Closed
subclasses of pathlib.PurePosixPath never call __init__ or __new__#85281
Copy link
Labels
3.10 (EOL)end of lifeend of life3.7 (EOL)end of lifeend of life3.8 (EOL)end of lifeend of life3.9 (EOL)end of lifeend of lifestdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytopic-pathlibtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

Description

conchylicultor
mannequin
opened on Jun 25, 2020
Issue body actions
BPO 41109
Nosy @pitrou, @websurfer5, @Conchylicultor, @Louis-Vincent
PRs
  • bpo-41109: subclasses of pathlib.Path and pathlib.PurePath now call the subclass's __init__() and __new__() functions when returning new objects #21920
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2020-06-25.02:51:23.231>
    labels = ['type-bug', '3.8', '3.9', '3.10', '3.7', 'library']
    title = 'subclasses of pathlib.PurePosixPath never call __init__ or __new__'
    updated_at = <Date 2020-08-19.06:37:57.142>
    user = 'https://github.com/conchylicultor'

    bugs.python.org fields:

    activity = <Date 2020-08-19.06:37:57.142>
    actor = 'Jeffrey.Kintscher'
    assignee = 'none'
    closed = False
    closed_date = None
    closer = None
    components = ['Library (Lib)']
    creation = <Date 2020-06-25.02:51:23.231>
    creator = 'conchylicultor'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 41109
    keywords = ['patch']
    message_count = 12.0
    messages = ['372297', '372298', '375030', '375031', '375041', '375047', '375162', '375332', '375333', '375334', '375364', '375638']
    nosy_count = 4.0
    nosy_names = ['pitrou', 'Jeffrey.Kintscher', 'conchylicultor', 'louis-vincent.boudre']
    pr_nums = ['21920']
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue41109'
    versions = ['Python 3.6', 'Python 3.7', 'Python 3.8', 'Python 3.9', 'Python 3.10']

    Activity

    Next
    1. conchylicultor commented on Jun 25, 2020

      @conchylicultor
      conchylicultormannequin
      on Jun 25, 2020
      MannequinAuthor
      More actions

      I have a subclass GithubPath of PurePosixPath.

      class GithubPath(pathlib.PurePosixPath):
      
        def __new__(cls, *args, **kwargs):
          print('New')
          return super().__new__(cls, *args, **kwargs)
      
        def __init__(self, *args, **kwargs):
          print('Init')
          super().__init__()
      

      Calling child.parent create a new GithubPath but without ever calling new nor init. So my subclass is never notified it is created.

      p = GithubPath()  # Print "New", "Init"
      
      p.parent  # Create a new GithubPath but bypass the constructors
      

      The reason seems to be that parent calls _from_parts which create a new object through object.__new__(cls):

      cpython/Lib/pathlib.py

      Line 689 in cf18c9e

      def _from_parts(cls, args, init=True):

      A hack is to subclass _init but it seems hacky as it relies on internal implementation detail.

    2. conchylicultor
      added
      3.7 (EOL)end of lifeend of life
      3.8 (EOL)end of lifeend of life
      3.9 (EOL)end of lifeend of life
      3.10 (EOL)end of lifeend of life
      type-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error
      on Jun 25, 2020
    3. conchylicultor commented on Jun 25, 2020

      @conchylicultor
      conchylicultormannequin
      on Jun 25, 2020
      MannequinAuthor
      More actions

      Note that this likely affect all methods which returns new Path by calling _from_parts or _from_parsed_parts, like .absolute, .resolve,...

    4. websurfer5 commented on Aug 8, 2020

      @websurfer5
      websurfer5mannequin
      on Aug 8, 2020
      Mannequin
      More actions

      The workaround is to override _init(), which I agree is not desirable.

      This is the relevant code in PurePath, which is the super class of PurePosixPath:

          @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

      ...

          def _init(self):
              # Overridden in concrete Path
              pass

      To me, the clean way to get the desired behavior seems like it would be to have _init() call self.__init__().

          def _init(self):
              # Overridden in concrete Path
              self.__init__()

      This fixes p.parent, but GithubPath() ends up calling GithubPath.__init__() twice – the first time by PurePath.__new__() calling PurePath._init() and the second time by the GithubPath object creation.

    5. websurfer5 commented on Aug 8, 2020

      @websurfer5
      websurfer5mannequin
      on Aug 8, 2020
      Mannequin
      More actions

      Clarification:

      PurePath.__new__() calls PurePath._from_parts(), which then calls PurePath._init()

    6. conchylicultor commented on Aug 8, 2020

      @conchylicultor
      conchylicultormannequin
      on Aug 8, 2020
      MannequinAuthor
      More actions

      Before solving this issue, I think it would be best to think on a more generic solution on how to make Pathlib more extensible. Related to: https://discuss.python.org/t/make-pathlib-extensible/3428

      For instance, if childs created with p.parent(), p / 'subdir' need to forward some state (e.g. RemotePath(path, password=, user=)).

      Rather than __init__, maybe there should be some __post_init__ like dataclasses.

    7. 3 remaining items

    8. Louis-Vincent commented on Aug 13, 2020

      @Louis-Vincent
      Louis-Vincentmannequin
      on Aug 13, 2020
      Mannequin
      More actions

      This is not true, because the classmethod use the library shortcuts the
      class mro order, to prevent infinite loop inthe __new__. However, it was
      using __init__ before hand, we would
      Not have this issue

      Le jeu. 13 août 2020 à 15:27, Jeffrey Kintscher <[email protected]> a
      écrit :

      Jeffrey Kintscher <[email protected]> added the comment:

      Adding __init__() to PurePath complicates things and doesn't provide any
      benefit. A subclass that calls super.__init__() ends up invoking
      object.__init__(), which is perfectly fine.

      I was able to find a solution by calling type(self)() instead of
      object.__new__() in most cases. I am working on a PR.

      ----------


      Python tracker <[email protected]>
      <https://bugs.python.org/issue41109\>


    9. Louis-Vincent commented on Aug 13, 2020

      @Louis-Vincent
      Louis-Vincentmannequin
      on Aug 13, 2020
      Mannequin
      More actions

      This is not true, because the classmethod used the library shortcuts the
      class mro order, this is to prevent infinite loop inthe __new__. However,
      If it was using __init__ before hand, we would
      Not have this issue

      Le jeu. 13 août 2020 à 15:31, Louis-Vincent Boudreault <
      [email protected]> a écrit :

      This is not true, because the classmethod use the library shortcuts the
      class mro order, to prevent infinite loop inthe __new__. However, it was
      using __init__ before hand, we would
      Not have this issue

      Le jeu. 13 août 2020 à 15:27, Jeffrey Kintscher <[email protected]>
      a écrit :

      >
      > Jeffrey Kintscher <[email protected]> added the comment:
      >
      > Adding __init__() to PurePath complicates things and doesn't provide any
      > benefit. A subclass that calls super.__init__() ends up invoking
      > object.__init__(), which is perfectly fine.
      >
      > I was able to find a solution by calling type(self)() instead of
      > object.__new__() in most cases. I am working on a PR.
      >
      > ----------
      >
      > _______________________________________
      > Python tracker <[email protected]>
      > <https://bugs.python.org/issue41109\>
      > _______________________________________
      >

    10. websurfer5 commented on Aug 14, 2020

      @websurfer5
      websurfer5mannequin
      on Aug 14, 2020
      Mannequin
      More actions

      The current implementation calls object.__new__(cls), where cls is the child class type, from within a class method (@classmethod). This is fine for Path.__new__() and PurePath.__new__(), which are called by the child class's __new__(), because we don't want them to recursively call the child class's __new__() when the child class is created. This all works as expected when the child class is instantiated outside of Path and PurePath, and the child's __init__() gets called as expected. I don't see any point in making changes to this behavior.

      When one of approximately 20 PurePath and Path functions and properties instantiate a new child class object the same way PurePath.__new__() and Path.__new__() do, the child class's __new__() and __init__() functions are not called. This is the problem we are trying to solve.

      My fix is to add normal functions (i.e. not decorated with @classmethod) to instantiate child class objects using

      obj = type(self)()

      This creates a child class instance, and the child's __new__() and __init__() functions get called.

      Once I have finished re-plumbing Path and PurePath to use the new functions and created the necessary unit tests (to make sure I didn't break anything), I will also look at adding
      a proper __init__() function to the two classes instead of having __new__() initialize the member variables. I didn't mean to imply that __init__() isn't useful. It is required to allow the child class to initialize its own variable. I just meant it isn't required to force calling __init__() and __new__() in the child class.

    11. websurfer5 commented on Aug 19, 2020

      @websurfer5
      websurfer5mannequin
      on Aug 19, 2020
      Mannequin
      More actions

      I created a PR that should provide the desired behavior: __init__() and __new__() get called in subclass objects that are created by Path and PurePath. Also, Path and PurePath now have __init__() functions, and the __new__() functions only return new objects and rely upon __init__() to perform the object initialization.

    12. websurfer5
      added
      stdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directory
      on Aug 19, 2020
    13. ezio-melotti
      transferred this issue fromon Apr 10, 2022
    14. brettcannon
      added
      topic-pathlib
      on Jun 21, 2022
    15. barneygale commented on Jan 5, 2023

      @barneygale
      barneygale
      on Jan 5, 2023
      Contributor
      More actions

      I believe #100481 would address this.

    16. barneygale
      added a commit that references this issue on Feb 4, 2023

      pythonGH-101362 - Optimise pathlib by deferring path normalisation

      b428069
    17. barneygale
      added a commit that references this issue on Apr 3, 2023

      GH-76846, GH-85281: Call `__new__()` and `__init__()` on pathlib subc…

      11c3020
    18. barneygale commented on Apr 3, 2023

      @barneygale
      barneygale
      on Apr 3, 2023
      Contributor
      More actions

      Fixed in Python 3.12 / #102789 / 11c3020

    19. barneygale
      closed this as completedon Apr 3, 2023
    20. gaogaotiantian
      added a commit that references this issue on Apr 8, 2023

      pythonGH-76846, pythonGH-85281: Call `__new__()` and `__init__()` on …

      2658fae
    21. warsaw
      added a commit that references this issue on Apr 11, 2023

      pythonGH-76846, pythonGH-85281: Call `__new__()` and `__init__()` on …

      ecd5328
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

      3.10 (EOL)end of lifeend of life3.7 (EOL)end of lifeend of life3.8 (EOL)end of lifeend of life3.9 (EOL)end of lifeend of lifestdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytopic-pathlibtype-bugAn unexpected behavior, bug, or errorAn unexpected behavior, bug, or error

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        You are visitor 01402520 — GeezerVison does not store the pages you look up.
        Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.