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 — GH-72904: Add `glob.translate()` function by barneygale · Pull Request #106703 · python/cpython

Conversation

@barneygale

@barneygale barneygale commented Jul 12, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor

Add glob.translate() function that converts a pathname with shell wildcards to a regular expression. The regular expression is used by pathlib to implement match() and glob().

This function differs from fnmatch.translate() in that wildcards do not match path separators by default, and that a * pattern segment matches precisely one path segment. When recursive is set to true, ** pattern segments match any number of path segments, and ** cannot appear outside its own segment.

In pathlib, this change speeds up directory walking (because _make_child_relpath() does less work), makes path objects smaller (they don't need a _lines slot), and removes the need for some gnarly code.

  • Issue: '*' matches entire path in fnmatch #72904

📚 Documentation preview 📚: https://cpython-previews--106703.org.readthedocs.build/

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

mariosasko, wimglenn, and edgarrmondragon reacted with thumbs up emoji
All reactions
  • 👍 3 reactions
@barneygale
pythonGH-72904: Add optional *seps* argument to fnmatch.translate()
bbfd404
If a sequence of path separators is given to the new argument,
`translate()` produces a pattern that matches similarly to
`pathlib.Path.glob()`. Specifically:

- A `*` pattern segment matches precisely one path segment.
- A `**` pattern segment matches any number of path segments
- If `**` appears in any other position within the pattern, `ValueError` is
  raised.
- `*` and `?` wildcards in other positions don't match path separators.

This change allows us to factor out a lot of complex code in pathlib.
@barneygale barneygale added the topic-pathlib label Jul 12, 2023
@bedevere-bot bedevere-bot mentioned this pull request Jul 12, 2023
'*' matches entire path in fnmatch #72904
Closed
@barneygale
barneygale marked this pull request as ready for review July 13, 2023 00:41
@bedevere-bot bedevere-bot added the awaiting core review label Jul 13, 2023
barneygale added 3 commits July 13, 2023 12:43
@barneygale
Simplify _make_child_relpath() further
da9948d
@barneygale
Fix default value in docs
a07118b
@barneygale
Match style of surrounding fnmatch code a little better.
2728dcd
@barneygale barneygale added the performance Performance or resource usage label Jul 13, 2023
@barneygale barneygale mentioned this pull request Jul 16, 2023
GH-73435: Implement recursive wildcards in pathlib.PurePath.match() #101398
Merged
@barneygale
Merge branch 'main' into pythongh-72904-fnmatch-seps
fbcf4e3
@barneygale

barneygale commented Jul 19, 2023

Copy link
Copy Markdown
Contributor Author

~20% globbing speedup:

$ ./python -m timeit -s 'from pathlib import Path; p = Path()' 'list(p.glob("**/*", follow_symlinks=False))'
2 loops, best of 5: 175 msec per loop  # before
2 loops, best of 5: 146 msec per loop  # after
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

barneygale added 2 commits July 19, 2023 22:17
@barneygale
Docs + naming improvements
a0ce9c4
@barneygale
Replace *seps* with *sep*
5b620fb
@barneygale barneygale changed the title GH-72904: Add optional *seps* argument to fnmatch.translate() GH-72904: Add optional *sep* argument to fnmatch.translate() Jul 26, 2023
@barneygale barneygale mentioned this pull request Jul 26, 2023
Glob behavior is inconsistent across libraries #106747
Closed
jaraco
jaraco reviewed Aug 4, 2023
View reviewed changes
Comment thread Doc/library/fnmatch.rst Resolved

Uh oh!

There was an error while loading. Please reload this page.

@barneygale @jaraco
Update Doc/library/fnmatch.rst
51f2698
Co-authored-by: Jason R. Coombs <[email protected]>
jaraco
jaraco approved these changes Aug 4, 2023
View reviewed changes
Comment thread Doc/library/fnmatch.rst Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/fnmatch.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/fnmatch.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/fnmatch.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/test/test_fnmatch.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/test/test_fnmatch.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@bedevere-bot bedevere-bot added awaiting merge and removed awaiting core review labels Aug 4, 2023
jaraco
jaraco reviewed Aug 4, 2023
View reviewed changes
Comment thread Lib/fnmatch.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

@jaraco jaraco mentioned this pull request Aug 5, 2023
Generalize glob functionality. jaraco/zipp#108
Merged
4 tasks done
barneygale added 2 commits August 12, 2023 02:51
@barneygale
Move to glob.translate()
9c8c3f3
@barneygale
Whoops
8518ea2
@barneygale barneygale changed the title GH-72904: Add optional *sep* argument to fnmatch.translate() GH-72904: Add glob.translate() function Aug 12, 2023
@barneygale

barneygale commented Aug 12, 2023

Copy link
Copy Markdown
Contributor Author

I've moved this to a new glob.translate() function.

It was easy enough to implement a recursive argument, so I did that and made its default False to match glob().

It's much harder to implement an include_hidden argument, so I've left that for now. I don't feel great about it, tbh.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@barneygale
barneygale requested a review from jaraco August 12, 2023 02:19
barneygale added 2 commits August 13, 2023 21:33
@barneygale
Deduplicate code to handle character sets
75129c8
@barneygale
Add support for include_hidden=False
2505590
barneygale added 3 commits September 27, 2023 00:16
@barneygale
Fix _make_child_relpath()
afb2d43
@barneygale
Minor code improvements
d73df1b
@barneygale
Add another test for include_hidden=False
c70afe3
@barneygale

barneygale commented Sep 27, 2023

Copy link
Copy Markdown
Contributor Author

Right, after some futzing around I'm going to mark this PR as ready again.

In fnmatch.py, I've split the translate() method into _translate() and _join_translated_parts(). This minimises the diff, risk, and performance impact in that module.

(In #109879 I've re-implemented fnmatch.translate(), but that PR is an optional side-quest now)

In glob.py, I've spent some time making the implementation of translate(include_hidden=False) as clear and performant as I can. There's still a fair whack of regex involved but I think it's followable.

In pathlib.py, I've made pattern matching use str(path) directly, rather than a slight variant that represents empty paths as '' rather than '.'.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@barneygale
barneygale marked this pull request as ready for review September 27, 2023 00:40
@bedevere-app bedevere-app Bot added the awaiting review label Sep 27, 2023
@mariosasko mariosasko mentioned this pull request Sep 28, 2023
.glob("**/filename") returns incorrect results fsspec/filesystem_spec#1380
Closed
@barneygale barneygale mentioned this pull request Sep 29, 2023
User-defined virtual path objects #110109
Closed
8 tasks done
barneygale added 3 commits September 30, 2023 17:27
@barneygale
Merge branch 'main' into pythongh-72904-fnmatch-seps
9cb2952
@barneygale
Add whatsnew entry
f178b14
@barneygale
Collapse adjacent ** segments.
4a726aa
@barneygale

barneygale commented Sep 30, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

Timings:

$ ./python -m timeit -n 5 -s 'from glob import glob' 'list(glob("**/*", recursive=True, include_hidden=True))'
5 loops, best of 5: 105 msec per loop  # for interest
$ ./python -m timeit -n 5 -s 'from pathlib import Path; p = Path()' 'list(p.glob("**/*", follow_symlinks=True))'
5 loops, best of 5: 86.6 msec per loop  # before
5 loops, best of 5: 72.7 msec per loop  # after

So Path.glob() is ~20% faster than before, and ~45% faster than glob.glob(), at least for this test case.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

AA-Turner
AA-Turner reviewed Sep 30, 2023
View reviewed changes

@AA-Turner AA-Turner left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A desk review:

A

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

barneygale reacted with thumbs up emoji
All reactions
  • 👍 1 reaction
Comment thread Lib/glob.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/glob.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/glob.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/glob.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/pathlib.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread Lib/glob.py Resolved

Uh oh!

There was an error while loading. Please reload this page.

barneygale and others added 2 commits September 30, 2023 23:21
@barneygale @AA-Turner
Apply suggestions from code review
78292eb
Co-authored-by: Adam Turner <[email protected]>
@barneygale
Add comment explaining regex that consumes "empty" paths.
5d4062c
@barneygale
barneygale requested a review from AA-Turner October 3, 2023 19:59
@barneygale

barneygale commented Oct 5, 2023

Copy link
Copy Markdown
Contributor Author

I've realised that the docs are a bit skew-whiff. Fix is in a separate PR: #110418

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@barneygale barneygale mentioned this pull request Oct 13, 2023
maintainership & project lead jazzband/pathlib2#70
Open
@barneygale barneygale mentioned this pull request Oct 25, 2023
gh-111259: Optimize recursive wildcards in pathlib #111303
Merged
@barneygale
Merge branch 'main' into pythongh-72904-fnmatch-seps
1ad624d
@barneygale
barneygale requested a review from serhiy-storchaka October 28, 2023 20:54
@encukou

encukou commented Nov 3, 2023

Copy link
Copy Markdown
Member

Is it correct to keep duplicated path separators?

>>> glob.translate('a//b')
'(?s:a//b)\\Z'
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@barneygale

barneygale commented Nov 3, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

Is it correct to keep duplicated path separators?

>>> glob.translate('a//b')
'(?s:a//b)\\Z'

glob() keeps them:

>>> os.makedirs('a/b')
>>> glob.glob('a//b')
['a//b']

So I reckon yes?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@barneygale

barneygale commented Nov 7, 2023

Copy link
Copy Markdown
Contributor Author

Also, the number of additional slashes is meaningful in some cases, e.g. in Windows UNC paths or POSIX paths starting with two forward slashes. I don't think a pattern like /foo should match a path like //foo, and vice-versa.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@barneygale

barneygale commented Nov 12, 2023 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Contributor Author

Hey @encukou, do you think I can merge this, or should I wait for a more complete review from someone?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@encukou

encukou commented Nov 13, 2023

Copy link
Copy Markdown
Member

Oh, I should have been more clear that I wouldn't get to a thorough review in any reasonable time.
You already have a few approvals, merge away!

barneygale reacted with heart emoji
All reactions
  • ❤️ 1 reaction

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@barneygale
barneygale merged commit cf67ebf into python:main Nov 13, 2023
@bedevere-app bedevere-app Bot removed the awaiting review label Nov 13, 2023
aisk pushed a commit to aisk/cpython that referenced this pull request Feb 11, 2024
@barneygale @jaraco
@AA-Turner @aisk
pythonGH-72904: Add glob.translate() function (python#106703)
610266f
Add `glob.translate()` function that converts a pathname with shell wildcards to a regular expression. The regular expression is used by pathlib to implement `match()` and `glob()`.

This function differs from `fnmatch.translate()` in that wildcards do not match path separators by default, and that a `*` pattern segment matches precisely one path segment. When *recursive* is set to true, `**` pattern segments match any number of path segments, and `**` cannot appear outside its own segment.

In pathlib, this change speeds up directory walking (because `_make_child_relpath()` does less work), makes path objects smaller (they don't need a `_lines` slot), and removes the need for some gnarly code.

Co-authored-by: Jason R. Coombs <[email protected]>
Co-authored-by: Adam Turner <[email protected]>
Glyphack pushed a commit to Glyphack/cpython that referenced this pull request Sep 2, 2024
@barneygale @jaraco
@AA-Turner @Glyphack
pythonGH-72904: Add glob.translate() function (python#106703)
ba33158
Add `glob.translate()` function that converts a pathname with shell wildcards to a regular expression. The regular expression is used by pathlib to implement `match()` and `glob()`.

This function differs from `fnmatch.translate()` in that wildcards do not match path separators by default, and that a `*` pattern segment matches precisely one path segment. When *recursive* is set to true, `**` pattern segments match any number of path segments, and `**` cannot appear outside its own segment.

In pathlib, this change speeds up directory walking (because `_make_child_relpath()` does less work), makes path objects smaller (they don't need a `_lines` slot), and removes the need for some gnarly code.

Co-authored-by: Jason R. Coombs <[email protected]>
Co-authored-by: Adam Turner <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@jaraco jaraco Awaiting requested review from jaraco
@AA-Turner AA-Turner Awaiting requested review from AA-Turner AA-Turner is a code owner
@serhiy-storchaka serhiy-storchaka Awaiting requested review from serhiy-storchaka

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

performance Performance or resource usage topic-pathlib

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

5 participants

@barneygale @encukou @jaraco @AA-Turner @bedevere-bot
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.