Pyanno: Python Annotations
By Charles M. Chen,
This project came out of my work at Temboo, a startup in the Tribeca neighborhood of New York City.
They're hiring software engineers who want to work in Python, Qt, Java.
Contact us for details.

Simple Examples
from pyanno import raises, abstractMethod, returnType, parameterTypes, deprecatedMethod, \ privateMethod, protectedMethod, selfType, ignoreType @abstractMethod def func2(): pass @returnType(int, int) @parameterTypes(int, int) def divisionAndModulus(numerator, denominator): # return quotient and remainder of numerator and denominator quotient, remainder = divmod(numerator, denominator) return quotient, remainder @raises(IOError, ValueError) def func1(f, s): f.write( int(s) ) @deprecatedMethod def func2(): ... @privateMethod def func3(): ... @protectedMethod def func4(): ...
Version 0.77 (August 28th, 2007)
Source distribution includes python source, license, and this document.
Pyanno is a Python module that provides annotations for Python code.
The Pyanno annotations have two functions:
Conventional text comments def getDistance(p1, p2): """ getDistance() expects to be given two Points; it returns a floating point value representing the distance between those two points. """ ... Pyanno annotations from geometry import Point @returnType(float) @parameterTypes(Point, Point) def getDistance(p1, p2): """ getDistance() calculates the distance between two points. """ ...
Sample Usage
The following examples are all valid. For brevity, I've skipped the actual function definition and show only the annotations. from pyanno import raises, abstractMethod, returnType, parameterTypes, deprecatedMethod, \ privateMethod, protectedMethod, selfType, ignoreType, callableType @returnType # returns nothing @returnType ( ) # returns nothing @returnType ( ' ' ) # returns nothing @returnType ( str ) # returns a string (or None) @returnType ( ' str ' ) # returns a string (example of string-escaping, equivalent to previous example) @returnType ( str, int ) @returnType ( 'str', int ) @returnType ( ' str, int ' ) # string-escape multiple types in single string, equivalent to two previous examples @returnType ( tuple ) # returns a tuple @returnType ( () ) # equivalent to previous example @returnType ( dict ) # returns a dict @returnType ( {} ) # equivalent to previous example @returnType ( list ) # returns a list @returnType ( [] ) # equivalent to previous example @returnType ( [int] ) # returns a list containing only ints @returnType ( (int, ) ) # a tuple containing only ints (Note trailing comma) @returnType ( {int:str} ) # a map with int keys and string values @returnType ( {int: [ str ] } ) # a map with int keys and string-list values @returnType ( QObject ) # class @returnType ( 'QObject' ) # string-escaped, equivalent to previous example @returnType ( ClassName('QObject') ) # class, reference valid even if QObject has not been imported. @returnType ( ignoreType ) # One argument is required, but no type checking is done. @returnType ( classType ) @returnType ( instanceType ) @returnType ( callableType ) # accepts any method, function, closure, lambda, class or callable instance. @parameterTypes( selfType ) @parameterTypes( int ) # accepts an int argument @parameterTypes( int, [str] ) # accepts an int and a list of strings
The Annotations
Special Types
Parameterized Collections and Nesting of Types
You can check the types of tuple and list values, and dict key and value types.
@returnType ( (int, ) ) # a tuple containing only ints (Note trailing comma) @returnType ( [int] ) # a list containing only ints @returnType ( {int:str} ) # a map with int keys and string values @returnType ( [QObject] ) # a list of QObjects. # Collections can be arbitrarily nested: @returnType ( {str:[{str:int}]} ) # a map whose keys are string and whose values are # lists of dicts mapping strings to ints. @returnType ( [ [int] ] ) # a list of lists which contain only ints
[int] means 'a list of ints', not 'a list containing one int.'
You cannot specify multiple valid types for collection contents, ie. '[int, str]' is not valid.
Warning: Make sure to use a trailing comma when parameterizing tuples with a single element. (x,) is a tuple with a single element, (x) is just an element wrapped in parenthesis.
@returnType ( (int ) ) # wrong, missing trailing comma (means an int, not a tuple containing ints). @returnType ( (int, ) ) # correct.
String-Escaping Types
There are a number of situations where you cannot pass ordinary python types to annotations.
For example:
Class A: @parameterTypes( selfType, A ) def compare(self, other): return ==
This is an ordinary comparison method. Unfortunately, this annotation refers to class A within the definition of class A. As such, it is undefined.
@parameterTypes( selfType, 'A' )
The solution is to quote the class in a string. The annotations use lazy evaluation of their arguments.
You can string-escape multiple types in a single string. The following are equivalent:
@returnType ( str, int ) @returnType ( 'str', int ) @returnType ( ' str, int ' )
String-escaped and raw types can be mixed and matched arbitrarily:
@returnType ( 'str, Class1', str, 'int', str, 'Class2, Class1')
Circular References & ClassName
Python does not permit circular imports. ie. if module A imports module B, B cannot also import A.
A class cannot be referenced in an annotation if it is not imported - even if string-escaped.
A solution is to use ClassName.
@parameterTypes( selfType, ClassName('TestClass1' ) ) def doSomething(self, other): pass
Other options are to use ignoreType, or not use an annotation in this case.
Optional Annotation Arguments
The arguments for @parameterTypes, @returnType, @privateMethod and @raises are optional. The @abstractMethod and @deprecatedMethod annotations has no arguments.
These three forms:
@returnType @returnType ( ) @returnType ( ' ' )
all mean the same thing: this method returns nothing (a value of type NoneType).
Other Possible Annotations
These annotations have a common theme of bringing keywords and annotations familiar to Java and C++ users into the Python world.
Given that these languages reflect a very different design philosophy from Python, this may seem questionable. That is, are Pyanno annotations "Pythonic"?
On the one hand, Pyanno annotations would have no value if Python was statically-typed, like Java or C++. But Pyanno annotations do not make Python a statically-typed language, (see this). After all, Pyanno type-checking occurs at run-time, not compile time.
It's not a question of statically-type or dynamic-typing, but of access to type-checking at our own discretion. One can apply the annotations as narrowly as you like. For example, you may only want to annotate the public interface for a library or module. In this way, Pyanno annotations help offset a potential weakness of the language.
In many ways, their real value lies in providing more accurate documentation rather than helping to guarantee correctness.
Project Status
Version 0.77 released August 28th, 2007.
  • Added callableType type.
Version 0.76 released August 11th, 2007.
  • Added the @deprecated, @privateMethod and @protectedMethod annotations.
  • Added type-checking of the arguments to the @raises annotation.
  • Improved the documentation.
  • Elaborated the unit test suite, which is still incomplete. The unit tests are not part of the source distribution.
Version 0.75 released August 8th, 2007.
  • Initial release.
To do list & known bugs:
  • Add a @deprecated annotation.
This text is available under the ASF (Apache) License.
This is a non-viral Open Source license.
Pyanno annotations are implemented with Python's function decorators feature (new in Python 2.4):
Keywords and Keyphrases
In the interest of improving my goolge karma, allow me to mention:
python annotations, annotating python, python function decorators, type safe python, strongly typed python, statically typed python, python documentation, python type safety, python type checking, python function decorators, metaprogramming, python introspection,