views:

1269

answers:

3

I didn't really pay as much attention to Python 3's development as I would have liked, and only just noticed some interesting new syntax changes. Specifically from this SO answer function parameter annotation:

def digits(x:'nonnegative number') -> "yields number's digits":
    # ...

Not knowing anything about this, I thought it could maybe be used for implementing static typing in Python!

After some searching, there seemed to be a lot discussion regarding (entirely optional) static typing in Python, such as that mentioned in PEP 3107, and "Adding Optional Static Typing to Python" (and part 2)

..but, I'm not clear how far this has progressed. Are there any implementations of static typing, using the parameter-annotation? Did any of the parameterised-type ideas make it into Python 3?

+8  A: 

As mentioned in that PEP, static type checking is one of the possible applications that function annotations can be used for, but they're leaving it up to third-party libraries to decide how to do it. That is, there isn't going to be an official implementation in core python.

As far as third-party implementations are concerned, there are some snippets (such as http://code.activestate.com/recipes/572161/), which seem to do the job pretty well.

EDIT:

As a note, I want to mention that checking behavior is preferable to checking type, therefore I think static typechecking is not so great an idea. My answer above is aimed at answering the question, not because I would do typechecking myself in such a way.

sykora
+1 for duck typing (checking behaviour than type)
fengshaun
Note that the snippet mentioned enforce dynamic type checking not **static** type checking.
Andrea Zilio
+4  A: 

"Static typing" in Python can only be implemented so that the type checking is done in run-time, which means it slows down the application. Therefore you don't want that as a generality. Instead you want some of your methods to check it's inputs. This can be easily done with plain asserts, or with decorators if you (mistakenly) think you need it a lot.

There is also an alternative to static type checking, and that is to use an aspect oriented component architecture like The Zope Component Architecture. Instead of checking the type, you adapt it. So instead of:

assert isinstance(theobject, myclass)

you do this:

theobject = IMyClass(theobject)

If theobject already implements IMyClass nothing happens. If it doesn't, an adapter that wraps whatever theobject is to IMyClass will be looked up, and used instead of theobject. If no adapter is found, you get an error.

This combined the dynamicism of Python with the desire to have a specific type in a specific way.

Lennart Regebro
Although.... it hasn't been ported to Python 3 yet. But that will probably happen this year or so.
Lennart Regebro
+4  A: 

Thanks for reading my code!

Indeed, it's not hard to create a generic annotation enforcer in Python. Here's my take:

'''Very simple enforcer of type annotations.

This toy super-decorator can decorate all functions in a given module that have 
annotations so that the type of input and output is enforced; an AssertionError is
raised on mismatch.

This module also has a test function func() which should fail and logging facility 
log which defaults to print. 

Since this is a test module, I cut corners by only checking *keyword* arguments.

'''

import sys

log = print


def func(x:'int' = 0) -> 'str':
    '''An example function that fails type checking.'''
    return x


# For simplicity, I only do keyword args.
def check_type(*args):
    param, value, assert_type = args
    log('Checking {0} = {1} of {2}.'.format(*args))
    if not isinstance(value, assert_type):
        raise AssertionError(
            'Check failed - parameter {0} = {1} not {2}.'
            .format(*args))
    return value

def decorate_func(func):    
    def newf(*args, **kwargs):
        for k, v in kwargs.items():
            check_type(k, v, ann[k])
        return check_type('<return_value>', func(*args, **kwargs), ann['return'])

    ann = {k: eval(v) for k, v in func.__annotations__.items()}
    newf.__doc__ = func.__doc__
    newf.__type_checked = True
    return newf

def decorate_module(module = '__main__'):
    '''Enforces type from annotation for all functions in module.'''
    d = sys.modules[module].__dict__
    for k, f in d.items():
        if getattr(f, '__annotations__', {}) and not getattr(f, '__type_checked', False):
            log('Decorated {0!r}.'.format(f.__name__))
            d[k] = decorate_func(f)


if __name__ == '__main__':
    decorate_module()

    # This will raise AssertionError.
    func(x = 5)

Given this simplicity, it's strange at the first sight that this thing is not mainstream. However, I believe there are good reasons why it's not as useful as it might seem. Generally, type checking helps because if you add integer and dictionary, chances are you made some obvious mistake (and if you meant something reasonable, it's still better to be explicit than implicit).

But in real life you often mix quantities of the same computer type as seen by compiler but clearly different human type, for example the following snippet contains an obvious mistake:

height = 1.75 # Bob's height in meters.
length = len(sys.modules) # Number of modules imported by program.
area = height * length # What's that supposed to mean???

Any human should immediately see a mistake in the above line provided it knows the 'human type' of variables height and length even though it looks to computer as perfectly legal multiplication of int and float.

There's more that can be said about possible solutions to this problem, but enforcing 'computer types' is apparently a half-solution, so, at least in my opinion, it's worse than no solution at all. It's the same reason why Systems Hungarian is a terrible idea while Apps Hungarian is a great one. There's more at the very informative post of Joel Spolsky.

Now if somebody was to implement some kind of Pythonic third-party library that would automatically assign to real-world data its human type and then took care to transform that type like width * height -> area and enforce that check with function annotations, I think that would be a type checking people could really use!

ilya n.
And here's what I just wrote about perils of strong typing: http://stackoverflow.com/questions/1251791/what-are-the-limits-of-type-checking-and-type-systems/1276675#1276675
ilya n.
What if bob had to paint one square meter of his bedroom with red paint for every module he imports?
Otto Allmendinger
Then you should use explicit type cast to punish Bob. Every system of strong typing, whether static or dynamic, has provisions for type casts: e.g. `'string' + 5` is prohibited but `'string' + str(5)` is fine.
ilya n.
Also note that your example would be just `sq.m * number = sq.m` which *does* make more sense than mine example of `m * number = sq.m ???`
ilya n.