Merge branch 'master' into python-2.4

This commit is contained in:
rocky
2018-03-04 21:46:24 -05:00
18 changed files with 211 additions and 373 deletions

View File

@@ -57,6 +57,19 @@ disassembler called `pydisasm`.
### Semantic equivalence vs. exact source code ### Semantic equivalence vs. exact source code
Consider how Python compiles something like "(x*y) + 5". Early on
Python creates an "abstract syntax tree" (AST) for this. And this is
"abstract" in the sense that unimportant, redundant or unnecceary
items have been removed. Here, this means that any notion that you
wrote "x+y" in parenthesis is lost, since in this context they are
unneeded. Also lost is the fact that the multiplication didn't have
spaces around it while the addition did. It should not come as a
surprise then that the bytecode which is derived from the AST also has
no notion of such possible variation. Generally this kind of thing
isn't noticed since the Python community has laid out a very rigid set
of formatting guidelines; and it has largely beaten the community into
compliance.
Almost all versions of Python can perform some sort of code Almost all versions of Python can perform some sort of code
improvement that can't be undone. In earlier versions of Python it is improvement that can't be undone. In earlier versions of Python it is
rare; in later Python versions, it is more common. rare; in later Python versions, it is more common.
@@ -66,7 +79,7 @@ If the code emitted is semantically equivalent, then this isn't a bug.
For example the code might be For example the code might be
``` ```python
if a: if a:
if b: if b:
x = 1 x = 1
@@ -74,7 +87,7 @@ if a:
and we might produce: and we might produce:
``` ```python
if a and b: if a and b:
x = 1 x = 1
``` ```
@@ -87,24 +100,35 @@ else:
``` ```
may come out as `elif`. may come out as `elif` or vice versa.
As mentioned in the README, It is possible that Python changes what As mentioned in the README, It is possible that Python changes what
you write to be more efficient. For example, for: you write to be more efficient. For example, for:
``` ```python
if True: if True:
x = 5 x = 5
``` ```
Python will generate code like: Python will generate code like:
``` ```python
x = 5 x = 5
``` ```
Even more extreme, if your code is:
```python
if False:
x = 1
y = 2
# ...
```
Python will eliminate the entire "if" statement.
So just because the text isn't the same, does not So just because the text isn't the same, does not
necessarily mean there's a bug. necessarily mean there's a bug.

View File

@@ -5,4 +5,4 @@ if [[ $0 == ${BASH_SOURCE[0]} ]] ; then
echo "This script should be *sourced* rather than run directly through bash" echo "This script should be *sourced* rather than run directly through bash"
exit 1 exit 1
fi fi
export PYVERSIONS='3.5.3 3.6.3 2.6.9 3.3.6 2.7.14 3.4.2' export PYVERSIONS='3.5.5 3.6.4 2.6.9 3.3.7 2.7.14 3.4.8'

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,13 @@
# From 2.3 Queue.py
# Bug was adding COME_FROM from while
# confusing the else
def put(item, block=True, timeout=None):
if block:
if timeout:
while True:
if item:
block = 1
else:
block = 5
elif item:
block = False

View File

@@ -4,3 +4,14 @@ def __new__(cls, encode, decode, streamreader=None, streamwriter=None,
incrementalencoder=None, incrementaldecoder=None, name=None, incrementalencoder=None, incrementaldecoder=None, name=None,
*, _is_text_encoding=None): *, _is_text_encoding=None):
return return
# From 3.3 _pyio.py. A closure is created here.
# This changes how the default params are found
class StringIO(object):
def __init__(self, initial_value="", newline="\n"):
super(StringIO, self).__init__()
# No closure created here
class StringIO2(object):
def __init__(self, initial_value="", newline="\n"):
return 5

View File

@@ -1,49 +0,0 @@
# Bug was found in 3.6 _osx_support.py in if/elif needing
# EXTENDED_ARGS which are the targets of jumps.
def get_platform_osx(_config_vars, osname, release, machine, sys, re):
"""Filter values for get_platform()"""
macver = _config_vars.get('MACOSX_DEPLOYMENT_TARGET', '')
macrelease = release or 10
macver = macver or macrelease
if macver:
release = macver
osname = "macosx"
cflags = _config_vars.get('CFLAGS', _config_vars.get('CFLAGS', ''))
if macrelease:
try:
macrelease = tuple(int(i) for i in macrelease.split('.')[0:2])
except ValueError:
macrelease = (10, 0)
else:
macrelease = (10, 0)
if (macrelease >= (10, 4)) and '-arch' in cflags.strip():
machine = 'fat'
archs = re.findall(r'-arch\s+(\S+)', cflags)
archs = tuple(sorted(set(archs)))
if len(archs) == 1:
machine = archs[0]
elif archs == ('i386', 'ppc'):
machine = 'fat'
elif archs == ('i386', 'x86_64'):
machine = 'intel'
elif archs == ('i386', 'ppc', 'x86_64'):
machine = 'fat3'
elif archs == ('ppc64', 'x86_64'):
machine = 'fat64'
elif archs == ('i386', 'ppc', 'ppc64', 'x86_64'):
machine = 'universal'
else:
raise ValueError(
"Don't know machine value for archs=%r" % (archs,))
elif machine == 'i386':
if sys.maxsize >= 2**32:
machine = 'x86_64'
return (osname, release, machine)

View File

@@ -1,5 +1,30 @@
# Python 3.6's changes for calling functions. # Python 3.6's changes for calling functions.
# See https://github.com/rocky/python-uncompyle6/issues/58 # See https://github.com/rocky/python-uncompyle6/issues/58
# CALL_FUNCTION_EX takes 2 to 3 arguments on the stack: the function, the tuple of positional arguments,
# and optionally the dict of keyword arguments if bit 0 of oparg is 1. # CALL_FUNCTION_EX takes 2 to 3 arguments on the stack:
a(*[]) # * the function,
# * the tuple of positional arguments, and optionally
# * the dict of keyword arguments if bit 0 of oparg is 1.
from foo import f, dialect, args, kwds, reader
f(*[])
# From Python 3.6 csv.py
# (f, dialect) are positional arg tuples, *args, is by itself, i.e.
# no tuple.
x = reader(f, dialect, *args, **kwds)
# From 3.6 functools.py
# Below there is a load_closure instruction added
def cmp_to_key(mycmp):
class K(object):
def __ge__():
return mycmp()
return
# In this situation though, there is no load_closure
def cmp2_to_key(mycmp):
class K2(object):
def __ge__():
return 5
return

View File

@@ -6,3 +6,12 @@ def a85decode(b, *, foldspaces=False, adobe=False, ignorechars=b' \t\n\r\v'):
_UNSET = object() _UNSET = object()
def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET): def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
return return
# From 3.6 compileall.py Bug is making default values are in quotes
def compile_command(source, filename="<input>", symbol="single"):
return
# From 3.6 _pyio.py. Bug was in getting order of metaclass=abc.ABCMeta right
import abc
class IOBase(metaclass=abc.ABCMeta):
pass

View File

@@ -115,7 +115,6 @@ class Python26Parser(Python2Parser):
# Semantic actions want else_suitel to be at index 3 # Semantic actions want else_suitel to be at index 3
ifelsestmtl ::= testexpr c_stmts_opt cf_jb_cf_pop else_suitel ifelsestmtl ::= testexpr c_stmts_opt cf_jb_cf_pop else_suitel
ifelsestmtc ::= testexpr c_stmts_opt ja_cf_pop else_suitec ifelsestmtc ::= testexpr c_stmts_opt ja_cf_pop else_suitec
# Semantic actions want suite_stmts_opt to be at index 3 # Semantic actions want suite_stmts_opt to be at index 3

View File

@@ -19,6 +19,7 @@ spark grammar differences over Python 3.5 for Python 3.6.
from uncompyle6.parser import PythonParserSingle, nop_func from uncompyle6.parser import PythonParserSingle, nop_func
from spark_parser import DEFAULT_DEBUG as PARSER_DEFAULT_DEBUG from spark_parser import DEFAULT_DEBUG as PARSER_DEFAULT_DEBUG
from uncompyle6.parsers.parse35 import Python35Parser from uncompyle6.parsers.parse35 import Python35Parser
from uncompyle6.scanners.tok import Token
class Python36Parser(Python35Parser): class Python36Parser(Python35Parser):
@@ -77,6 +78,8 @@ class Python36Parser(Python35Parser):
def customize_grammar_rules(self, tokens, customize): def customize_grammar_rules(self, tokens, customize):
super(Python36Parser, self).customize_grammar_rules(tokens, customize) super(Python36Parser, self).customize_grammar_rules(tokens, customize)
self.check_reduce['call_kw'] = 'AST'
for i, token in enumerate(tokens): for i, token in enumerate(tokens):
opname = token.kind opname = token.kind
@@ -186,6 +189,8 @@ class Python36Parser(Python35Parser):
self.addRule("""expr ::= call_ex_kw self.addRule("""expr ::= call_ex_kw
expr ::= call_ex_kw2 expr ::= call_ex_kw2
expr ::= call_ex_kw3 expr ::= call_ex_kw3
expr ::= call_ex_kw4
call_ex_kw ::= expr expr build_map_unpack_with_call call_ex_kw ::= expr expr build_map_unpack_with_call
CALL_FUNCTION_EX_KW CALL_FUNCTION_EX_KW
call_ex_kw2 ::= expr call_ex_kw2 ::= expr
@@ -193,6 +198,10 @@ class Python36Parser(Python35Parser):
build_map_unpack_with_call build_map_unpack_with_call
CALL_FUNCTION_EX_KW CALL_FUNCTION_EX_KW
call_ex_kw3 ::= expr call_ex_kw3 ::= expr
build_tuple_unpack_with_call
expr
CALL_FUNCTION_EX_KW
call_ex_kw4 ::= expr
expr expr
expr expr
CALL_FUNCTION_EX_KW CALL_FUNCTION_EX_KW
@@ -212,6 +221,21 @@ class Python36Parser(Python35Parser):
seen_GET_AWAITABLE_YIELD_FROM, seen_GET_AWAITABLE_YIELD_FROM,
next_token) next_token)
def reduce_is_invalid(self, rule, ast, tokens, first, last):
invalid = super(Python36Parser,
self).reduce_is_invalid(rule, ast,
tokens, first, last)
if invalid:
return invalid
if rule[0] == 'call_kw':
# Make sure we don't derive call_kw
nt = ast[0]
while not isinstance(nt, Token):
if nt[0] == 'call_kw':
return True
nt = nt[0]
return False
class Python36ParserSingle(Python36Parser, PythonParserSingle): class Python36ParserSingle(Python36Parser, PythonParserSingle):
pass pass

View File

@@ -1131,6 +1131,12 @@ class Scanner2(Scanner):
source = self.setup_loops[label] source = self.setup_loops[label]
else: else:
source = offset source = offset
# FIXME: The grammar for 2.6 and before doesn't
# handle COME_FROM's from a loop inside if's
# It probably should.
if (self.version > 2.6 or
self.code[source] != self.opc.SETUP_LOOP or
self.code[label] != self.opc.JUMP_FORWARD):
targets[label] = targets.get(label, []) + [source] targets[label] = targets.get(label, []) + [source]
pass pass
pass pass

View File

@@ -455,6 +455,33 @@ def customize_for_version(self, is_pypy, version):
self.n_call_ex_kw2 = call_ex_kw2 self.n_call_ex_kw2 = call_ex_kw2
def call_ex_kw3(node): def call_ex_kw3(node):
"""Handle CALL_FUNCTION_EX 1 (have KW) but without
BUILD_MAP_UNPACK_WITH_CALL"""
self.preorder(node[0])
self.write('(')
args = node[1][0]
if args == 'expr':
args = args[0]
if args == 'tuple':
if self.call36_tuple(args) > 0:
self.write(', ')
pass
pass
self.write('*')
self.preorder(node[1][1])
self.write(', ')
kwargs = node[2]
if kwargs == 'expr':
kwargs = kwargs[0]
self.write('**')
self.preorder(kwargs)
self.write(')')
self.prune()
self.n_call_ex_kw3 = call_ex_kw3
def call_ex_kw4(node):
"""Handle CALL_FUNCTION_EX 2 (have KW) but without """Handle CALL_FUNCTION_EX 2 (have KW) but without
BUILD_{MAP,TUPLE}_UNPACK_WITH_CALL""" BUILD_{MAP,TUPLE}_UNPACK_WITH_CALL"""
self.preorder(node[0]) self.preorder(node[0])
@@ -478,8 +505,7 @@ def customize_for_version(self, is_pypy, version):
self.preorder(kwargs) self.preorder(kwargs)
self.write(')') self.write(')')
self.prune() self.prune()
self.n_call_ex_kw3 = call_ex_kw3 self.n_call_ex_kw4 = call_ex_kw4
def call36_tuple(node): def call36_tuple(node):
""" """
@@ -665,7 +691,7 @@ def customize_for_version(self, is_pypy, version):
self.prune() self.prune()
return return
self.n_return_closure = return_closure self.n_return_closure = return_closure
pass # version > 3.6 pass # version >= 3.6
pass # version > 3.4 pass # version >= 3.4
pass # version > 3.0 pass # version >= 3.0
return return

View File

@@ -456,8 +456,11 @@ def make_function3(self, node, is_lambda, nested=1, codeNode=None):
# MAKE_CLOSURE adds an additional closure slot # MAKE_CLOSURE adds an additional closure slot
# Thank you, Python: such a well-thought out system that has # In Python 3.6 stack entries change again. I understand
# changed and continues to change many times. # 3.7 changes some of those changes. Yes, it is hard to follow
# and I am sure I haven't been able to keep up.
# Thank you, Python.
def build_param(ast, name, default): def build_param(ast, name, default):
"""build parameters: """build parameters:
@@ -482,10 +485,23 @@ def make_function3(self, node, is_lambda, nested=1, codeNode=None):
# MAKE_FUNCTION_... or MAKE_CLOSURE_... # MAKE_FUNCTION_... or MAKE_CLOSURE_...
assert node[-1].kind.startswith('MAKE_') assert node[-1].kind.startswith('MAKE_')
# Python 3.3+ adds a qualified name at TOS (-1)
# moving down the LOAD_LAMBDA instruction
if 3.0 <= self.version <= 3.2:
lambda_index = -2
elif 3.03 <= self.version:
lambda_index = -3
else:
lambda_index = None
args_node = node[-1] args_node = node[-1]
if isinstance(args_node.attr, tuple): if isinstance(args_node.attr, tuple):
if self.version <= 3.3 and len(node) > 2 and node[-3] != 'LOAD_LAMBDA': pos_args, kw_args, annotate_argc = args_node.attr
# positional args are after kwargs # FIXME: there is probably a better way to classify this.
if (self.version <= 3.3 and len(node) > 2 and
node[lambda_index] != 'LOAD_LAMBDA' and
(node[0].kind.startswith('kwarg') or node[-4].kind != 'load_closure')):
# args are after kwargs; kwargs are bundled as one node
defparams = node[1:args_node.attr[0]+1] defparams = node[1:args_node.attr[0]+1]
else: else:
# args are before kwargs; kwags as bundled as one node # args are before kwargs; kwags as bundled as one node
@@ -502,7 +518,7 @@ def make_function3(self, node, is_lambda, nested=1, codeNode=None):
expr_node = node[0] expr_node = node[0]
if (expr_node[0] == 'LOAD_CONST' and if (expr_node[0] == 'LOAD_CONST' and
isinstance(expr_node[0].attr, tuple)): isinstance(expr_node[0].attr, tuple)):
defparams = list(expr_node[0].attr) defparams = [repr(a) for a in expr_node[0].attr]
elif expr_node[0] in frozenset(('list', 'tuple', 'dict', 'set')): elif expr_node[0] in frozenset(('list', 'tuple', 'dict', 'set')):
defparams = [self.traverse(n, indent='') for n in expr_node[0][:-1]] defparams = [self.traverse(n, indent='') for n in expr_node[0][:-1]]
else: else:

View File

@@ -536,278 +536,8 @@ class SourceWalker(GenericASTTraversal, object):
pass pass
self.n_unmapexpr = unmapexpr self.n_unmapexpr = unmapexpr
if version >= 3.6: pass # version >= 3.4
######################## pass # version >= 3.0
# Python 3.6+ Additions
#######################
TABLE_DIRECT.update({
'fstring_expr': ( "{%c%{conversion}}", 0),
'fstring_single': ( "f'{%c%{conversion}}'", 0),
'fstring_multi': ( "f'%c'", 0),
'func_args36': ( "%c(**", 0),
'try_except36': ( '%|try:\n%+%c%-%c\n\n', 1, 2 ),
'unpack_list': ( '*%c', (0, 'list') ),
'starred': ( '*%c', (0, 'expr') ),
'call_ex' : (
'%c(%c)',
(0, 'expr'), 1),
'call_ex_kw' : (
'%c(%c)',
(0, 'expr'), 2),
})
TABLE_R.update({
'CALL_FUNCTION_EX': ('%c(*%P)', 0, (1, 2, ', ', 100)),
# Not quite right
'CALL_FUNCTION_EX_KW': ('%c(**%C)', 0, (2, 3, ',')),
})
def build_unpack_tuple_with_call(node):
if node[0] == 'expr':
tup = node[0][0]
else:
tup = node[0]
pass
assert tup == 'tuple'
self.call36_tuple(tup)
buwc = node[-1]
assert buwc.kind.startswith('BUILD_TUPLE_UNPACK_WITH_CALL')
for n in node[1:-1]:
self.f.write(', *')
self.preorder(n)
pass
self.prune()
return
self.n_build_tuple_unpack_with_call = build_unpack_tuple_with_call
def build_unpack_map_with_call(node):
n = node[0]
if n == 'expr':
n = n[0]
if n == 'dict':
self.call36_dict(n)
first = 1
sep = ', **'
else:
first = 0
sep = '**'
for n in node[first:-1]:
self.f.write(sep)
self.preorder(n)
sep = ', **'
pass
self.prune()
return
self.n_build_map_unpack_with_call = build_unpack_map_with_call
def call_ex_kw2(node):
"""Handle CALL_FUNCTION_EX 2 (have KW) but with
BUILD_{MAP,TUPLE}_UNPACK_WITH_CALL"""
# This is weird shit. Thanks Python!
self.preorder(node[0])
self.write('(')
assert node[1] == 'build_tuple_unpack_with_call'
btuwc = node[1]
tup = btuwc[0]
if tup == 'expr':
tup = tup[0]
assert tup == 'tuple'
self.call36_tuple(tup)
assert node[2] == 'build_map_unpack_with_call'
self.write(', ')
d = node[2][0]
if d == 'expr':
d = d[0]
assert d == 'dict'
self.call36_dict(d)
args = btuwc[1]
self.write(', *')
self.preorder(args)
self.write(', **')
star_star_args = node[2][1]
if star_star_args == 'expr':
star_star_args = star_star_args[0]
self.preorder(star_star_args)
self.write(')')
self.prune()
self.n_call_ex_kw2 = call_ex_kw2
def call_ex_kw3(node):
"""Handle CALL_FUNCTION_EX 2 (have KW) but without
BUILD_{MAP,TUPLE}_UNPACK_WITH_CALL"""
self.preorder(node[0])
self.write('(')
args = node[1][0]
if args == 'tuple':
if self.call36_tuple(args) > 0:
self.write(', ')
pass
pass
else:
self.write('*')
self.preorder(args)
self.write(', ')
pass
kwargs = node[2]
if kwargs == 'expr':
kwargs = kwargs[0]
self.write('**')
self.preorder(kwargs)
self.write(')')
self.prune()
self.n_call_ex_kw3 = call_ex_kw3
def call36_tuple(node):
"""
A tuple used in a call, these are like normal tuples but they
don't have the enclosing parenthesis.
"""
assert node == 'tuple'
# Note: don't iterate over last element which is a
# BUILD_TUPLE...
flat_elems = flatten_list(node[:-1])
self.indent_more(INDENT_PER_LEVEL)
sep = ''
for elem in flat_elems:
if elem in ('ROT_THREE', 'EXTENDED_ARG'):
continue
assert elem == 'expr'
line_number = self.line_number
value = self.traverse(elem)
if line_number != self.line_number:
sep += '\n' + self.indent + INDENT_PER_LEVEL[:-1]
self.write(sep, value)
sep = ', '
self.indent_less(INDENT_PER_LEVEL)
return len(flat_elems)
self.call36_tuple = call36_tuple
def call36_dict(node):
"""
A dict used in a call_ex_kw2, which are a dictionary items expressed
in a call. This should format to:
a=1, b=2
In other words, no braces, no quotes around keys and ":" becomes
"=".
We will source-code use line breaks to guide us when to break.
"""
p = self.prec
self.prec = 100
self.indent_more(INDENT_PER_LEVEL)
sep = INDENT_PER_LEVEL[:-1]
line_number = self.line_number
assert node[0].kind.startswith('kvlist')
# Python 3.5+ style key/value list in dict
kv_node = node[0]
l = list(kv_node)
i = 0
# Respect line breaks from source
while i < len(l):
self.write(sep)
name = self.traverse(l[i], indent='')
# Strip off beginning and trailing quotes in name
name = name[1:-1]
if i > 0:
line_number = self.indent_if_source_nl(line_number,
self.indent + INDENT_PER_LEVEL[:-1])
line_number = self.line_number
self.write(name, '=')
value = self.traverse(l[i+1], indent=self.indent+(len(name)+2)*' ')
self.write(value)
sep = ","
if line_number != self.line_number:
sep += "\n" + self.indent + INDENT_PER_LEVEL[:-1]
line_number = self.line_number
i += 2
pass
self.prec = p
self.indent_less(INDENT_PER_LEVEL)
return
self.call36_dict = call36_dict
FSTRING_CONVERSION_MAP = {1: '!s', 2: '!r', 3: '!a'}
def f_conversion(node):
node.conversion = FSTRING_CONVERSION_MAP.get(node.data[1].attr, '')
def fstring_expr(node):
f_conversion(node)
self.default(node)
self.n_fstring_expr = fstring_expr
def fstring_single(node):
f_conversion(node)
self.default(node)
self.n_fstring_single = fstring_single
# def kwargs_only_36(node):
# keys = node[-1].attr
# num_kwargs = len(keys)
# values = node[:num_kwargs]
# for i, (key, value) in enumerate(zip(keys, values)):
# self.write(key + '=')
# self.preorder(value)
# if i < num_kwargs:
# self.write(',')
# self.prune()
# return
# self.n_kwargs_only_36 = kwargs_only_36
def kwargs_36(node):
self.write('(')
keys = node[-1].attr
num_kwargs = len(keys)
num_posargs = len(node) - (num_kwargs + 1)
n = len(node)
assert n >= len(keys)+2
sep = ''
# FIXME: adjust output for line breaks?
for i in range(num_posargs):
self.write(sep)
self.preorder(node[i])
sep = ', '
i = num_posargs
j = 0
# FIXME: adjust output for line breaks?
while i < n-1:
self.write(sep)
self.write(keys[j] + '=')
self.preorder(node[i])
i += 1
j += 1
self.write(')')
self.prune()
return
self.n_kwargs_36 = kwargs_36
def return_closure(node):
# Nothing should be output here
self.prune()
return
self.n_return_closure = return_closure
pass # version > 3.6
pass # version > 3.4
pass # version > 3.0
return return
f = property(lambda s: s.params['f'], f = property(lambda s: s.params['f'],
@@ -1801,17 +1531,21 @@ class SourceWalker(GenericASTTraversal, object):
class_name = node[2][0].pattr class_name = node[2][0].pattr
else: else:
class_name = node[1][2].pattr class_name = node[1][2].pattr
buildclass = node build_class = node
else: else:
build_class = node[0]
if self.version >= 3.6: if self.version >= 3.6:
class_name = node[0][1][0].attr.co_name if build_class[1][0] == 'load_closure':
buildclass = node[0] code_node = build_class[1][1]
else:
code_node = build_class[1][0]
class_name = code_node.attr.co_name
else: else:
class_name = node[1][0].pattr class_name = node[1][0].pattr
buildclass = node[0] build_class = node[0]
assert 'mkfunc' == buildclass[1] assert 'mkfunc' == build_class[1]
mkfunc = buildclass[1] mkfunc = build_class[1]
if mkfunc[0] == 'kwargs': if mkfunc[0] == 'kwargs':
if 3.0 <= self.version <= 3.2: if 3.0 <= self.version <= 3.2:
for n in mkfunc: for n in mkfunc:
@@ -1833,9 +1567,9 @@ class SourceWalker(GenericASTTraversal, object):
subclass_info = node subclass_info = node
else: else:
subclass_info = node[0] subclass_info = node[0]
elif buildclass[1][0] == 'load_closure': elif build_class[1][0] == 'load_closure':
# Python 3 with closures not functions # Python 3 with closures not functions
load_closure = buildclass[1] load_closure = build_class[1]
if hasattr(load_closure[-3], 'attr'): if hasattr(load_closure[-3], 'attr'):
# Python 3.3 classes with closures work like this. # Python 3.3 classes with closures work like this.
# Note have to test before 3.2 case because # Note have to test before 3.2 case because
@@ -1846,34 +1580,34 @@ class SourceWalker(GenericASTTraversal, object):
subclass_code = load_closure[-2].attr subclass_code = load_closure[-2].attr
else: else:
raise 'Internal Error n_classdef: cannot find class body' raise 'Internal Error n_classdef: cannot find class body'
if hasattr(buildclass[3], '__len__'): if hasattr(build_class[3], '__len__'):
subclass_info = buildclass[3] subclass_info = build_class[3]
elif hasattr(buildclass[2], '__len__'): elif hasattr(build_class[2], '__len__'):
subclass_info = buildclass[2] subclass_info = build_class[2]
else: else:
raise 'Internal Error n_classdef: cannot superclass name' raise 'Internal Error n_classdef: cannot superclass name'
elif self.version >= 3.6 and node == 'classdefdeco2': elif self.version >= 3.6 and node == 'classdefdeco2':
subclass_info = node subclass_info = node
subclass_code = buildclass[1][0].attr subclass_code = build_class[1][0].attr
else: else:
subclass_code = buildclass[1][0].attr subclass_code = build_class[1][0].attr
subclass_info = node[0] subclass_info = node[0]
else: else:
if node == 'classdefdeco2': if node == 'classdefdeco2':
buildclass = node build_class = node
else: else:
buildclass = node[0] build_class = node[0]
build_list = buildclass[1][0] build_list = build_class[1][0]
if hasattr(buildclass[-3][0], 'attr'): if hasattr(build_class[-3][0], 'attr'):
subclass_code = buildclass[-3][0].attr subclass_code = build_class[-3][0].attr
class_name = buildclass[0].pattr class_name = build_class[0].pattr
elif (buildclass[-3] == 'mkfunc' and elif (build_class[-3] == 'mkfunc' and
node == 'classdefdeco2' and node == 'classdefdeco2' and
buildclass[-3][0] == 'load_closure'): build_class[-3][0] == 'load_closure'):
subclass_code = buildclass[-3][1].attr subclass_code = build_class[-3][1].attr
class_name = buildclass[-3][0][0].pattr class_name = build_class[-3][0][0].pattr
elif hasattr(node[0][0], 'pattr'): elif hasattr(node[0][0], 'pattr'):
subclass_code = buildclass[-3][1].attr subclass_code = build_class[-3][1].attr
class_name = node[0][0].pattr class_name = node[0][0].pattr
else: else:
raise 'Internal Error n_classdef: cannot find class name' raise 'Internal Error n_classdef: cannot find class name'
@@ -1954,13 +1688,13 @@ class SourceWalker(GenericASTTraversal, object):
else: else:
l = n l = n
while i < l: while i < l:
# 3.6+ may have this
if kwargs:
self.write("%s=" % kwargs[j])
j += 1
value = self.traverse(node[i]) value = self.traverse(node[i])
i += 1 i += 1
self.write(sep, value) self.write(sep, value)
# 3.6+ may have this
if kwargs:
self.write("=%s" % kwargs[j])
j += 1
sep = line_separator sep = line_separator
pass pass
pass pass