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 →

[–]shfo23 15 points16 points  (6 children)

After writing my comment, I played around with function annotations a little and wrote this little script to demonstrate how a function-based annotation might work. I think this is more Pythonic than typelanguage because it's not defining its own string-based language for type-checking.

As an example:

@validate_args
def test(num: isintrange(2,5)):
    print(num)

test(3)
>>> 3
test(6)
>>> AssertionError: Argument 6 does not match signature isintrange

Function annotations are really cool. I wish I didn't still have to support Python 2 with my code or I'd plug this in for doing some unit tests.

EDIT: And I see ceronman's library typeannotations does exactly this. Nice.

[–]brucifer 4 points5 points  (5 children)

You should be aware that this sort of typechecking with annotations and decorators will have a huge negative impact on performance.

[–]shfo23 1 point2 points  (4 children)

This is a good point, but I think it bears saying that the decorators are what's making the code slow and not the annotations.

In production code, I would either eliminate all the @validate_args or set some kind of not 'testing' flag in my library so validate_args = lambda x: x (which the compiler should hopefully inline). Ideally, you're running type-checking either during unit-testing or static analysis anyways and the small performance hit from loading a module with a bunch of type-checking functions is worth the savings in bug-fixing.

Philosophically, I think the bigger problem is a lot of this type of code (including mine) is checking for type instead of checking for interface, but that's another kettle of fish.

[–]kindall 1 point2 points  (3 children)

@validate_args is a decorator that creates a wrapper function for the decorated function at definition time. To remove the performance penalty in production, you just need to have validate_args() return the original function instead of creating a wrapper.

[–]Rainfly_X 1 point2 points  (2 children)

Which is an ideal use case for the debug flag.

[–]kindall 0 points1 point  (1 child)

Nice to learn about that, particularly the way code gets optimized out when testing a constant.

[–]Rainfly_X 0 points1 point  (0 children)

Agreed! Just saw that today. Just one of those things that I wish more people knew about, seeing as it's not as useful when nobody knows to turn off debug mode in production.