From 52cc1d838f4fee573e57b5b182d8e5f5db63240f Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Sun, 18 Mar 2007 15:41:51 +0000 Subject: Implement PEP 3115 -- new metaclass syntax and semantics. The compiler package hasn't been updated yet; test_compiler.py fails. Otherwise all tests seem to be passing now. There are no occurrences of __metaclass__ left in the standard library. Docs have not been updated. --- Grammar/Grammar | 2 +- Include/Python-ast.h | 8 +- Include/opcode.h | 10 +- Lib/build_class.py | 0 Lib/ctypes/_endian.py | 6 +- Lib/opcode.py | 5 +- Lib/string.py | 3 +- Lib/test/crashers/modify_dict_attr.py | 3 +- Lib/test/leakers/test_selftype.py | 4 +- Lib/test/pickletester.py | 4 +- Lib/test/test_ast.py | 8 +- Lib/test/test_copy.py | 4 +- Lib/test/test_descr.py | 81 +++++++------ Lib/test/test_metaclass.py | 218 ++++++++++++++++++++++++++++++++++ Lib/unittest.py | 3 - Modules/_bsddb.c | 4 + Parser/Python.asdl | 7 +- Python/Python-ast.c | 28 ++++- Python/ast.c | 59 ++++----- Python/bltinmodule.c | 109 +++++++++++++++++ Python/ceval.c | 89 +++----------- Python/compile.c | 174 +++++++++++++++++++-------- Python/graminit.c | 2 +- Python/import.c | 3 +- Python/symtable.c | 5 + 25 files changed, 603 insertions(+), 236 deletions(-) create mode 100644 Lib/build_class.py create mode 100644 Lib/test/test_metaclass.py diff --git a/Grammar/Grammar b/Grammar/Grammar index 0277799..0e459ca 100644 --- a/Grammar/Grammar +++ b/Grammar/Grammar @@ -119,7 +119,7 @@ exprlist: expr (',' expr)* [','] testlist: test (',' test)* [','] dictsetmaker: (test ':' test (',' test ':' test)* [',']) | (test (',' test)* [',']) -classdef: 'class' NAME ['(' [testlist] ')'] ':' suite +classdef: 'class' NAME ['(' [arglist] ')'] ':' suite arglist: (argument ',')* (argument [',']| '*' test [',' '**' test] | '**' test) argument: test [gen_for] | test '=' test # Really [keyword '='] test diff --git a/Include/Python-ast.h b/Include/Python-ast.h index 233a576..e07f025 100644 --- a/Include/Python-ast.h +++ b/Include/Python-ast.h @@ -82,6 +82,9 @@ struct _stmt { struct { identifier name; asdl_seq *bases; + asdl_seq *keywords; + expr_ty starargs; + expr_ty kwargs; asdl_seq *body; } ClassDef; @@ -380,8 +383,9 @@ mod_ty _Py_Suite(asdl_seq * body, PyArena *arena); stmt_ty _Py_FunctionDef(identifier name, arguments_ty args, asdl_seq * body, asdl_seq * decorators, expr_ty returns, int lineno, int col_offset, PyArena *arena); -#define ClassDef(a0, a1, a2, a3, a4, a5) _Py_ClassDef(a0, a1, a2, a3, a4, a5) -stmt_ty _Py_ClassDef(identifier name, asdl_seq * bases, asdl_seq * body, int +#define ClassDef(a0, a1, a2, a3, a4, a5, a6, a7, a8) _Py_ClassDef(a0, a1, a2, a3, a4, a5, a6, a7, a8) +stmt_ty _Py_ClassDef(identifier name, asdl_seq * bases, asdl_seq * keywords, + expr_ty starargs, expr_ty kwargs, asdl_seq * body, int lineno, int col_offset, PyArena *arena); #define Return(a0, a1, a2, a3) _Py_Return(a0, a1, a2, a3) stmt_ty _Py_Return(expr_ty value, int lineno, int col_offset, PyArena *arena); diff --git a/Include/opcode.h b/Include/opcode.h index 316ba4f..662fbb4 100644 --- a/Include/opcode.h +++ b/Include/opcode.h @@ -59,8 +59,9 @@ extern "C" { #define BINARY_OR 66 #define INPLACE_POWER 67 #define GET_ITER 68 - +#define STORE_LOCALS 69 #define PRINT_EXPR 70 +#define LOAD_BUILD_CLASS 71 #define INPLACE_LSHIFT 75 #define INPLACE_RSHIFT 76 @@ -69,14 +70,13 @@ extern "C" { #define INPLACE_OR 79 #define BREAK_LOOP 80 #define WITH_CLEANUP 81 -#define LOAD_LOCALS 82 + #define RETURN_VALUE 83 #define IMPORT_STAR 84 #define MAKE_BYTES 85 #define YIELD_VALUE 86 #define POP_BLOCK 87 #define END_FINALLY 88 -#define BUILD_CLASS 89 #define HAVE_ARGUMENT 90 /* Opcodes from here have an argument: */ @@ -120,10 +120,10 @@ extern "C" { #define RAISE_VARARGS 130 /* Number of raise arguments (1, 2 or 3) */ /* CALL_FUNCTION_XXX opcodes defined below depend on this definition */ #define CALL_FUNCTION 131 /* #args + (#kwargs<<8) */ -#define MAKE_FUNCTION 132 /* #defaults */ +#define MAKE_FUNCTION 132 /* #defaults + #kwdefaults<<8 + #annotations<<16 */ #define BUILD_SLICE 133 /* Number of items */ -#define MAKE_CLOSURE 134 /* #free vars */ +#define MAKE_CLOSURE 134 /* same as MAKE_FUNCTION */ #define LOAD_CLOSURE 135 /* Load free variable from closure */ #define LOAD_DEREF 136 /* Load and dereference from closure cell */ #define STORE_DEREF 137 /* Store into cell */ diff --git a/Lib/build_class.py b/Lib/build_class.py new file mode 100644 index 0000000..e69de29 diff --git a/Lib/ctypes/_endian.py b/Lib/ctypes/_endian.py index 6de0d47..61ac334 100644 --- a/Lib/ctypes/_endian.py +++ b/Lib/ctypes/_endian.py @@ -42,18 +42,16 @@ if sys.byteorder == "little": LittleEndianStructure = Structure - class BigEndianStructure(Structure): + class BigEndianStructure(Structure, metaclass=_swapped_meta): """Structure with big endian byte order""" - __metaclass__ = _swapped_meta _swappedbytes_ = None elif sys.byteorder == "big": _OTHER_ENDIAN = "__ctype_le__" BigEndianStructure = Structure - class LittleEndianStructure(Structure): + class LittleEndianStructure(Structure, metaclass=_swapped_meta): """Structure with little endian byte order""" - __metaclass__ = _swapped_meta _swappedbytes_ = None else: diff --git a/Lib/opcode.py b/Lib/opcode.py index 69982f2..61b288a 100644 --- a/Lib/opcode.py +++ b/Lib/opcode.py @@ -98,8 +98,10 @@ def_op('BINARY_XOR', 65) def_op('BINARY_OR', 66) def_op('INPLACE_POWER', 67) def_op('GET_ITER', 68) +def_op('STORE_LOCALS', 69) def_op('PRINT_EXPR', 70) +def_op('LOAD_BUILD_CLASS', 71) def_op('INPLACE_LSHIFT', 75) def_op('INPLACE_RSHIFT', 76) @@ -108,14 +110,13 @@ def_op('INPLACE_XOR', 78) def_op('INPLACE_OR', 79) def_op('BREAK_LOOP', 80) def_op('WITH_CLEANUP', 81) -def_op('LOAD_LOCALS', 82) + def_op('RETURN_VALUE', 83) def_op('IMPORT_STAR', 84) def_op('MAKE_BYTES', 85) def_op('YIELD_VALUE', 86) def_op('POP_BLOCK', 87) def_op('END_FINALLY', 88) -def_op('BUILD_CLASS', 89) HAVE_ARGUMENT = 90 # Opcodes from here have an argument: diff --git a/Lib/string.py b/Lib/string.py index 8281936..6aafb65 100644 --- a/Lib/string.py +++ b/Lib/string.py @@ -119,9 +119,8 @@ class _TemplateMetaclass(type): cls.pattern = _re.compile(pattern, _re.IGNORECASE | _re.VERBOSE) -class Template: +class Template(metaclass=_TemplateMetaclass): """A string class for supporting $-substitutions.""" - __metaclass__ = _TemplateMetaclass delimiter = '$' idpattern = r'[_a-z][_a-z0-9]*' diff --git a/Lib/test/crashers/modify_dict_attr.py b/Lib/test/crashers/modify_dict_attr.py index 2a8fce0..ac1f0a8 100644 --- a/Lib/test/crashers/modify_dict_attr.py +++ b/Lib/test/crashers/modify_dict_attr.py @@ -7,11 +7,10 @@ class Y(object): class type_with_modifiable_dict(Y, type): pass -class MyClass(object): +class MyClass(object, metaclass=type_with_modifiable_dict): """This class has its __dict__ attribute completely exposed: user code can read, reassign and even delete it. """ - __metaclass__ = type_with_modifiable_dict if __name__ == '__main__': diff --git a/Lib/test/leakers/test_selftype.py b/Lib/test/leakers/test_selftype.py index 4207c32..12f2934 100644 --- a/Lib/test/leakers/test_selftype.py +++ b/Lib/test/leakers/test_selftype.py @@ -6,8 +6,8 @@ import gc def leak(): class T(type): pass - class U(type): - __metaclass__ = T + class U(type, metaclass=T): + pass U.__class__ = U del U gc.collect(); gc.collect(); gc.collect() diff --git a/Lib/test/pickletester.py b/Lib/test/pickletester.py index cb4b431..b10c57d 100644 --- a/Lib/test/pickletester.py +++ b/Lib/test/pickletester.py @@ -88,8 +88,8 @@ class initarg(C): class metaclass(type): pass -class use_metaclass(object): - __metaclass__ = metaclass +class use_metaclass(object, metaclass=metaclass): + pass # DATA0 .. DATA2 are the pickles we expect under the various protocols, for # the object returned by create_data(). diff --git a/Lib/test/test_ast.py b/Lib/test/test_ast.py index 5d22053..c702ab1 100644 --- a/Lib/test/test_ast.py +++ b/Lib/test/test_ast.py @@ -144,13 +144,19 @@ def run_tests(): (eval_tests, eval_results, "eval")): for i, o in itertools.izip(input, output): ast_tree = compile(i, "?", kind, 0x400) + if to_tuple(ast_tree) != o: + print("i=", i) + print("o=", o) + print("kind=", kind) + print("tree=", ast_tree) + print("tuple=", to_tuple(ast_tree)) assert to_tuple(ast_tree) == o test_order(ast_tree, (0, 0)) #### EVERYTHING BELOW IS GENERATED ##### exec_results = [ ('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [], None, None, [], None, None, [], []), [('Pass', (1, 9))], [], None)]), -('Module', [('ClassDef', (1, 0), 'C', [], [('Pass', (1, 8))])]), +('Module', [('ClassDef', (1, 0), 'C', [], [], None, None, [('Pass', (1, 8))])]), ('Module', [('FunctionDef', (1, 0), 'f', ('arguments', [], None, None, [], None, None, [], []), [('Return', (1, 8), ('Num', (1, 15), 1))], [], None)]), ('Module', [('Delete', (1, 0), [('Name', (1, 4), 'v', ('Del',))])]), ('Module', [('Assign', (1, 0), [('Name', (1, 0), 'v', ('Store',))], ('Num', (1, 4), 1))]), diff --git a/Lib/test/test_copy.py b/Lib/test/test_copy.py index fd6109c..dbca158 100644 --- a/Lib/test/test_copy.py +++ b/Lib/test/test_copy.py @@ -191,8 +191,8 @@ class TestCopy(unittest.TestCase): # type. class Meta(type): pass - class C: - __metaclass__ = Meta + class C(metaclass=Meta): + pass self.assertEqual(copy.deepcopy(C), C) def test_deepcopy_deepcopy(self): diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index e8db29e..6cd8ccd 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -616,9 +616,8 @@ def pylists(): vereq(a[100:200], (100,200)) def metaclass(): - if verbose: print("Testing __metaclass__...") - class C: - __metaclass__ = type + if verbose: print("Testing metaclass...") + class C(metaclass=type): def __init__(self): self.__state = 0 def getstate(self): @@ -629,9 +628,10 @@ def metaclass(): vereq(a.getstate(), 0) a.setstate(10) vereq(a.getstate(), 10) - class D: - class __metaclass__(type): - def myself(cls): return cls + class _metaclass(type): + def myself(cls): return cls + class D(metaclass=_metaclass): + pass vereq(D.myself(), D) d = D() verify(d.__class__ is D) @@ -639,8 +639,8 @@ def metaclass(): def __new__(cls, name, bases, dict): dict['__spam__'] = 1 return type.__new__(cls, name, bases, dict) - class C: - __metaclass__ = M1 + class C(metaclass=M1): + pass vereq(C.__spam__, 1) c = C() vereq(c.__spam__, 1) @@ -663,8 +663,7 @@ def metaclass(): continue setattr(it, key, self.dict[key].__get__(it, self)) return it - class C: - __metaclass__ = M2 + class C(metaclass=M2): def spam(self): return 42 vereq(C.name, 'C') @@ -690,8 +689,7 @@ def metaclass(): name = "__super" setattr(cls, name, super(cls)) return cls - class A: - __metaclass__ = autosuper + class A(metaclass=autosuper): def meth(self): return "A" class B(A): @@ -729,8 +727,7 @@ def metaclass(): dict[key] = property(get, set) return super(autoproperty, metaclass).__new__(metaclass, name, bases, dict) - class A: - __metaclass__ = autoproperty + class A(metaclass=autoproperty): def _get_x(self): return -self.__x def _set_x(self, x): @@ -744,8 +741,7 @@ def metaclass(): class multimetaclass(autoproperty, autosuper): # Merge of multiple cooperating metaclasses pass - class A: - __metaclass__ = multimetaclass + class A(metaclass=multimetaclass): def _get_x(self): return "A" class B(A): @@ -764,8 +760,8 @@ def metaclass(): counter = 0 def __init__(self, *args): T.counter += 1 - class C: - __metaclass__ = T + class C(metaclass=T): + pass vereq(T.counter, 1) a = C() vereq(type(a), C) @@ -1273,8 +1269,8 @@ def dynamics(): # Test comparison of classes with dynamic metaclasses class dynamicmetaclass(type): pass - class someclass: - __metaclass__ = dynamicmetaclass + class someclass(metaclass=dynamicmetaclass): + pass verify(someclass != object) def errors(): @@ -1505,36 +1501,39 @@ def altmro(): L = type.mro(cls) L.reverse() return L - class X(D,B,C,A): - __metaclass__ = PerverseMetaType + class X(D,B,C,A, metaclass=PerverseMetaType): + pass vereq(X.__mro__, (object, A, C, B, D, X)) vereq(X().f(), "A") try: - class X(object): - class __metaclass__(type): - def mro(self): - return [self, dict, object] + class _metaclass(type): + def mro(self): + return [self, dict, object] + class X(object, metaclass=_metaclass): + pass except TypeError: pass else: raise TestFailed, "devious mro() return not caught" try: - class X(object): - class __metaclass__(type): - def mro(self): - return [1] + class _metaclass(type): + def mro(self): + return [1] + class X(object, metaclass=_metaclass): + pass except TypeError: pass else: raise TestFailed, "non-class mro() return not caught" try: - class X(object): - class __metaclass__(type): - def mro(self): - return 1 + class _metaclass(type): + def mro(self): + return 1 + class X(object, metaclass=_metaclass): + pass except TypeError: pass else: @@ -3575,11 +3574,11 @@ def test_mutable_bases_with_failing_mro(): class E(D): pass - class F(D): - __metaclass__ = WorkOnce + class F(D, metaclass=WorkOnce): + pass - class G(D): - __metaclass__ = WorkAlways + class G(D, metaclass=WorkAlways): + pass # Immediate subclasses have their mro's adjusted in alphabetical # order, so E's will get adjusted before adjusting F's fails. We @@ -3690,15 +3689,15 @@ def subclass_right_op(): def dict_type_with_metaclass(): if verbose: - print("Testing type of __dict__ when __metaclass__ set...") + print("Testing type of __dict__ when metaclass set...") class B(object): pass class M(type): pass - class C: + class C(metaclass=M): # In 2.3a1, C.__dict__ was a real dict rather than a dict proxy - __metaclass__ = M + pass veris(type(C.__dict__), type(B.__dict__)) def meth_class_get(): diff --git a/Lib/test/test_metaclass.py b/Lib/test/test_metaclass.py new file mode 100644 index 0000000..df81079 --- /dev/null +++ b/Lib/test/test_metaclass.py @@ -0,0 +1,218 @@ +doctests = """ + +Basic class construction. + + >>> class C: + ... def meth(self): print("Hello") + ... + >>> C.__class__ is type + True + >>> a = C() + >>> a.__class__ is C + True + >>> a.meth() + Hello + >>> + +Use *args notation for the bases. + + >>> class A: pass + >>> class B: pass + >>> bases = (A, B) + >>> class C(*bases): pass + >>> C.__bases__ == bases + True + >>> + +Use a trivial metaclass. + + >>> class M(type): + ... pass + ... + >>> class C(metaclass=M): + ... def meth(self): print("Hello") + ... + >>> C.__class__ is M + True + >>> a = C() + >>> a.__class__ is C + True + >>> a.meth() + Hello + >>> + +Use **kwds notation for the metaclass keyword. + + >>> kwds = {'metaclass': M} + >>> class C(**kwds): pass + ... + >>> C.__class__ is M + True + >>> a = C() + >>> a.__class__ is C + True + >>> + +Use a metaclass with a __prepare__ static method. + + >>> class M(type): + ... @staticmethod + ... def __prepare__(*args, **kwds): + ... print("Prepare called:", args, kwds) + ... return dict() + ... def __new__(cls, name, bases, namespace, **kwds): + ... print("New called:", kwds) + ... return type.__new__(cls, name, bases, namespace) + ... + >>> class C(metaclass=M): + ... def meth(self): print("Hello") + ... + Prepare called: ('C', ()) {} + New called: {} + >>> + +Also pass another keyword. + + >>> class C(object, metaclass=M, other="haha"): + ... pass + ... + Prepare called: ('C', (,)) {'other': 'haha'} + New called: {'other': 'haha'} + >>> C.__class__ is M + True + >>> C.__bases__ == (object,) + True + >>> a = C() + >>> a.__class__ is C + True + >>> + +Check that build_class doesn't mutate the kwds dict. + + >>> kwds = {'metaclass': type} + >>> class C(**kwds): pass + ... + >>> kwds == {'metaclass': type} + True + >>> + +Use various combinations of explicit keywords and **kwds. + + >>> bases = (object,) + >>> kwds = {'metaclass': M, 'other': 'haha'} + >>> class C(*bases, **kwds): pass + ... + Prepare called: ('C', (,)) {'other': 'haha'} + New called: {'other': 'haha'} + >>> C.__class__ is M + True + >>> C.__bases__ == (object,) + True + >>> class B: pass + >>> kwds = {'other': 'haha'} + >>> class C(B, metaclass=M, *bases, **kwds): pass + ... + Prepare called: ('C', (, )) {'other': 'haha'} + New called: {'other': 'haha'} + >>> C.__class__ is M + True + >>> C.__bases__ == (B, object) + True + >>> + +Check for duplicate keywords. + + >>> class C(metaclass=type, metaclass=type): pass + ... + Traceback (most recent call last): + [...] + TypeError: __build_class__() got multiple values for keyword argument 'metaclass' + >>> + +Another way. + + >>> kwds = {'metaclass': type} + >>> class C(metaclass=type, **kwds): pass + ... + Traceback (most recent call last): + [...] + TypeError: __build_class__() got multiple values for keyword argument 'metaclass' + >>> + +Use a __prepare__ method that returns an instrumented dict. + + >>> class LoggingDict(dict): + ... def __setitem__(self, key, value): + ... print("d[%r] = %r" % (key, value)) + ... dict.__setitem__(self, key, value) + ... + >>> class Meta(type): + ... @staticmethod + ... def __prepare__(name, bases): + ... return LoggingDict() + ... + >>> class C(metaclass=Meta): + ... foo = 2+2 + ... foo = 42 + ... bar = 123 + ... + d['__module__'] = 'test.test_metaclass' + d['foo'] = 4 + d['foo'] = 42 + d['bar'] = 123 + >>> + +Use a metaclass that doesn't derive from type. + + >>> def meta(name, bases, namespace, **kwds): + ... print("meta:", name, bases) + ... print("ns:", sorted(namespace.items())) + ... print("kw:", sorted(kwds.items())) + ... return namespace + ... + >>> class C(metaclass=meta): + ... a = 42 + ... b = 24 + ... + meta: C () + ns: [('__module__', 'test.test_metaclass'), ('a', 42), ('b', 24)] + kw: [] + >>> type(C) is dict + True + >>> print(sorted(C.items())) + [('__module__', 'test.test_metaclass'), ('a', 42), ('b', 24)] + >>> + +And again, with a __prepare__ attribute. + + >>> def prepare(name, bases, **kwds): + ... print("prepare:", name, bases, sorted(kwds.items())) + ... return LoggingDict() + ... + >>> meta.__prepare__ = prepare + >>> class C(metaclass=meta, other="booh"): + ... a = 1 + ... a = 2 + ... b = 3 + ... + prepare: C () [('other', 'booh')] + d['__module__'] = 'test.test_metaclass' + d['a'] = 1 + d['a'] = 2 + d['b'] = 3 + meta: C () + ns: [('__module__', 'test.test_metaclass'), ('a', 2), ('b', 3)] + kw: [('other', 'booh')] + >>> + +""" + +__test__ = {'doctests' : doctests} + +def test_main(verbose=False): + from test import test_support + from test import test_metaclass + test_support.run_doctest(test_metaclass, verbose) + +if __name__ == "__main__": + test_main(verbose=True) diff --git a/Lib/unittest.py b/Lib/unittest.py index cde310f..eab0372 100644 --- a/Lib/unittest.py +++ b/Lib/unittest.py @@ -84,9 +84,6 @@ if sys.version_info[:2] < (2, 2): # Test framework core ############################################################################## -# All classes defined herein are 'new-style' classes, allowing use of 'super()' -__metaclass__ = type - def _strclass(cls): return "%s.%s" % (cls.__module__, cls.__name__) diff --git a/Modules/_bsddb.c b/Modules/_bsddb.c index c3d3415..b3e33c5 100644 --- a/Modules/_bsddb.c +++ b/Modules/_bsddb.c @@ -5991,6 +5991,10 @@ PyMODINIT_FUNC init_bsddb(void) * from both DBError and KeyError, since the API only supports * using one base class. */ PyDict_SetItemString(d, "KeyError", PyExc_KeyError); + { + PyObject *builtin_mod = PyImport_ImportModule("__builtin__"); + PyDict_SetItemString(d, "__builtins__", builtin_mod); + } PyRun_String("class DBNotFoundError(DBError, KeyError): pass\n" "class DBKeyEmptyError(DBError, KeyError): pass", Py_file_input, d, d); diff --git a/Parser/Python.asdl b/Parser/Python.asdl index 3dc3c60..7c2c9077 100644 --- a/Parser/Python.asdl +++ b/Parser/Python.asdl @@ -11,7 +11,12 @@ module Python version "$Revision$" stmt = FunctionDef(identifier name, arguments args, stmt* body, expr* decorators, expr? returns) - | ClassDef(identifier name, expr* bases, stmt* body) + | ClassDef(identifier name, + expr* bases, + keyword* keywords, + expr? starargs, + expr? kwargs, + stmt* body) | Return(expr? value) | Delete(expr* targets) diff --git a/Python/Python-ast.c b/Python/Python-ast.c index c024a62..76c86c1 100644 --- a/Python/Python-ast.c +++ b/Python/Python-ast.c @@ -49,6 +49,9 @@ static PyTypeObject *ClassDef_type; static char *ClassDef_fields[]={ "name", "bases", + "keywords", + "starargs", + "kwargs", "body", }; static PyTypeObject *Return_type; @@ -477,7 +480,7 @@ static int init_types(void) FunctionDef_type = make_type("FunctionDef", stmt_type, FunctionDef_fields, 5); if (!FunctionDef_type) return 0; - ClassDef_type = make_type("ClassDef", stmt_type, ClassDef_fields, 3); + ClassDef_type = make_type("ClassDef", stmt_type, ClassDef_fields, 6); if (!ClassDef_type) return 0; Return_type = make_type("Return", stmt_type, Return_fields, 1); if (!Return_type) return 0; @@ -835,8 +838,9 @@ FunctionDef(identifier name, arguments_ty args, asdl_seq * body, asdl_seq * } stmt_ty -ClassDef(identifier name, asdl_seq * bases, asdl_seq * body, int lineno, int - col_offset, PyArena *arena) +ClassDef(identifier name, asdl_seq * bases, asdl_seq * keywords, expr_ty + starargs, expr_ty kwargs, asdl_seq * body, int lineno, int col_offset, + PyArena *arena) { stmt_ty p; if (!name) { @@ -850,6 +854,9 @@ ClassDef(identifier name, asdl_seq * bases, asdl_seq * body, int lineno, int p->kind = ClassDef_kind; p->v.ClassDef.name = name; p->v.ClassDef.bases = bases; + p->v.ClassDef.keywords = keywords; + p->v.ClassDef.starargs = starargs; + p->v.ClassDef.kwargs = kwargs; p->v.ClassDef.body = body; p->lineno = lineno; p->col_offset = col_offset; @@ -1974,6 +1981,21 @@ ast2obj_stmt(void* _o) if (PyObject_SetAttrString(result, "bases", value) == -1) goto failed; Py_DECREF(value); + value = ast2obj_list(o->v.ClassDef.keywords, ast2obj_keyword); + if (!value) goto failed; + if (PyObject_SetAttrString(result, "keywords", value) == -1) + goto failed; + Py_DECREF(value); + value = ast2obj_expr(o->v.ClassDef.starargs); + if (!value) goto failed; + if (PyObject_SetAttrString(result, "starargs", value) == -1) + goto failed; + Py_DECREF(value); + value = ast2obj_expr(o->v.ClassDef.kwargs); + if (!value) goto failed; + if (PyObject_SetAttrString(result, "kwargs", value) == -1) + goto failed; + Py_DECREF(value); value = ast2obj_list(o->v.ClassDef.body, ast2obj_stmt); if (!value) goto failed; if (PyObject_SetAttrString(result, "body", value) == -1) diff --git a/Python/ast.c b/Python/ast.c index c8357b1..d4c8967 100644 --- a/Python/ast.c +++ b/Python/ast.c @@ -2092,28 +2092,6 @@ ast_for_testlist_gexp(struct compiling *c, const node* n) return ast_for_testlist(c, n); } -/* like ast_for_testlist() but returns a sequence */ -static asdl_seq* -ast_for_class_bases(struct compiling *c, const node* n) -{ - /* testlist: test (',' test)* [','] */ - assert(NCH(n) > 0); - REQ(n, testlist); - if (NCH(n) == 1) { - expr_ty base; - asdl_seq *bases = asdl_seq_new(1, c->c_arena); - if (!bases) - return NULL; - base = ast_for_expr(c, CHILD(n, 0)); - if (!base) - return NULL; - asdl_seq_SET(bases, 0, base); - return bases; - } - - return seq_for_testlist(c, n); -} - static stmt_ty ast_for_expr_stmt(struct compiling *c, const node *n) { @@ -3032,9 +3010,10 @@ ast_for_with_stmt(struct compiling *c, const node *n) static stmt_ty ast_for_classdef(struct compiling *c, const node *n) { - /* classdef: 'class' NAME ['(' testlist ')'] ':' suite */ - asdl_seq *bases, *s; - + /* classdef: 'class' NAME ['(' arglist ')'] ':' suite */ + asdl_seq *s; + expr_ty call, dummy; + REQ(n, classdef); if (!strcmp(STR(CHILD(n, 1)), "None")) { @@ -3042,32 +3021,36 @@ ast_for_classdef(struct compiling *c, const node *n) return NULL; } - if (NCH(n) == 4) { + if (NCH(n) == 4) { /* class NAME ':' suite */ s = ast_for_suite(c, CHILD(n, 3)); if (!s) return NULL; - return ClassDef(NEW_IDENTIFIER(CHILD(n, 1)), NULL, s, LINENO(n), - n->n_col_offset, c->c_arena); + return ClassDef(NEW_IDENTIFIER(CHILD(n, 1)), NULL, NULL, NULL, NULL, s, + LINENO(n), n->n_col_offset, c->c_arena); } - /* check for empty base list */ - if (TYPE(CHILD(n,3)) == RPAR) { + + if (TYPE(CHILD(n, 3)) == RPAR) { /* class NAME '(' ')' ':' suite */ s = ast_for_suite(c, CHILD(n,5)); if (!s) return NULL; - return ClassDef(NEW_IDENTIFIER(CHILD(n, 1)), NULL, s, LINENO(n), - n->n_col_offset, c->c_arena); + return ClassDef(NEW_IDENTIFIER(CHILD(n, 1)), NULL, NULL, NULL, NULL, s, + LINENO(n), n->n_col_offset, c->c_arena); } - /* else handle the base class list */ - bases = ast_for_class_bases(c, CHILD(n, 3)); - if (!bases) + /* class NAME '(' arglist ')' ':' suite */ + /* build up a fake Call node so we can extract its pieces */ + dummy = Name(NEW_IDENTIFIER(CHILD(n, 1)), Load, LINENO(n), n->n_col_offset, c->c_arena); + call = ast_for_call(c, CHILD(n, 3), dummy); + if (!call) return NULL; - s = ast_for_suite(c, CHILD(n, 6)); if (!s) return NULL; - return ClassDef(NEW_IDENTIFIER(CHILD(n, 1)), bases, s, LINENO(n), - n->n_col_offset, c->c_arena); + + return ClassDef(NEW_IDENTIFIER(CHILD(n, 1)), + call->v.Call.args, call->v.Call.keywords, + call->v.Call.starargs, call->v.Call.kwargs, s, + LINENO(n), n->n_col_offset, c->c_arena); } static stmt_ty diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 5d87744..4aa9c62 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -31,6 +31,113 @@ static PyObject *filterunicode(PyObject *, PyObject *); static PyObject *filtertuple (PyObject *, PyObject *); static PyObject * +builtin___build_class__(PyObject *self, PyObject *args, PyObject *kwds) +{ + PyObject *func, *name, *bases, *mkw, *meta, *prep, *ns, *res; + Py_ssize_t nargs, nbases; + + assert(args != NULL); + if (!PyTuple_Check(args)) { + PyErr_SetString(PyExc_TypeError, + "__build_class__: args is not a tuple"); + return NULL; + } + nargs = PyTuple_GET_SIZE(args); + if (nargs < 2) { + PyErr_SetString(PyExc_TypeError, + "__build_class__: not enough arguments"); + return NULL; + } + func = PyTuple_GET_ITEM(args, 0); /* Better be callable */ + name = PyTuple_GET_ITEM(args, 1); + if (!PyString_Check(name)) { + PyErr_SetString(PyExc_TypeError, + "__build_class__: name is not a string"); + return NULL; + } + bases = PyTuple_GetSlice(args, 2, nargs); + if (bases == NULL) + return NULL; + nbases = nargs - 2; + + if (kwds == NULL) { + meta = NULL; + mkw = NULL; + } + else { + mkw = PyDict_Copy(kwds); /* Don't modify kwds passed in! */ + if (mkw == NULL) { + Py_DECREF(bases); + return NULL; + } + meta = PyDict_GetItemString(mkw, "metaclass"); + if (meta != NULL) { + Py_INCREF(meta); + if (PyDict_DelItemString(mkw, "metaclass") < 0) { + Py_DECREF(meta); + Py_DECREF(mkw); + Py_DECREF(bases); + return NULL; + } + } + } + if (meta == NULL) { + if (PyTuple_GET_SIZE(bases) == 0) + meta = (PyObject *) (&PyType_Type); + else { + PyObject *base0 = PyTuple_GET_ITEM(bases, 0); + meta = (PyObject *) (base0->ob_type); + } + Py_INCREF(meta); + } + prep = PyObject_GetAttrString(meta, "__prepare__"); + if (prep == NULL) { + PyErr_Clear(); + ns = PyDict_New(); + } + else { + PyObject *pargs = Py_BuildValue("OO", name, bases); + if (pargs == NULL) { + Py_DECREF(prep); + Py_DECREF(meta); + Py_XDECREF(mkw); + Py_DECREF(bases); + return NULL; + } + ns = PyEval_CallObjectWithKeywords(prep, pargs, mkw); + Py_DECREF(pargs); + Py_DECREF(prep); + if (ns == NULL) { + Py_DECREF(meta); + Py_XDECREF(mkw); + Py_DECREF(bases); + return NULL; + } + } + res = PyObject_CallFunctionObjArgs(func, ns, NULL); + if (res != NULL) { + PyObject *margs; + Py_DECREF(res); + res = NULL; + margs = Py_BuildValue("OOO", name, bases, ns); + if (margs != NULL) { + res = PyEval_CallObjectWithKeywords(meta, margs, mkw); + Py_DECREF(margs); + } + } + Py_DECREF(ns); + Py_DECREF(meta); + Py_XDECREF(mkw); + Py_DECREF(bases); + return res; +} + +PyDoc_STRVAR(build_class_doc, +"__build_class__(func, name, *bases, metaclass=None, **kwds) -> class\n\ +\n\ +Internal helper function used by the class statement."); + +static PyObject * builtin___import__(PyObject *self, PyObject *args, PyObject *kwds) { static char *kwlist[] = {"name", "globals", "locals", "fromlist", @@ -2103,6 +2210,8 @@ NOTE: This is implemented using itertools.izip()."); static PyMethodDef builtin_methods[] = { + {"__build_class__", (PyCFunction)builtin___build_class__, + METH_VARARGS | METH_KEYWORDS, build_class_doc}, {"__import__", (PyCFunction)builtin___import__, METH_VARARGS | METH_KEYWORDS, import_doc}, {"abs", builtin_abs, METH_O, abs_doc}, {"all", builtin_all, METH_O, all_doc}, diff --git a/Python/ceval.c b/Python/ceval.c index 5cad632..5a3fc59 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -117,7 +117,6 @@ static int assign_slice(PyObject *, PyObject *, static PyObject * cmp_outcome(int, PyObject *, PyObject *); static PyObject * import_from(PyObject *, PyObject *); static int import_all_from(PyObject *, PyObject *); -static PyObject * build_class(PyObject *, PyObject *, PyObject *); static void set_exc_info(PyThreadState *, PyObject *, PyObject *, PyObject *); static void reset_exc_info(PyThreadState *); static void format_exc_check_arg(PyObject *, char *, PyObject *); @@ -1532,14 +1531,12 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) } break; - case LOAD_LOCALS: - if ((x = f->f_locals) != NULL) { - Py_INCREF(x); - PUSH(x); - continue; - } - PyErr_SetString(PyExc_SystemError, "no locals"); - break; + case STORE_LOCALS: + x = POP(); + v = f->f_locals; + Py_XDECREF(v); + f->f_locals = x; + continue; case RETURN_VALUE: retval = POP(); @@ -1586,16 +1583,16 @@ PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) Py_DECREF(v); break; - case BUILD_CLASS: - u = TOP(); - v = SECOND(); - w = THIRD(); - STACKADJ(-2); - x = build_class(u, v, w); - SET_TOP(x); - Py_DECREF(u); - Py_DECREF(v); - Py_DECREF(w); + case LOAD_BUILD_CLASS: + x = PyDict_GetItemString(f->f_builtins, + "__build_class__"); + if (x == NULL) { + PyErr_SetString(PyExc_ImportError, + "__build_class__ not found"); + break; + } + Py_INCREF(x); + PUSH(x); break; case STORE_NAME: @@ -4023,60 +4020,6 @@ import_all_from(PyObject *locals, PyObject *v) return err; } -static PyObject * -build_class(PyObject *methods, PyObject *bases, PyObject *name) -{ - PyObject *metaclass = NULL, *result, *base; - - if (PyDict_Check(methods)) - metaclass = PyDict_GetItemString(methods, "__metaclass__"); - if (metaclass != NULL) - Py_INCREF(metaclass); - else if (PyTuple_Check(bases) && PyTuple_GET_SIZE(bases) > 0) { - base = PyTuple_GET_ITEM(bases, 0); - metaclass = PyObject_GetAttrString(base, "__class__"); - if (metaclass == NULL) { - PyErr_Clear(); - metaclass = (PyObject *)base->ob_type; - Py_INCREF(metaclass); - } - } - else { - PyObject *g = PyEval_GetGlobals(); - if (g != NULL && PyDict_Check(g)) - metaclass = PyDict_GetItemString(g, "__metaclass__"); - if (metaclass == NULL) - metaclass = (PyObject *) &PyType_Type; - Py_INCREF(metaclass); - } - result = PyObject_CallFunctionObjArgs(metaclass, name, bases, methods, - NULL); - Py_DECREF(metaclass); - if (result == NULL && PyErr_ExceptionMatches(PyExc_TypeError)) { - /* A type error here likely means that the user passed - in a base that was not a class (such the random module - instead of the random.random type). Help them out with - by augmenting the error message with more information.*/ - - PyObject *ptype, *pvalue, *ptraceback; - - PyErr_Fetch(&ptype, &pvalue, &ptraceback); - if (PyString_Check(pvalue)) { - PyObject *newmsg; - newmsg = PyString_FromFormat( - "Error when calling the metaclass bases\n" - " %s", - PyString_AS_STRING(pvalue)); - if (newmsg != NULL) { - Py_DECREF(pvalue); - pvalue = newmsg; - } - } - PyErr_Restore(ptype, pvalue, ptraceback); - } - return result; -} - static void format_exc_check_arg(PyObject *exc, char *format_str, PyObject *obj) { diff --git a/Python/compile.c b/Python/compile.c index 1e4dddf..4c22441 100644 --- a/Python/compile.c +++ b/Python/compile.c @@ -176,6 +176,11 @@ static int inplace_binop(struct compiler *, operator_ty); static int expr_constant(expr_ty e); static int compiler_with(struct compiler *, stmt_ty); +static int compiler_call_helper(struct compiler *c, int n, + asdl_seq *args, + asdl_seq *keywords, + expr_ty starargs, + expr_ty kwargs); static PyCodeObject *assemble(struct compiler *, int addNone); static PyObject *__doc__; @@ -734,6 +739,8 @@ opcode_stack_effect(int opcode, int oparg) case PRINT_EXPR: return -1; + case LOAD_BUILD_CLASS: + return 1; case INPLACE_LSHIFT: case INPLACE_RSHIFT: case INPLACE_AND: @@ -744,8 +751,8 @@ opcode_stack_effect(int opcode, int oparg) return 0; case WITH_CLEANUP: return -1; /* XXX Sometimes more */ - case LOAD_LOCALS: - return 1; + case STORE_LOCALS: + return -1; case RETURN_VALUE: return -1; case IMPORT_STAR: @@ -757,8 +764,6 @@ opcode_stack_effect(int opcode, int oparg) return 0; case END_FINALLY: return -1; /* or -2 or -3 if exception occurred */ - case BUILD_CLASS: - return -2; case STORE_NAME: return -1; @@ -1509,54 +1514,107 @@ compiler_function(struct compiler *c, stmt_ty s) static int compiler_class(struct compiler *c, stmt_ty s) { - int n; + static PyObject *build_class = NULL; + static PyObject *locals = NULL; PyCodeObject *co; PyObject *str; - /* push class name on stack, needed by BUILD_CLASS */ - ADDOP_O(c, LOAD_CONST, s->v.ClassDef.name, consts); - /* push the tuple of base classes on the stack */ - n = asdl_seq_LEN(s->v.ClassDef.bases); - if (n > 0) - VISIT_SEQ(c, expr, s->v.ClassDef.bases); - ADDOP_I(c, BUILD_TUPLE, n); - if (!compiler_enter_scope(c, s->v.ClassDef.name, (void *)s, - s->lineno)) - return 0; - c->u->u_private = s->v.ClassDef.name; - Py_INCREF(c->u->u_private); - str = PyString_InternFromString("__name__"); - if (!str || !compiler_nameop(c, str, Load)) { - Py_XDECREF(str); - compiler_exit_scope(c); - return 0; + PySTEntryObject *ste; + + /* initialize statics */ + if (build_class == NULL) { + build_class = PyString_FromString("__build_class__"); + if (build_class == NULL) + return 0; } - - Py_DECREF(str); - str = PyString_InternFromString("__module__"); - if (!str || !compiler_nameop(c, str, Store)) { - Py_XDECREF(str); - compiler_exit_scope(c); - return 0; + if (locals == NULL) { + locals = PyString_FromString("__locals__"); + if (locals == NULL) + return 0; } - Py_DECREF(str); - if (!compiler_body(c, s->v.ClassDef.body)) { - compiler_exit_scope(c); + /* ultimately generate code for: + = __build_class__(, , *, **) + where: + is a function/closure created from the class body + is the class name + is the positional arguments and *varargs argument + is the keyword arguments and **kwds argument + This borrows from compiler_call. + */ + + /* 0. Create a fake variable named __locals__ */ + ste = PySymtable_Lookup(c->c_st, s); + if (ste == NULL) + return 0; + assert(PyList_Check(ste->ste_varnames)); + if (PyList_Append(ste->ste_varnames, locals) < 0) return 0; - } - ADDOP_IN_SCOPE(c, LOAD_LOCALS); - ADDOP_IN_SCOPE(c, RETURN_VALUE); - co = assemble(c, 1); + /* 1. compile the class body into a code object */ + if (!compiler_enter_scope(c, s->v.ClassDef.name, (void *)s, s->lineno)) + return 0; + /* this block represents what we do in the new scope */ + { + /* use the class name for name mangling */ + Py_INCREF(s->v.ClassDef.name); + c->u->u_private = s->v.ClassDef.name; + /* force it to have one mandatory argument */ + c->u->u_argcount = 1; + /* load the first argument ... */ + ADDOP_I(c, LOAD_FAST, 0); + /* ... and store it into f_locals */ + ADDOP_IN_SCOPE(c, STORE_LOCALS); + /* load __name__ ... */ + str = PyString_InternFromString("__name__"); + if (!str || !compiler_nameop(c, str, Load)) { + Py_XDECREF(str); + compiler_exit_scope(c); + return 0; + } + Py_DECREF(str); + /* ... and store it as __module__ */ + str = PyString_InternFromString("__module__"); + if (!str || !compiler_nameop(c, str, Store)) { + Py_XDECREF(str); + compiler_exit_scope(c); + return 0; + } + Py_DECREF(str); + /* compile the body proper */ + if (!compiler_body(c, s->v.ClassDef.body)) { + compiler_exit_scope(c); + return 0; + } + /* return None */ + ADDOP_O(c, LOAD_CONST, Py_None, consts); + ADDOP_IN_SCOPE(c, RETURN_VALUE); + /* create the code object */ + co = assemble(c, 1); + } + /* leave the new scope */ compiler_exit_scope(c); if (co == NULL) return 0; + /* 2. load the 'build_class' function */ + ADDOP(c, LOAD_BUILD_CLASS); + + /* 3. load a function (or closure) made from the code object */ compiler_make_closure(c, co, 0); Py_DECREF(co); - ADDOP_I(c, CALL_FUNCTION, 0); - ADDOP(c, BUILD_CLASS); + /* 4. load class name */ + ADDOP_O(c, LOAD_CONST, s->v.ClassDef.name, consts); + + /* 5. generate the rest of the code for the call */ + if (!compiler_call_helper(c, 2, + s->v.ClassDef.bases, + s->v.ClassDef.keywords, + s->v.ClassDef.starargs, + s->v.ClassDef.kwargs)) + return 0; + + /* 6. store into */ if (!compiler_nameop(c, s->v.ClassDef.name, Store)) return 0; return 1; @@ -2613,21 +2671,37 @@ compiler_compare(struct compiler *c, expr_ty e) static int compiler_call(struct compiler *c, expr_ty e) { - int n, code = 0; - VISIT(c, expr, e->v.Call.func); - n = asdl_seq_LEN(e->v.Call.args); - VISIT_SEQ(c, expr, e->v.Call.args); - if (e->v.Call.keywords) { - VISIT_SEQ(c, keyword, e->v.Call.keywords); - n |= asdl_seq_LEN(e->v.Call.keywords) << 8; - } - if (e->v.Call.starargs) { - VISIT(c, expr, e->v.Call.starargs); + return compiler_call_helper(c, 0, + e->v.Call.args, + e->v.Call.keywords, + e->v.Call.starargs, + e->v.Call.kwargs); +} + +/* shared code between compiler_call and compiler_class */ +static int +compiler_call_helper(struct compiler *c, + int n, /* Args already pushed */ + asdl_seq *args, + asdl_seq *keywords, + expr_ty starargs, + expr_ty kwargs) +{ + int code = 0; + + n += asdl_seq_LEN(args); + VISIT_SEQ(c, expr, args); + if (keywords) { + VISIT_SEQ(c, keyword, keywords); + n |= asdl_seq_LEN(keywords) << 8; + } + if (starargs) { + VISIT(c, expr, starargs); code |= 1; } - if (e->v.Call.kwargs) { - VISIT(c, expr, e->v.Call.kwargs); + if (kwargs) { + VISIT(c, expr, kwargs); code |= 2; } switch (code) { diff --git a/Python/graminit.c b/Python/graminit.c index 5c7fb6a..1287219 100644 --- a/Python/graminit.c +++ b/Python/graminit.c @@ -1635,7 +1635,7 @@ static arc arcs_76_2[2] = { {23, 4}, }; static arc arcs_76_3[2] = { - {9, 5}, + {14, 5}, {15, 6}, }; static arc arcs_76_4[1] = { diff --git a/Python/import.c b/Python/import.c index fae60cf..33953c7 100644 --- a/Python/import.c +++ b/Python/import.c @@ -72,9 +72,10 @@ extern time_t PyOS_GetLastModificationTime(char *, FILE *); 3030 (added keyword-only parameters) 3040 (added signature annotations) 3050 (print becomes a function) + 3060 (PEP 3115 metaclass syntax) . */ -#define MAGIC (3050 | ((long)'\r'<<16) | ((long)'\n'<<24)) +#define MAGIC (3060 | ((long)'\r'<<16) | ((long)'\n'<<24)) /* Magic word as global; note that _PyImport_Init() can change the value of this global to accommodate for alterations of how the diff --git a/Python/symtable.c b/Python/symtable.c index 5bac2a2..e9c9391 100644 --- a/Python/symtable.c +++ b/Python/symtable.c @@ -983,6 +983,11 @@ symtable_visit_stmt(struct symtable *st, stmt_ty s) if (!symtable_add_def(st, s->v.ClassDef.name, DEF_LOCAL)) return 0; VISIT_SEQ(st, expr, s->v.ClassDef.bases); + VISIT_SEQ(st, keyword, s->v.ClassDef.keywords); + if (s->v.ClassDef.starargs) + VISIT(st, expr, s->v.ClassDef.starargs); + if (s->v.ClassDef.kwargs) + VISIT(st, expr, s->v.ClassDef.kwargs); if (!symtable_enter_block(st, s->v.ClassDef.name, ClassBlock, (void *)s, s->lineno)) return 0; -- cgit v0.12