[Python-ideas] New 3.x restriction on number of keyword arguments

M.-A. Lemburg mal at egenix.com
Sat Oct 23 00:36:30 CEST 2010


Cesare Di Mauro wrote:
> 2010/10/22 M.-A. Lemburg <mal at egenix.com>
> 
>> Cesare Di Mauro wrote:
>>> I think that having more than 255 arguments for a function call is a very
>>> rare case for which a workaround (may be passing a tuple/list or a
>>> dictionary) can be a better solution than having to introduce a brand new
>>> opcode to handle it.
>>
>> It's certainly rare when writing applications by hand, but such
>> limits can be reached with code generators wrapping external resources
>> such as database query rows, spreadsheet rows, sensor data input, etc.
>>
>> We've had such a limit before (number of lines in a module) and that
>> was raised for the same reason.
>>
>>> Changing the current opcode(s) is a very bad idea, since common cases
>> will
>>> slow down.
>>
>> I'm sure there are ways to avoid that, e.g. by using EXTENDED_ARG
>> for such cases.
>>
>> --
>> Marc-Andre Lemburg
>> eGenix.com
>>
> 
> I've patched Python 3.2 alpha 3 with a rough solution using EXTENDED_ARG for
> CALL_FUNCTION* opcodes, raising the arguments and keywords limits to 65535
> maximum. I hope it'll be enough. :)

Sure, we don't have to raise it to 2**64 :-) Looks like a pretty simple fix,
indeed.

I wish we could get rid off all the byte shifting and div'ery
use in the byte compiler - I'm pretty sure that such operations
are rather slow nowadays compared to working with 16-bit or 32-bit
integers and dropping the notion of taking the word "byte"
in byte code literally.

> In ast.c:
> 
> ast_for_arguments:
> if (nposargs > 65535 || nkwonlyargs > 65535) {
> ast_error(n, "more than 65535 arguments");
> return NULL;
> }
> 
> ast_for_call:
> if (nargs + ngens > 65535 || nkeywords > 65535) {
> ast_error(n, "more than 65535 arguments");
> return NULL;
> }
> 
> 
> In compile.c:
> 
> opcode_stack_effect:
> #define NARGS(o) (((o) & 0xff) + ((o) >> 8 & 0xff00) + 2*(((o) >> 8 & 0xff)
> + ((o) >> 16 & 0xff00)))
>      case CALL_FUNCTION:
>          return -NARGS(oparg);
>      case CALL_FUNCTION_VAR:
>      case CALL_FUNCTION_KW:
>          return -NARGS(oparg)-1;
>      case CALL_FUNCTION_VAR_KW:
>          return -NARGS(oparg)-2;
> #undef NARGS
> #define NARGS(o) (((o) % 256) + 2*(((o) / 256) % 256))
>      case MAKE_FUNCTION:
>          return -NARGS(oparg) - ((oparg >> 16) & 0xffff);
>      case MAKE_CLOSURE:
>          return -1 - NARGS(oparg) - ((oparg >> 16) & 0xffff);
> #undef NARGS
> 
> compiler_call_helper:
> int len;
> int code = 0;
> 
> len = asdl_seq_LEN(args) + n;
> n = len & 0xff | (len & 0xff00) << 8;
> VISIT_SEQ(c, expr, args);
> if (keywords) {
>   VISIT_SEQ(c, keyword, keywords);
>   len = asdl_seq_LEN(keywords);
>   n |= (len & 0xff | (len & 0xff00) << 8) << 8;
> }
> 
> 
> In ceval.c:
> 
> PyEval_EvalFrameEx:
>      TARGET_WITH_IMPL(CALL_FUNCTION_VAR, _call_function_var_kw)
>      TARGET_WITH_IMPL(CALL_FUNCTION_KW, _call_function_var_kw)
>      TARGET(CALL_FUNCTION_VAR_KW)
>      _call_function_var_kw:
>      {
>          int na = oparg & 0xff | oparg >> 8 & 0xff00;
>          int nk = (oparg & 0xff00 | oparg >> 8 & 0xff0000) >> 8;
> 
> 
> call_function:
>    int na = oparg & 0xff | oparg >> 8 & 0xff00;
>    int nk = (oparg & 0xff00 | oparg >> 8 & 0xff0000) >> 8;
> 
> 
> A quick example:
> 
> s = '''def f(*Args, **Keywords):
>     print('Got', len(Args), 'arguments and', len(Keywords), 'keywords')
> 
> def g():
>     f(''' + ', '.join(str(i) for i in range(500)) + ', ' + ', '.join('k{} =
> {}'.format(i, i) for i in range(500)) + ''')
> 
> g()
> '''
> 
> c = compile(s, '<string>', 'exec')
> eval(c)
> from dis import dis
> dis(g)
> 
> 
> The output is:
> 
> Got 500 arguments and 500 keywords
> 
> 5 0 LOAD_GLOBAL 0 (f)
> 3 LOAD_CONST 1 (0)
> 6 LOAD_CONST 2 (1)
> [...]
> 1497 LOAD_CONST 499 (498)
> 1500 LOAD_CONST 500 (499)
> 1503 LOAD_CONST 501 ('k0')
> 1506 LOAD_CONST 1 (0)
> 1509 LOAD_CONST 502 ('k1')
> 1512 LOAD_CONST 2 (1)
> [...]
> 4491 LOAD_CONST 999 ('k498')
> 4494 LOAD_CONST 499 (498)
> 4497 LOAD_CONST 1000 ('k499')
> 4500 LOAD_CONST 500 (499)
> 4503 EXTENDED_ARG 257
> 4506 CALL_FUNCTION 16905460
> 4509 POP_TOP
> 4510 LOAD_CONST 0 (None)
> 4513 RETURN_VALUE
> 
> The dis module seems to have some problem displaying the correct extended
> value, but I have no time now to check and fix it.
> 
> Anyway, I'm still unconvinced of the need to raise the function def/call
> limits.

It may seem strange to have functions, methods or object constructors
with more than 255 parameters, but as I said: when using code generators,
the generators don't care whether they use 100 or 300 parameters. Even if
just 10 parameters are actually used later on. However, the user
will care a lot if the generators fail due such limits and then become
unusable.

As example, take a database query method that exposes 3-4 parameters
for each query field. In more complex database schemas that you find
in e.g. data warehouse applications, it is not uncommon to have
100+ query fields or columns in a data table.

With the current
limit in function/call argument counts, such a model could not be
mapped directly to Python. Instead, you'd have to turn to solutions
based on other data structures that are not automatically checked
by Python when calling methods/functions.

-- 
Marc-Andre Lemburg
eGenix.com

Professional Python Services directly from the Source  (#1, Oct 22 2010)
>>> Python/Zope Consulting and Support ...        http://www.egenix.com/
>>> mxODBC.Zope.Database.Adapter ...             http://zope.egenix.com/
>>> mxODBC, mxDateTime, mxTextTools ...        http://python.egenix.com/
________________________________________________________________________

::: Try our new mxODBC.Connect Python Database Interface for free ! ::::


   eGenix.com Software, Skills and Services GmbH  Pastor-Loeh-Str.48
    D-40764 Langenfeld, Germany. CEO Dipl.-Math. Marc-Andre Lemburg
           Registered at Amtsgericht Duesseldorf: HRB 46611
               http://www.egenix.com/company/contact/



More information about the Python-ideas mailing list