[Python-ideas] New syntax for 'dynamic' attribute access
Guido van Rossum
guido at python.org
Fri Feb 9 16:51:47 CET 2007
I've thought of the same syntax. I think you should submit this to the
PEP editor and argue on Python-dev for its inclusion in Python 2.6 --
there's no benefit that I see of waiting until 3.0.
--Guido
On 2/9/07, Ben North <ben at redfrontdoor.org> wrote:
> Hi,
>
> I'd like to describe an addition I made to Python syntax which allows
> easier access to attributes where the attribute name is only known at
> run-time. For example:
>
> setattr(self, method_name, getattr(self.metadata, method_name))
>
> from Lib/distutils/dist.py could be rewritten
>
> self.(method_name) = self.metadata.(method_name)
>
> As noted in the PEP-style description below, I mostly did this for
> fun, but I thought it might be worth bringing to the attention of
> python-ideas. A quick search through prior postings and Google for
> this idea didn't come up with anything.
>
> Ben.
>
>
>
> - - - - 8< - - - -
>
>
> PEP: XXX
> Title: Syntax For Dynamic Attribute Access
> Version: $Revision$
> Last-Modified: $Date$
> Author: Ben North <ben at redfrontdoor.org>
> Status: Draft
> Type: Standards Track
> Content-Type: text/plain
> Created: 29-Jan-2007
> Post-History:
>
>
> Abstract
>
> Dynamic attribute access is currently possible using the "getattr"
> and "setattr" builtins. The present PEP suggests a new syntax to
> make such access easier, allowing the coder for example to write
>
> x.('foo_%d' % n) += 1
>
> z = y.('foo_%d' % n).('bar_%s' % s)
>
> instead of
>
> attr_name = 'foo_%d' % n
> setattr(x, attr_name, getattr(x, attr_name) + 1)
>
> z = getattr(getattr(y, 'foo_%d' % n), 'bar_%s' % s)
>
>
> Note
>
> (I wrote this patch mostly to advance my own understanding of and
> experiment with the python language, but I've written it up in the
> style of a PEP in case it might be a useful idea.)
>
>
> Rationale
>
> Dictionary access and indexing both have a friendly invocation
> syntax: instead of x.__getitem__(12) the coder can write x[12].
> This also allows the use of subscripted elements in an augmented
> assignment, as in "x[12] += 1". The present proposal brings this
> ease-of-use to dynamic attribute access too.
>
> Attribute access is currently possible in two ways:
>
> * When the attribute name is known at code-writing time, the
> ".NAME" trailer can be used, as in
>
> x.foo = 42
> y.bar += 100
>
> * When the attribute name is computed dynamically at run-time, the
> "getattr" and "setattr" builtins must be used:
>
> x = getattr(y, 'foo_%d' % n)
> setattr(z, 'bar_%s' % s, 99)
>
> The "getattr" builtin also allows the coder to specify a default
> value to be returned in the event that the object does not have
> an attribute of the given name:
>
> x = getattr(y, 'foo_%d' % n, 0)
>
> This PEP describes a new syntax for dynamic attribute access ---
> "x.(expr)" --- with examples given in the Abstract above. The new
> syntax also allows the provision of a default value in the "get"
> case, as in:
>
> x = y.('foo_%d' % n, None)
>
> This 2-argument form of dynamic attribute access is not permitted
> as the target of an (augmented or normal) assignment. Finally,
> the new syntax can be used with the "del" statement, as in
>
> del x.(attr_name)
>
>
> Impact On Existing Code
>
> The proposed new syntax is not currently valid, so no existing
> well-formed programs have their meaning altered by this proposal.
>
> Across all "*.py" files in the 2.5 distribution, there are around
> 600 uses of "getattr", "setattr" or "delattr". They break down as
> follows (figures have some room for error because they were
> arrived at by partially-manual inspection):
>
> c.300 uses of plain "getattr(x, attr_name)", which could be
> replaced with the new syntax;
>
> c.150 uses of the 3-argument form, i.e., with the default
> value; these could be replaced with the 2-argument form
> of the new syntax (the cases break down into c.125 cases
> where the attribute name is a literal string, and c.25
> where it's only known at run-time);
>
> c.5 uses of the 2-argument form with a literal string
> attribute name, which I think could be replaced with the
> standard "x.attribute" syntax;
>
> c.120 uses of setattr, of which 15 use getattr to find the
> new value; all could be replaced with the new syntax,
> the 15 where getattr is also involved would show a
> particular increase in clarity;
>
> c.5 uses which would have to stay as "getattr" because they
> are calls of a variable named "getattr" whose default
> value is the builtin "getattr";
>
> c.5 uses of the 2-argument form, inside a try/except block
> which catches AttributeError and uses a default value
> instead; these could use 2-argument form of the new
> syntax;
>
> c.10 uses of "delattr", which could use the new syntax.
>
> As examples, the line
>
> setattr(self, attr, change_root(self.root, getattr(self, attr)))
>
> from Lib/distutils/command/install.py could be rewritten
>
> self.(attr) = change_root(self.root, self.(attr))
>
> and the line
>
> setattr(self, method_name, getattr(self.metadata, method_name))
>
> from Lib/distutils/dist.py could be rewritten
>
> self.(method_name) = self.metadata.(method_name)
>
>
> Alternative Syntax For The New Feature
>
> Other syntaxes could be used, for example braces are currently
> invalid in a "trailer", so could be used here, giving
>
> x{'foo_%d' % n} += 1
>
> My personal preference is for the
>
> x.('foo_%d' % n) += 1
>
> syntax though: the presence of the dot shows there is attribute
> access going on; the parentheses have an analogous meaning to the
> mathematical "work this out first" meaning. This is also the
> syntax used in the language Matlab [1] for dynamic "field" access
> (where "field" is the Matlab term analogous to Python's
> "attribute").
>
>
> Error Cases
>
> Only strings are permitted as attribute names, so for instance the
> following error is produced:
>
> >>> x.(99) = 8
> Traceback (most recent call last):
> File "<stdin>", line 1, in <module>
> TypeError: attribute name must be string, not 'int'
>
> This is handled by the existing PyObject_GetAttr function.
>
>
> Draft Implementation
>
> A draft implementation adds a new alternative to the "trailer"
> clause in Grammar/Grammar; a new AST type, "DynamicAttribute" in
> Python.asdl, with accompanying changes to symtable.c, ast.c, and
> compile.c, and three new opcodes (load/store/del) with
> accompanying changes to opcode.h and ceval.c. The patch consists
> of c.180 additional lines in the core code, and c.100 additional
> lines of tests.
>
>
> References
>
> [1] Using Dynamic Field Names :: Data Types (MATLAB Programming)
> http://www.mathworks.com/access/helpdesk/help/techdoc/matlab_prog/f2-41859.html
>
>
> Copyright
>
> This document has been placed in the public domain.
>
> [PAGE-BREAK GOES HERE BUT REMOVED FOR EMAIL]
> Local Variables:
> mode: indented-text
> indent-tabs-mode: nil
> sentence-end-double-space: t
> fill-column: 70
> coding: utf-8
> End:
>
>
> - - - - 8< - - - -
> diff --exclude=graminit.c --exclude='Python-ast.[ch]' -Nwuar ORIG__Python-2.5/Grammar/Grammar Python-2.5/Grammar/Grammar
> --- ORIG__Python-2.5/Grammar/Grammar 2006-05-25 12:25:51.000000000 +0100
> +++ Python-2.5/Grammar/Grammar 2007-02-01 18:07:04.133160000 +0000
> @@ -119,7 +119,7 @@
> listmaker: test ( list_for | (',' test)* [','] )
> testlist_gexp: test ( gen_for | (',' test)* [','] )
> lambdef: 'lambda' [varargslist] ':' test
> -trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
> +trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME | '.' '(' test [',' test] ')'
> subscriptlist: subscript (',' subscript)* [',']
> subscript: '.' '.' '.' | test | [test] ':' [test] [sliceop]
> sliceop: ':' [test]
> diff --exclude=graminit.c --exclude='Python-ast.[ch]' -Nwuar ORIG__Python-2.5/Include/opcode.h Python-2.5/Include/opcode.h
> --- ORIG__Python-2.5/Include/opcode.h 2006-02-27 22:32:47.000000000 +0000
> +++ Python-2.5/Include/opcode.h 2007-02-02 13:36:58.542848000 +0000
> @@ -39,6 +39,10 @@
> #define SLICE 30
> /* Also uses 31-33 */
>
> +/* LOAD_DYNAMIC_ATTR is below because it takes an argument. */
> +#define STORE_DYNAMIC_ATTR 36
> +#define DELETE_DYNAMIC_ATTR 37
> +
> #define STORE_SLICE 40
> /* Also uses 41-43 */
>
> @@ -89,6 +93,9 @@
> #define UNPACK_SEQUENCE 92 /* Number of sequence items */
> #define FOR_ITER 93
>
> +#define LOAD_DYNAMIC_ATTR 94 /* Whether default given; 0 = no, 1 = yes */
> +/* STORE_DYNAMIC_ATTR, DELETE_DYNAMIC_ATTR are above; they take no argument. */
> +
> #define STORE_ATTR 95 /* Index in name list */
> #define DELETE_ATTR 96 /* "" */
> #define STORE_GLOBAL 97 /* "" */
> diff --exclude=graminit.c --exclude='Python-ast.[ch]' -Nwuar ORIG__Python-2.5/Lib/test/test_dynattr.py Python-2.5/Lib/test/test_dynattr.py
> --- ORIG__Python-2.5/Lib/test/test_dynattr.py 1970-01-01 01:00:00.000000000 +0100
> +++ Python-2.5/Lib/test/test_dynattr.py 2007-02-02 17:37:37.982390000 +0000
> @@ -0,0 +1,91 @@
> +import unittest
> +import warnings
> +import sys
> +from test import test_support
> +
> +class AttrHolder:
> + pass
> +
> +class TestDynAttr(unittest.TestCase):
> +
> + def test_simple_get(self):
> + a = AttrHolder()
> + a.foo = 100
> + a.attr_42 = 1
> + self.assertEqual(a.('foo'), 100)
> + self.assertEqual(a.('fo' + 'o'), 100)
> + self.assertEqual(a.('f' + 'o' + [('o' if True else 'obar')][0]), 100)
> + self.assertEqual(a.({'FOO': 'fo'}['FOO'] + 'o'), 100)
> + self.assertEqual(a.('fo%s' % 'o'), 100)
> + self.assertEqual(a.('attr_42'), 1)
> + self.assertEqual(a.('attr_%d' % 42), 1)
> + self.assertEqual(a.('foo' if True else 'attr_42'), 100)
> +
> + def test_nested_get(self):
> + a = AttrHolder()
> + a.b = AttrHolder()
> + a.b.c = 1
> + attr_name_b = 'b'
> + attr_name_c = 'c'
> + self.assertEqual(a.(attr_name_b).(attr_name_c), 1)
> +
> + def test_defaulting_get(self):
> + a = AttrHolder()
> + a.foo = 100
> + self.assertEqual(a.('foo', 99), 100)
> + self.assertEqual(a.('bar', 99), 99)
> + self.assertEqual(a.('baz', 99), 99)
> + self.assertEqual(a.('foo' if True else 'attr_42', 99), 100)
> + self.assertEqual(a.('foo' if False else 'attr_42', 99), 99)
> +
> + @staticmethod
> + def attempt_non_string_use(attr_name):
> + a = AttrHolder()
> + return a.(attr_name)
> +
> + def test_only_strings_allowed(self):
> + self.assertRaises(TypeError, TestDynAttr.attempt_non_string_use, 99)
> + self.assertRaises(TypeError, TestDynAttr.attempt_non_string_use, None)
> + self.assertRaises(TypeError, TestDynAttr.attempt_non_string_use, 1.0)
> + self.assertRaises(TypeError, TestDynAttr.attempt_non_string_use, AttrHolder)
> + self.assertRaises(TypeError, TestDynAttr.attempt_non_string_use, sys)
> + self.assertRaises(TypeError, TestDynAttr.attempt_non_string_use, ())
> + self.assertRaises(TypeError, TestDynAttr.attempt_non_string_use, [])
> + self.assertRaises(TypeError, TestDynAttr.attempt_non_string_use, {})
> + self.assertRaises(TypeError, TestDynAttr.attempt_non_string_use, (1, 2))
> +
> + def test_augassign(self):
> + a = AttrHolder()
> + a.foo = 100
> + a.('foo') += 10
> + self.assertEqual(a.foo, 110)
> + self.assertEqual(a.('fo' + 'o'), 110)
> + a.('f' + 'o' + 'o') *= 10
> + self.assertEqual(a.foo, 1100)
> + self.assertEqual(a.('fo' + 'o'), 1100)
> + a.('foobar'[:3]) /= 5
> + self.assertEqual(a.foo, 220)
> + self.assertEqual(a.('fo' + 'o'), 220)
> + a.(['foo', 'bar', 'baz'][0]) -= 40
> + self.assertEqual(a.foo, 180)
> + self.assertEqual(a.('fo' + 'o'), 180)
> +
> + def test_setattr(self):
> + a = AttrHolder()
> + a.('foo') = 99
> + self.assertEqual(a.foo, 99)
> + a.('bar' + '_baz') = 100
> + self.assertEqual(a.bar_baz, 100)
> +
> + def test_delattr(self):
> + a = AttrHolder()
> + a.foo = 99
> + del a.('foo')
> + self.assertEqual(hasattr(a, 'foo'), False)
> +
> +
> +def test_main():
> + test_support.run_unittest(TestDynAttr)
> +
> +if __name__ == "__main__":
> + test_main()
> diff --exclude=graminit.c --exclude='Python-ast.[ch]' -Nwuar ORIG__Python-2.5/Lib/test/test_syntax.py Python-2.5/Lib/test/test_syntax.py
> --- ORIG__Python-2.5/Lib/test/test_syntax.py 2006-05-19 07:43:50.000000000 +0100
> +++ Python-2.5/Lib/test/test_syntax.py 2007-02-02 17:21:04.991119000 +0000
> @@ -235,6 +235,18 @@
> >>> f() += 1
> Traceback (most recent call last):
> SyntaxError: illegal expression for augmented assignment (<doctest test.test_syntax[33]>, line 1)
> +
> +Outlawed uses of dynamic attributes:
> +
> +>>> x.('foo', 0) += 1
> +Traceback (most recent call last):
> +SyntaxError: augmented assignment to 2-argument dynamic-attribute expression not possible (<doctest test.test_syntax[34]>, line 1)
> +>>> x.('foo', 0) = 1
> +Traceback (most recent call last):
> +SyntaxError: can't assign to 2-argument form of dynamic-attribute expression (<doctest test.test_syntax[35]>, line 1)
> +>>> del x.('foo', 0)
> +Traceback (most recent call last):
> +SyntaxError: can't delete 2-argument form of dynamic-attribute expression (<doctest test.test_syntax[36]>, line 1)
> """
>
> import re
> diff --exclude=graminit.c --exclude='Python-ast.[ch]' -Nwuar ORIG__Python-2.5/Parser/Python.asdl Python-2.5/Parser/Python.asdl
> --- ORIG__Python-2.5/Parser/Python.asdl 2006-04-04 05:00:23.000000000 +0100
> +++ Python-2.5/Parser/Python.asdl 2007-02-02 12:39:36.665151000 +0000
> @@ -72,6 +72,7 @@
>
> -- the following expression can appear in assignment context
> | Attribute(expr value, identifier attr, expr_context ctx)
> + | DynamicAttribute(expr value, expr attr, expr? dflt, expr_context ctx)
> | Subscript(expr value, slice slice, expr_context ctx)
> | Name(identifier id, expr_context ctx)
> | List(expr* elts, expr_context ctx)
> diff --exclude=graminit.c --exclude='Python-ast.[ch]' -Nwuar ORIG__Python-2.5/Python/ast.c Python-2.5/Python/ast.c
> --- ORIG__Python-2.5/Python/ast.c 2006-09-05 04:56:01.000000000 +0100
> +++ Python-2.5/Python/ast.c 2007-02-02 13:54:51.388446000 +0000
> @@ -353,6 +353,13 @@
> }
> e->v.Attribute.ctx = ctx;
> break;
> + case DynamicAttribute_kind:
> + if ((ctx == Store || ctx == Del)
> + && e->v.DynamicAttribute.dflt)
> + expr_name = "2-argument form of dynamic-attribute expression";
> + else
> + e->v.DynamicAttribute.ctx = ctx;
> + break;
> case Subscript_kind:
> e->v.Subscript.ctx = ctx;
> break;
> @@ -1427,7 +1434,7 @@
> static expr_ty
> ast_for_trailer(struct compiling *c, const node *n, expr_ty left_expr)
> {
> - /* trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME
> + /* trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME | '.' '(' test [',' test] ')'
> subscriptlist: subscript (',' subscript)* [',']
> subscript: '.' '.' '.' | test | [test] ':' [test] [sliceop]
> */
> @@ -1440,8 +1447,30 @@
> return ast_for_call(c, CHILD(n, 1), left_expr);
> }
> else if (TYPE(CHILD(n, 0)) == DOT ) {
> + if (TYPE(CHILD(n, 1)) == NAME)
> return Attribute(left_expr, NEW_IDENTIFIER(CHILD(n, 1)), Load,
> LINENO(n), n->n_col_offset, c->c_arena);
> + else {
> + expr_ty e_val, e_dflt;
> + REQ(CHILD(n, 1), LPAR);
> + if (!(e_val = ast_for_expr(c, CHILD(n, 2))))
> + return NULL;
> +
> + if (TYPE(CHILD(n, 3)) == RPAR) {
> + assert(NCH(n) == 3);
> + e_dflt = NULL;
> + } else {
> + assert(NCH(n) == 5);
> + REQ(CHILD(n, 3), COMMA);
> + REQ(CHILD(n, 5), RPAR);
> + if (!(e_dflt = ast_for_expr(c, CHILD(n, 4))))
> + return NULL;
> + }
> +
> + return DynamicAttribute(left_expr, e_val, e_dflt, Load,
> + LINENO(n), n->n_col_offset,
> + c->c_arena);
> + }
> }
> else {
> REQ(CHILD(n, 0), LSQB);
> @@ -1964,6 +1993,15 @@
> case Attribute_kind:
> case Subscript_kind:
> break;
> + case DynamicAttribute_kind:
> + if (expr1->v.DynamicAttribute.dflt) {
> + ast_error(ch, "augmented assignment to "
> + "2-argument dynamic-attribute "
> + "expression not possible");
> + return NULL;
> + }
> + break;
> +
> default:
> ast_error(ch, "illegal expression for augmented "
> "assignment");
> diff --exclude=graminit.c --exclude='Python-ast.[ch]' -Nwuar ORIG__Python-2.5/Python/ceval.c Python-2.5/Python/ceval.c
> --- ORIG__Python-2.5/Python/ceval.c 2006-08-13 19:10:10.000000000 +0100
> +++ Python-2.5/Python/ceval.c 2007-02-08 12:56:39.637479000 +0000
> @@ -1787,6 +1787,17 @@
> if (err == 0) continue;
> break;
>
> + case STORE_DYNAMIC_ATTR:
> + w = POP();
> + v = POP();
> + u = POP();
> + err = PyObject_SetAttr(v, w, u); /* v.w = u */
> + Py_DECREF(w);
> + Py_DECREF(v);
> + Py_DECREF(u);
> + if (err == 0) continue;
> + break;
> +
> case DELETE_ATTR:
> w = GETITEM(names, oparg);
> v = POP();
> @@ -1795,6 +1806,14 @@
> Py_DECREF(v);
> break;
>
> + case DELETE_DYNAMIC_ATTR:
> + w = POP();
> + v = POP();
> + err = PyObject_SetAttr(v, w, (PyObject *)NULL);
> + Py_DECREF(w);
> + Py_DECREF(v);
> + break;
> +
> case STORE_GLOBAL:
> w = GETITEM(names, oparg);
> v = POP();
> @@ -1994,6 +2013,28 @@
> if (x != NULL) continue;
> break;
>
> + case LOAD_DYNAMIC_ATTR:
> + if (oparg)
> + u = POP();
> + else
> + u = NULL;
> + w = POP();
> + v = TOP();
> + x = PyObject_GetAttr(v, w);
> + if (x == NULL && u != NULL
> + && PyErr_ExceptionMatches(PyExc_AttributeError))
> + {
> + PyErr_Clear();
> + Py_INCREF(u);
> + x = u;
> + }
> + Py_DECREF(v);
> + Py_DECREF(w);
> + Py_XDECREF(u); /* This one may be NULL (if no default) */
> + SET_TOP(x);
> + if (x != NULL) continue;
> + break;
> +
> case COMPARE_OP:
> w = POP();
> v = TOP();
> diff --exclude=graminit.c --exclude='Python-ast.[ch]' -Nwuar ORIG__Python-2.5/Python/compile.c Python-2.5/Python/compile.c
> --- ORIG__Python-2.5/Python/compile.c 2006-08-12 02:45:47.000000000 +0100
> +++ Python-2.5/Python/compile.c 2007-02-08 12:55:51.958809000 +0000
> @@ -1357,6 +1357,13 @@
> case SLICE+3:
> return -1;
>
> + case LOAD_DYNAMIC_ATTR:
> + return -1 - oparg;
> + case STORE_DYNAMIC_ATTR:
> + return -3;
> + case DELETE_DYNAMIC_ATTR:
> + return -2;
> +
> case STORE_SLICE+0:
> return -2;
> case STORE_SLICE+1:
> @@ -3641,6 +3648,41 @@
> return 0;
> }
> break;
> + case DynamicAttribute_kind:
> + {
> + int has_default_p = (e->v.DynamicAttribute.dflt != NULL);
> +
> + if (e->v.DynamicAttribute.ctx != AugStore) {
> + VISIT(c, expr, e->v.DynamicAttribute.value);
> + VISIT(c, expr, e->v.DynamicAttribute.attr);
> + if (has_default_p)
> + VISIT(c, expr, e->v.DynamicAttribute.dflt);
> + }
> + switch (e->v.DynamicAttribute.ctx) {
> + case AugLoad:
> + assert(!has_default_p);
> + ADDOP_I(c, DUP_TOPX, 2);
> + /* Fall through to Load */
> + case Load:
> + ADDOP_I(c, LOAD_DYNAMIC_ATTR, has_default_p);
> + break;
> + case AugStore:
> + ADDOP(c, ROT_THREE);
> + /* Fall through to Store */
> + case Store:
> + assert(!has_default_p);
> + ADDOP(c, STORE_DYNAMIC_ATTR);
> + break;
> + case Del:
> + ADDOP(c, DELETE_DYNAMIC_ATTR);
> + break;
> + default:
> + PyErr_SetString(PyExc_SystemError,
> + "invalid context in dynamic-attribute expression");
> + return 0;
> + }
> + break;
> + }
> case Subscript_kind:
> switch (e->v.Subscript.ctx) {
> case AugLoad:
> @@ -3700,6 +3742,18 @@
> auge->v.Attribute.ctx = AugStore;
> VISIT(c, expr, auge);
> break;
> + case DynamicAttribute_kind:
> + assert(e->v.DynamicAttribute.dflt == NULL);
> + auge = DynamicAttribute(e->v.DynamicAttribute.value, e->v.DynamicAttribute.attr, NULL,
> + AugLoad, e->lineno, e->col_offset, c->c_arena);
> + if (auge == NULL)
> + return 0;
> + VISIT(c, expr, auge);
> + VISIT(c, expr, s->v.AugAssign.value);
> + ADDOP(c, inplace_binop(c, s->v.AugAssign.op));
> + auge->v.DynamicAttribute.ctx = AugStore;
> + VISIT(c, expr, auge);
> + break;
> case Subscript_kind:
> auge = Subscript(e->v.Subscript.value, e->v.Subscript.slice,
> AugLoad, e->lineno, e->col_offset, c->c_arena);
> diff --exclude=graminit.c --exclude='Python-ast.[ch]' -Nwuar ORIG__Python-2.5/Python/symtable.c Python-2.5/Python/symtable.c
> --- ORIG__Python-2.5/Python/symtable.c 2006-08-12 02:43:40.000000000 +0100
> +++ Python-2.5/Python/symtable.c 2007-02-08 12:53:36.279608000 +0000
> @@ -1192,6 +1192,12 @@
> case Attribute_kind:
> VISIT(st, expr, e->v.Attribute.value);
> break;
> + case DynamicAttribute_kind:
> + VISIT(st, expr, e->v.DynamicAttribute.value);
> + VISIT(st, expr, e->v.DynamicAttribute.attr);
> + if (e->v.DynamicAttribute.dflt)
> + VISIT(st, expr, e->v.DynamicAttribute.dflt);
> + break;
> case Subscript_kind:
> VISIT(st, expr, e->v.Subscript.value);
> VISIT(st, slice, e->v.Subscript.slice);
>
>
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> http://mail.python.org/mailman/listinfo/python-ideas
>
--
--Guido van Rossum (home page: http://www.python.org/~guido/)
More information about the Python-ideas
mailing list