Skip to content

Commit 7d2a8d9

Browse files
author
Drew O'Meara
committed
fixed kwarg issues in ParseTupleAndKeywords() and builtin_print test
1 parent 9afb2d3 commit 7d2a8d9

File tree

2 files changed

+77
-54
lines changed

2 files changed

+77
-54
lines changed

builtin/tests/builtin.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -299,40 +299,43 @@ def gen2():
299299
doc="print"
300300
ok = False
301301
try:
302-
print("hello", sep=1)
302+
print("hello", sep=1, end="!")
303303
except TypeError as e:
304-
#if e.args[0] != "sep must be None or a string, not int":
305-
# raise
304+
if e.args[0] != "print() argument 1 must be str, not int":
305+
raise
306306
ok = True
307307
assert ok, "TypeError not raised"
308308

309309
try:
310-
print("hello", sep=" ", end=1)
310+
print("hello", sep=",", end=1)
311311
except TypeError as e:
312-
#if e.args[0] != "end must be None or a string, not int":
313-
# raise
312+
if e.args[0] != "print() argument 2 must be str, not int":
313+
raise
314314
ok = True
315315
assert ok, "TypeError not raised"
316316

317317
try:
318-
print("hello", sep=" ", end="\n", file=1)
318+
print("hello", sep=",", end="!", file=1)
319319
except AttributeError as e:
320-
#if e.args[0] != "'int' object has no attribute 'write'":
321-
# raise
320+
if e.args[0] != "'int' has no attribute 'write'":
321+
raise
322322
ok = True
323323
assert ok, "AttributeError not raised"
324324

325325
with open("testfile", "w") as f:
326-
print("hello", "world", sep=" ", end="\n", file=f)
326+
print("hello", "world", sep=", ", end="!\n", file=f)
327+
print("hells", "bells", end="...", file=f, sep="_")
328+
print(" ~", "Brother ", "Foo", "bar", file=f, end="", sep="")
327329

328330
with open("testfile", "r") as f:
329-
assert f.read() == "hello world\n"
331+
assert f.read() == "hello, world!\nhells_bells... ~Brother Foobar"
330332

331333
with open("testfile", "w") as f:
332334
print(1,2,3,sep=",",end=",\n", file=f)
335+
print("4","5", file=f, end="!", sep=",")
333336

334337
with open("testfile", "r") as f:
335-
assert f.read() == "1,2,3,\n"
338+
assert f.read() == "1,2,3,\n4,5!"
336339

337340
doc="round"
338341
assert round(1.1) == 1.0

py/args.go

Lines changed: 62 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -368,9 +368,7 @@
368368
// $
369369
//
370370
// PyArg_ParseTupleAndKeywords() only: Indicates that the remaining
371-
// arguments in the Python argument list are keyword-only. Currently,
372-
// all keyword-only arguments must also be optional arguments, so |
373-
// must always be specified before $ in the format string.
371+
// arguments in the Python argument list are keyword-only.
374372
//
375373
// New in version 3.3.
376374
//
@@ -416,17 +414,13 @@ func ParseTupleAndKeywords(args Tuple, kwargs StringDict, format string, kwlist
416414
if kwlist != nil && len(results) != len(kwlist) {
417415
return ExceptionNewf(TypeError, "Internal error: supply the same number of results and kwlist")
418416
}
419-
min, max, name, ops := parseFormat(format)
420-
keywordOnly := false
421-
err := checkNumberOfArgs(name, len(args)+len(kwargs), len(results), min, max)
417+
var opsBuf [16]formatOp
418+
min, name, kwOnly_i, ops := parseFormat(format, opsBuf[:0])
419+
err := checkNumberOfArgs(name, len(args)+len(kwargs), len(results), min, len(ops))
422420
if err != nil {
423421
return err
424422
}
425423

426-
if len(ops) > 0 && ops[0] == "$" {
427-
keywordOnly = true
428-
ops = ops[1:]
429-
}
430424
// Check all the kwargs are in kwlist
431425
// O(N^2) Slow but kwlist is usually short
432426
for kwargName := range kwargs {
@@ -439,46 +433,60 @@ func ParseTupleAndKeywords(args Tuple, kwargs StringDict, format string, kwlist
439433
found:
440434
}
441435

442-
// Create args tuple with all the arguments we have in
443-
args = args.Copy()
444-
for i, kw := range kwlist {
445-
if value, ok := kwargs[kw]; ok {
446-
if len(args) > i {
436+
// Walk through all the results we want
437+
for i, op := range ops {
438+
439+
var (
440+
arg Object
441+
kw string
442+
)
443+
if i < len(kwlist) {
444+
kw = kwlist[i]
445+
arg = kwargs[kw]
446+
}
447+
448+
// Consume ordered args first -- they should not require keyword only or also be specified via keyword
449+
if i < len(args) {
450+
if i >= kwOnly_i {
451+
return ExceptionNewf(TypeError, "%s() specifies argument '%s' that is keyword only", name, kw)
452+
}
453+
if arg != nil {
447454
return ExceptionNewf(TypeError, "%s() got multiple values for argument '%s'", name, kw)
448455
}
449-
args = append(args, value)
450-
} else if keywordOnly {
451-
args = append(args, nil)
456+
arg = args[i]
452457
}
453-
}
454-
for i, arg := range args {
455-
op := ops[i]
458+
459+
// Unspecified args retain their default value
460+
if arg == nil {
461+
continue
462+
}
463+
456464
result := results[i]
457-
switch op {
458-
case "O":
465+
switch op.code {
466+
case 'O':
459467
*result = arg
460-
case "Z", "z":
468+
case 'Z', 'z':
461469
if _, ok := arg.(NoneType); ok {
462470
*result = arg
463471
break
464472
}
465473
fallthrough
466-
case "U", "s":
474+
case 'U', 's':
467475
if _, ok := arg.(String); !ok {
468476
return ExceptionNewf(TypeError, "%s() argument %d must be str, not %s", name, i+1, arg.Type().Name)
469477
}
470478
*result = arg
471-
case "i":
479+
case 'i':
472480
if _, ok := arg.(Int); !ok {
473481
return ExceptionNewf(TypeError, "%s() argument %d must be int, not %s", name, i+1, arg.Type().Name)
474482
}
475483
*result = arg
476-
case "p":
484+
case 'p':
477485
if _, ok := arg.(Bool); !ok {
478486
return ExceptionNewf(TypeError, "%s() argument %d must be bool, not %s", name, i+1, arg.Type().Name)
479487
}
480488
*result = arg
481-
case "d":
489+
case 'd':
482490
switch x := arg.(type) {
483491
case Int:
484492
*result = Float(x)
@@ -500,30 +508,42 @@ func ParseTuple(args Tuple, format string, results ...*Object) error {
500508
return ParseTupleAndKeywords(args, nil, format, nil, results...)
501509
}
502510

511+
type formatOp struct {
512+
code byte
513+
modifier byte
514+
}
515+
503516
// Parse the format
504-
func parseFormat(format string) (min, max int, name string, ops []string) {
517+
func parseFormat(format string, in []formatOp) (min int, name string, kwOnly_i int, ops []formatOp) {
505518
name = "function"
506519
min = -1
507-
for format != "" {
508-
op := string(format[0])
509-
format = format[1:]
510-
if len(format) > 1 && (format[1] == '*' || format[1] == '#') {
511-
op += string(format[0])
512-
format = format[1:]
520+
kwOnly_i = 0xFFFF
521+
ops = in[:0]
522+
523+
N := len(format)
524+
for i := 0; i < N; {
525+
op := formatOp{code: format[i]}
526+
i++
527+
if i < N {
528+
if mod := format[i]; mod == '*' || mod == '#' {
529+
op.modifier = mod
530+
i++
531+
}
513532
}
514-
switch op {
515-
case ":", ";":
516-
name = format
517-
format = ""
518-
case "|":
533+
switch op.code {
534+
case ':', ';':
535+
name = format[i:]
536+
i = N
537+
case '$':
538+
kwOnly_i = len(ops)
539+
case '|':
519540
min = len(ops)
520541
default:
521542
ops = append(ops, op)
522543
}
523544
}
524-
max = len(ops)
525545
if min < 0 {
526-
min = max
546+
min = len(ops)
527547
}
528548
return
529549
}

0 commit comments

Comments
 (0)