-
-
Notifications
You must be signed in to change notification settings - Fork 31k
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
sys._enablelegacywindowsfsencoding() don't apply to os.fsencode and os.fsdecode #73427
Comments
The doc say that calling "sys._enablelegacywindowsfsencoding()" is equivalent to use "PYTHONLEGACYWINDOWSFSENCODING" environment variable. In fact, this no apply to "os.fsencode" and "os.fsdecode". Example with Python 3.6 64Bits on Windows 7 64 bits : EXAMPLE CODE 1 (sys._enablelegacywindowsfsencoding()): import sys
import os
# Force the use of legacy encoding like versions of Python prior to 3.6.
sys._enablelegacywindowsfsencoding()
# Show actual file system encoding
encoding = sys.getfilesystemencoding()
print('File system encoding:', encoding) # os.fsencode(filename) VS filename.encode(File system encoding)
First is encoded with "utf-8" and not "mbcs" (The actual File system encoding) EXAMPLE CODE 2 (PYTHONLEGACYWINDOWSFSENCODING): import sys
import os # Force the use of legacy encoding like versions of Python prior to 3.6. # Show actual file system encoding
encoding = sys.getfilesystemencoding()
print('File system encoding:', encoding) # os.fsencode(filename) VS filename.encode(File system encoding)
Everything encoded with "mbcs" (The actual File system encoding) In "os.fsencode" and "os.fsdecode" encoding and errors are cached on start and never updated by "sys._enablelegacywindowsfsencoding()" after. |
If you import os first then that's acceptable and we should document it more clearly. Try calling enable before importing os. I wouldn't be surprised if os is imported automatically, in which case we need to figure out some alternate caching mechanism that can be reset by the enable call (or demonstrate that the caching is of no benefit). |
import sys
# Force the use of legacy encoding like versions of Python prior to 3.6.
sys._enablelegacywindowsfsencoding()
# Show actual file system encoding
encoding = sys.getfilesystemencoding()
print('File system encoding:', encoding)
# os.fsencode(filename) VS filename.encode(File system encoding)
import os
print(os.fsencode('é'), 'é'.encode(encoding))
The result is the same. |
Then we do in fact need to make os.fsencode/fsdecode either stop caching the encoding completely, or figure out a way to reset the cache when that function is called. |
Adding Victor, who implemented the fs codec. AFAIK, it's not possible to change the encoding after interpreter initialization, since it will have been already used for many different things by the time you get to executing code. |
My experience with changing the Python "filesystem encoding" (sys.getfilesystemencoding()) at runtime: it doesn't work. The filesystem encoding must be set as soon as possible and must never change later. As soon as possible: before the first call to os.fsdecode(), which is implemented in C as Py_DecodeLocale(). For example, the encoding must be set before Python imports the first module. The filesystem encoding must be set before Python decodes *any* operating system data: command line arguments, any filename or path, environment variables, etc. Hopefully, Windows provides most operating system data as Unicode directly: command line arguments and environment variables are exposed as Unicode for example. os.fsdecode() and os.fsencode() have an important property: On Windows, the other property is maybe more imporant: If the property becomes false, for example if the filesystem encoding is changed at runtime, you get mojibake. Example:
-- Sorry, I didn't play with sys._enablelegacywindowsfsencoding() on Windows. I don't know if it would "work" if sys._enablelegacywindowsfsencoding() is the first instruction of an application. I expect that Python almost decodes nothing at startup on Windows, so it may work. sys._enablelegacywindowsfsencoding() is a private method, so it shouldn't be used. Maybe we could add a "fuse" (flag only sets to 1, cannot be reset to 0) to raise an exception if sys._enablelegacywindowsfsencoding() is called "too late", after the first call to os.fsdecode() / Py_DecodeLocale()? |
Windows doesn't use the fs encoding at all until Python code requests/provides something in bytes. Except for the caching in fsencode/fsdecode, there's no problem setting it once at the start of your program (and it can only be set once - there's no parameter and it cannot be undone). What I'm most interested in is whether caching the encoding in fsencode/fsdecode is actually an optimization - if not, remove it, and if so make a way to reset it. I'll get around to this sooner or later but I don't want to stop someone else from doing it. |
Personally, I call "sys._enablelegacywindowsfsencoding()" for only one reason : Theses libraries generally call "filename.encode(sys.getfilesystemencoding())" or "os.fsencode(filename)" before sending filenames from Python to C code. Actually, I didn't see any side effect for using this function. Maybe because I call it at start before anything else. Using the environment variable is not easy in my case. I can look if encoding caching in fsencode is efficient (On Windows). And eventually propose a code change for this. |
Hum, Python lacks a function to encode to/decode from the ANSI code page, something like codecs.code_page_encode() / code_page_decode() with CP_ACP. It would allow to get the same encoding in UTF-8 and legacy modes. |
Hum, it was long time ago since I worked on Windows. Well, Python has a "mbcs" codec which uses the ANSI code page which exists like "forever". These libraries should be patched to use "mbcs" instead of sys.getfilesystemencoding(). |
Yes, I reported this encoding issue to some of them. But, there is still some problems :
This problem will not be easy to eliminate totally... |
A little encoding cache benchmark. Current Code: import sys
def _fscodec():
encoding = sys.getfilesystemencoding()
errors = sys.getfilesystemencodeerrors()
def fsencode(filename):
filename = fspath(filename) # Does type-checking of `filename`.
if isinstance(filename, str):
return filename.encode(encoding, errors)
else:
return filename
def fsdecode(filename):
filename = fspath(filename) # Does type-checking of `filename`.
if isinstance(filename, bytes):
return filename.decode(encoding, errors)
else:
return filename
return fsencode, fsdecode fsencode, fsdecode = _fscodec() --------- import os %timeit os.fsdecode(b'\xc3\xa9') %timeit os.fsencode('é') Modified Code: from sys import getfilesystemencoding, getfilesystemencodeerrors
def fsencode(filename):
filename = fspath(filename) # Does type-checking of `filename`.
if isinstance(filename, str):
return filename.encode(getfilesystemencoding(),
getfilesystemencodeerrors())
else:
return filename
def fsdecode(filename):
filename = fspath(filename) # Does type-checking of `filename`.
if isinstance(filename, bytes):
return filename.decode(getfilesystemencoding(),
getfilesystemencodeerrors())
else:
return filename
---------
import os %timeit os.fsdecode(b'\xc3\xa9') %timeit os.fsencode('é') Result: Cache is a 17% speed up optimization. |
Thanks for checking that. I don't think it's worth retaining the cache on Windows in the face of the broken behaviour. Any real-world case where a lot of paths are being encoded or decoded is also likely to involve file-system access which will dwarf the encoding time. Further, passing bytes on Windows will result in another decode/encode cycle anyway, so there will be a bigger performance impact in using str (though even then, probably only when the str is already represented using 16-bit characters). Unless somebody wants to make a case for having a more complex mechanism to reset the cache, I'll make the change to remove it (protected by an 'if sys.platform.startswith('win')' check). |
Can't we just update the cache when the function changes the encoding? |
How? |
I took another look at this and it's still unclear whether it's worth the performance loss. Perhaps moving fsencode and fsdecode (almost) entirely into C would be a better approach? That shouldn't send us backwards at all, and all they really do is a typecheck and then calling a function that's already written in C. |
With 3.6 being over, is _enablelegacywindowsfsencoding still needed or is it time to deprecate it? |
Mercurial still use it. Mercurial has plan to move filesystem name from ANSI Code Page to UTF-8, but I don't know about its progress. |
Mercurial was affected by this bug. https://repo.mercurial-scm.org/hg/rev/00e0c5c06ed5 I don't know much about mercurial internal, but they may use legacy fs encoding like passing bytes path to functions like But they use legacystdio by setting envvar. I think they can use legacyfsencoding by setting envvar or Python/C API too.
@indygreg Would you try PYTHONLEGACYWINDOWSFSENCODING in Mercurial? |
IMO it's time to deprecate it since https://peps.python.org/pep-0686/ got approved for Python 3.15. As you can see, supporting other encodings than UTF-8 remains complicated and is less tested. For me, this legacy encoding was supposed to be a temporary thing to ease the Python on Windows migration to UTF-8. |
I wrote an article about PEP 529: https://vstinner.github.io/python36-utf8-windows.html |
This issue can be fixed by adding a private os._update_fs_encoding() function which would update fsencode(), fsdecode(), and os.environ functions. sys._enablelegacyfsencoding() would call it if os is cached in sys.modules. Something like that. |
We can not fix every possible cached fsencoding, and paths already encoded in utf-8. |
Every attempt to change the filesystem encoding at runtime was a big failure: https://vstinner.github.io/painful-history-python-filesystem-encoding.html I already proposed to deprecate sys._enablelegacywindowsfsencoding() some years ago, but apparently it was too early. |
I filed an issue to Mercurial bugzilla. |
I don't remember your proposal, but it was always meant to be deprecated. Just never actually seemed important enough to do it. Fixing this would require making |
Almost only user of legacywindowsfsencoding is Mercurial. So we need help from Mercurial developer... |
Since there is no reply from Mercurial developers, I merged the deprecation of |
Sounds good to me |
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:
bugs.python.org fields:
Linked PRs
_enablelegacywindowsfsencoding
#107729The text was updated successfully, but these errors were encountered: