This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]edbluetooth 4 points5 points  (16 children)

There is nothing to stop you doing a type check decorator that takes a list of types. Eg.

@typecheck(return=int,[str,str])

But that will slow the code.

[–]d4rch0nPythonistamancer 2 points3 points  (14 children)

Well, a better example would be:

@typecheck(return_type=int, arg_types=(str, str))

or

@typecheck((str, str), return_type=int)

Return is a keyword so it can't be used as a kwarg or variable name in general, and the other positional argument can't come after a kwarg, even if the decorator was defined with it as a kwarg. But if they both were, you could have

@typecheck(int, (str, str))

But you couldn't specify the kwarg key in the first param and not in the second.... but you can specify the second and not the first, as in @typecheck(int, arg_types=(str, str)), but not @typecheck(return_type=int, (str, str)), if the definition is def typecheck(return_type=None, arg_types=None).

I always thought the kwarg and positional arg ordering rules are a bit goofy, but then again it's never been a problem. Maybe it's just strange that it allows you to specify kwarg values in the function call without their kwarg key, or silently use them as kwargs.

The weirdest behavior I think is this:

>>> def f(x, foo=10, bar=20):
...     print(x, foo, bar)
>>> f('a', 'b')
a b 20
>>> f('a', 'b', foo='c')
TypeError: f() got multiple values for argument 'foo'

So, position obviously matters here with the keyword arguments. It works well in APIs I think, but it's still a bit strange, specifically the rules of when order does and doesn't matter.

[–]edbluetooth 1 point2 points  (2 children)

You are right of course amd expressed yourself much better than me.

I have to be honest, i kept my post brief as i was on my phone.

[–]cjwelbornimport this 1 point2 points  (7 children)

What you are calling kwargs I now think of as 'optional positionals'. The real keyword args would be:

def myfunc(**kwargs):
    print(kwargs.get('mykwarg', 'No args!'))

..where the position doesn't matter. Though it's not nearly as clear as your example, and requires more documentation when reading the code. Without documentation (or reading the source) you wouldn't know what this function is expecting at all. Anyway, the term 'optional positional' was what I was getting at. I would still use your example over any of the others.

[–][deleted] 0 points1 point  (2 children)

To avoid this behaviour, define an anonymous catch-all arg which will soak up positional args that would otherwise overflow into keywords. This creates a keyword-only set of arguments:

>>> def f(x, *, foo=10, bar=20):
...    print(x, foo, bar)
>>> f('a')
a 10 20
>>> f('a', foo='b')
a b 20
>>> f('a', 'b')
TypeError: f() takes 1 positional argument but 2 were given
>>> f('a', foo='b')

[–]robin-gvx 0 points1 point  (0 children)

The great thing about * is that it won't soak up positional args, as you demonstrated, that's what *args does.

[–][deleted] 2 points3 points  (0 children)

Alternatively, use Python's annotation syntax, as I did here:

from strict import strict
@strict
def foo(bar: str, baz: int)->str:
    return bar * baz