-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Initial Python 3 compatibility fixes #23256
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
Conversation
Note: Unlike Python 2, in Python 3 `unittest discover` loads the module, forcing the manual inclusion of a known path to swift_build_support in update_checkout Note: Renamed `ParserError` to `ArgParserError` to avoid confusion with `parser` module in build_swift Note: The syntax for declaring a metaclass changed between Python 2 and 3 in a severely incompatible way; it is not possible to (sanely) use the Python 3 syntax with a Python 2 interpreter. Here we take inspiration from the six module's `with_metaclass()` function to directly specify the metaclass in a way that, while ugly, is fully cross-compatible.
Note: Includes multiple whitespace and minor style changes to satisfy python_lint.py
Note: Retain old array shuffle by using random explicitly (Python 3 changed default algorithm) - test success depends on exact ordering, this seems wrong but fixing it is out of scope here.
Tests for this (beyond verifying the existing config stays working) will be tricky. The test suite doesn't run with Python 3 yet and I'm not aware of any infrastructure for Python 3 CI. |
@swift-ci please smoke test |
Nothing here stands out as an issue. Personally, I am a fan of separate changes when possible so that they can get merged earlier rather than later. |
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.
Seems that we are working in the same things and finding the same problems.
In #23038 I have many of this fixes. I wanted to break them apart per script, so it will be easier to review and accept, but all at the same time is also a good approach.
If you don’t mind, I will leave a couple of notes here and there.
I can review later which pieces I seem to have that these changes are missing, and see if they are interesting.
@@ -37,6 +38,9 @@ import time | |||
|
|||
from compare_perf_tests import LogParser | |||
|
|||
if not hasattr(sys.modules['__builtin__'], 'reduce'): | |||
reduce = functools.reduce | |||
|
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.
Maybe it is easier to use functools.reduce
in both Python 2 and 3.
from functools import reduce
(This happens in a couple more places in other files, I will use the same solution there)
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 learning as I went along, so didn't always pick up the best solutions, especially in some of my earlier changes.
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.
Can you adopt that throughout? It seems cleaner.
is_text = isinstance(msg, bytes) | ||
|
||
try: | ||
if is_text and getattr(stream, 'encoding', None): |
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.
bytes
in Python 3 do not have encoding
. Maybe you wanted to compare with isinstance(msg, str)
?
Python 3.6.3rc1+ (default, Feb 5 2019, 15:51:57)
[GCC 5.x 20180625 5.5.0+] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> b''.encode()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'bytes' object has no attribute 'encode'
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.
If I remember correctly it was specifically a check to find out whether the attribute was present or not - keep in mind I was maintaining compatibility with both 2 and 3, not just updating to 3.
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.
Sorry, I mistyped. Corrected: bytes
in Python 3 do not have encode(…)
. str
does.
from StringIO import StringIO | ||
try: | ||
from StringIO import StringIO # py2 | ||
except ModuleNotFoundError: |
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.
Minor: ModuleNotFoundError
was introduced in Python 3.6. Python 2 should find StringIO
just fine, but maybe you can change this with ImportError
(the supertype), which should work in any Python 2 and 3.
(This also happens in more places below)
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.
I was getting no such module errors from Python 3 because StringIO
is io.StringIO
in 3.
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.
What I wanted to say is that ModuleNotFoundError
was introduced in Python 3.6. ModuleNotFoundError
is a subtype of ImportError
. It will be more portable to use ImportError
, so anything from Python 3.0 to Python 3.6 can import io.StringIO
.
@@ -65,7 +71,7 @@ public struct SIMD${n}<Scalar> : SIMD where Scalar: SIMDScalar { | |||
self.init(${', '.join('xyzw'[:n])}) | |||
} | |||
|
|||
% for i in range(n): | |||
% for i in list(range(n)): |
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.
In the three cases above, list(range(n))
can be just range(n)
. range
returns a list in Python 2, and an iterator in Python 3. Both can be used directly by for … in …
.
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.
Right, these shouldn't need any changes.
try: | ||
xrange | ||
except NameError: | ||
xrange = range |
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.
In my version I simply change the xrange
to range
. In Py3, range
works as Py2's xrange
, and for the usages here, range
in Py2 is not generating very long list, and the list is completely iterated. It makes the code easier to read, IMO. The only change was in L156.
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.
I tend to agree; there was some reason I went with this approach that I've forgotten now
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, please just use range
for these cases; I think there's only like one instance of xrange
in this file anyway, it should just be converted to keep the diffs minimal.
quote(local_file)) | ||
for local_file, remote_file | ||
in local_to_remote_files.viewitems()) | ||
self.run_sftp(sftp_commands) | ||
|
||
def run_remote(self, command, remote_env={}): | ||
env_strings = ['{0}={1}'.format(k,v) for k,v in remote_env.viewitems()] | ||
env_strings = ['{0}={1}'.format(k,v) for k,v in remote_env.items()] |
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.
Besides this viewitems
, there's a viewvalues
in L44, another viewitems
in L51 and L67, a viewkeys
in L56. Didn’t you have problems with those in Python 3?
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.
I don't know that I ever finished getting this particular script working 😅
@@ -69,7 +69,7 @@ def write_input_file(args, ast, d, n): | |||
|
|||
def ensure_tmpdir(d): | |||
if d is not None and not os.path.exists(d): | |||
os.makedirs(d, 0700) | |||
os.makedirs(d, 0o700) |
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.
I found another one in L84 as well.
@@ -73,7 +73,7 @@ def test_capture(self): | |||
|
|||
self.assertEqual( | |||
shell.capture(["sh", "-c", "echo foo && false"], | |||
allow_non_zero_exit=True), "foo\n") | |||
allow_non_zero_exit=True).decode('ascii'), "foo\n") |
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.
I solved this with a b"foo\n"
instead.
@@ -1,4 +1,3 @@ | |||
|
|||
from update_checkout import main | |||
from update_checkout.update_checkout import main |
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.
If you don't want the absolute import, it can also be done with from .update_checkout import main
.
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.
This is another case of "the only solution I found that worked in both 2 and 3"
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.
from .update_checkout import main
__all__ = ["main"]
Just to let you know that worked when Im trying to compile the tensorflow branch... dont know if compatible with python2, changint the 2 __init__.py
files for from update_checkout.update_checkout import main
seem to not work on my end??
SCRIPT_FILE = os.path.abspath(__file__) | ||
SCRIPT_DIR = os.path.dirname(SCRIPT_FILE) | ||
sys.path.append(os.path.dirname(os.path.dirname(SCRIPT_DIR))) | ||
|
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.
Is this for fixing the test update_checkout.test-sh
? I changed the test instead to provide a working directory with the -t
parameter.
# RUN: %{python} -m unittest discover -s %swift_src_root/utils/update_checkout -t %swift_src_root/utils/
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.
This was to deal with importing swift_build_support, which lives one directory up from update-checkout and wasn't found by the default paths in 3.
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.
Yes, I understand why you add it. In my tests with Python 3, when running the script by itself, I didn’t need any changes here. But when running from the tests, I found out that the modules weren’t in the search path. I used the solution I pointed above, since it doesn’t modify the code for all cases.
num_iters=1, quantile=20) | ||
for _ in range(self.args.independent_samples)]) | ||
for _ in range(self.args.independent_samples)] | ||
return reduce(lambda m, res: (m.merge(res), m)[1], result_sets) |
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.
The original version of this seems less magical and easier for non-python folks to grok.
@@ -414,6 +418,7 @@ class BenchmarkDoctor(object): | |||
name = measurements['name'] | |||
setup, ratio = BenchmarkDoctor._setup_overhead(measurements) | |||
setup = 0 if ratio < 0.05 else setup | |||
|
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.
Please remove whitespace-only changes.
@@ -111,7 +111,7 @@ public func >=(lhs: (), rhs: ()) -> Bool { | |||
% equatableTypeParams = ", ".join(["{} : Equatable".format(c) for c in typeParams]) | |||
|
|||
% originalTuple = "(\"a\", {})".format(", ".join(map(str, range(1, arity)))) | |||
% greaterTuple = "(\"a\", {})".format(", ".join(map(str, range(1, arity - 1) + [arity]))) | |||
% greaterTuple = "(\"a\", {})".format(", ".join(map(str, list(range(1, arity - 1)) + [arity]))) |
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.
Don't need this change, either.
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.
Python 3 definitely went bananas without this change.
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.
I think this change is needed. In Python 3, range
will return an iterable, which cannot be added a list. The list(…)
transforms the iterable into a list, which will work.
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.
Yes, because if you enter arity=3; "(\"a\", {})".format(", ".join(map(str, range(1, arity - 1) + [arity])))
to an interpreter it will cause TypeError: unsupported operand type(s) for +: 'range' and 'list'
also https://stackoverflow.com/a/47472763 which leads to xrange() was renamed to range() in Python 3 and range(...) replaced with list(range(...))
This PR adds compatibility with Python versions 2 and 3 for the following parts of Swift:
.gyb
files in the stdlibgyb
gyb_sourcekit_support
gyb_syntax_support
line-directive
update_checkout
build_swift
swift_build_support
bug_reducer
Note: All of
llvm
andclang
, as well asbuild-script
, are already fully compatible with Python 3.Note: An upstream issue with
lit.py
's Windows support is blocking Python 3 compatibility for the test suites at this time.This PR does NOT provide complete Python 3 support; considerable further work is needed. Thoughts on whether this PR should be broken into multiple pieces and if so in what way would be most appreciated.