-
Notifications
You must be signed in to change notification settings - Fork 22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make sure that __dir__
returns new copies of __all__
#135
base: main
Are you sure you want to change the base?
Conversation
__dir__ should probably return new copies for each call so that the result will stay the same regardless of how the result of previous calls was modified.
__dir__
returns a new copies of __all__
__dir__
returns new copies of __all__
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #135 +/- ##
==========================================
+ Coverage 95.74% 96.01% +0.27%
==========================================
Files 5 5
Lines 235 251 +16
==========================================
+ Hits 225 241 +16
Misses 10 10 ☔ View full report in Codecov by Sentry. |
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.
Makes sense to me. I will say, from reading the documentation, it surprises me that dir()
does not make a copy, since it says it will sort the result.
@@ -90,7 +90,7 @@ def __getattr__(name): | |||
raise AttributeError(f"No {package_name} attribute {name}") | |||
|
|||
def __dir__(): | |||
return __all__ | |||
return list(__all__) |
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.
Very minor nit: I prefer to use the copy method over a new constructor. As a reader, it tells me that __all__
is already a list and not being converted.
return list(__all__) | |
return __all__.copy() |
On a more theoretical note (as these lists will generally be small enough to be negligible), using a method over a constructor permits the implementation to skip generic logic to handle multiple input types, so upstream optimizations in Python can potentially have higher impact.
Some timeit logs (py3.13)
In [2]: _dir = dir()
In [4]: %timeit _dir[:]
41.7 ns ± 0.858 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
In [5]: %timeit list(_dir)
46.5 ns ± 0.816 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
In [6]: %timeit _dir.copy()
38.3 ns ± 0.667 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
In [9]: x = list(range(100000))
In [10]: %timeit list(x)
273 μs ± 12.1 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
In [11]: %timeit x.copy()
243 μs ± 8.06 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
In [12]: %timeit x[:]
231 μs ± 11.1 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
In [13]: x = list(range(1000))
In [14]: %timeit list(x)
1.13 μs ± 15 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
In [15]: %timeit x.copy()
1.13 μs ± 16 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
In [16]: %timeit x[:]
1.15 μs ± 16.9 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
In [17]: x = list(range(10))
In [18]: %timeit list(x)
36.3 ns ± 0.642 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
In [19]: %timeit x.copy()
26.5 ns ± 0.504 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
In [20]: %timeit x[:]
30.6 ns ± 0.748 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
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.
Yeah, I was debating that myself but chose to use list()
over .copy()
to be consistent with
lazy-loader/lazy_loader/__init__.py
Line 99 in 4e78314
return __getattr__, __dir__, list(__all__) |
I'm happy to update both to .copy()
if desired. 😊
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.
Sure, that's fine with me. Wasn't going to suggest it since it's a little out-of-scope, but I don't see the harm in changing it in passing.
__dir__()
should probably return new copies for each call so that the result will stay the same regardless of how the result of previous calls was modified. That seems to be the standard behavior for Python's defaultobject.__dir__
.E.g.,
should all pass but currently they don't.