[Types-sig] A type checking system in Python
Clark C. Evans
cce@clarkevans.com
Wed, 21 Mar 2001 05:09:13 -0500 (EST)
This was a very insightful post on the main list.
---------- Forwarded message ----------
Date: Wed, 21 Mar 2001 09:15:50 GMT
From: Huaiyu Zhu <hzhu@mars.localdomain>
Type checking system in Python
Classes implement a hierarchy of inheritance of attributes (variables and
methods). They are useful for implementation modularization.
There is another type of modularization that is similar in nature, but is
independent of classes. This is the hierarchy of polymorphic interfaces.
Python supports polymorphism in a very liberal way: if it walks like a duck
and quacks like a duck, then it must be a duck. The archetypical examples
is that one can usually substitute a writable file with a user defined class
object that happens to have a write() methods with appropriate property.
In many programs it is necessary to ensure that a certain objects has
certain properties. But how can we express the idea that a function expect
a file-like object, while that can mean an endless list of things, many have
not yet been defined?
One solution promoted by some other languages is to define some abstract
classes for type checking purpose, and require them to be inherited by any
class that behaves like them. This is like what interfaces do in Java.
This solution is not satisfactory because
- It breaks polymorphism. One has to harness the property at the time the
classes are defined, instead of when they are used. For example, if an
application requires a property of "ReadableFile" which is satisfied by
sys.stdin, it is difficult to retrofit the definition of sys.stdin to be
an instance of such.
- It mixes two incompatible inheritances. For example, suppose that class A
has method A.m() but its subclass B overrides it with B.m(). The property
A satisfies is not necessarily satisfied by B.
>From the above it is clear that there is a useful conceptual hierarchy that
is independent of the class hierarchy. For lack of a better name let us
just call them Types. The overridability means that classes do not imply
Types, and the polymorphism means that Types do not imply classes. Their
main conceptual difference is that
- classes embody implementations
- Types embody requirements
Another way to describe the difference is
- classes are inherited by specialization
- Types are inherited by abstraction
For example, if we specify that A is a superclass of B, this does not change
anything about A, but it does mean that B will have all the attributes of A.
In contrast, if we specify that A is a superType of B, this does not change
anything about B, but it does mean that objects accepted as B must now be
accepted as A as well. As a concrete example, consider a function expecting
a Number as an argument. If we declare that 2.3 is a Float, and Float is a
special case of Number, then the function should accept 2.3 as argument.
Therefore, Types should have their own hierarchy, possibly with its own
syntax, that is independent of classes.
Since Python is a very dynamically typed system, it is also desirable to
make the type checking as flexible as possible, and completely voluntary,
while being able to deliver the same level of guarantees that a strongly and
statically typed language would give.
Type can also be used to express properties like Immutable, which could only
be guaranteed by a compiler. These properties will also be very useful for
compiler optimization.
All said, there is a role for classes to play in this affair - It is
possible to fake such a Type system using a class, with some caveats:
- It is dynamic, and very inefficient at run time
- It is dynamic, and can be circumvented in various ways.
Nevertheless, this dynamical type-checking does provide a fine illustration
of the concepts discussed above. If and when they can be coded in compiler
it will be of much greater use.
==================================================================
#!/usr/bin/env python
# $Id: test.py,v 1.1.1.1 2000/08/29 00:38:54 hzhu Exp $
"""
A simulated type checking system
Types are inherited by abstraction
"""
class Type:
def __init__(self, name):
"Define a new abstract Type"
self.name = name
self.types = []
self.classes = []
self.subs = []
self.attrs = []
self.objs = []
def include(self, obj):
"Check to see if object is of my Type"
# If it is explicitly included objects
for o in self.objs:
if obj == o: return 1
# If it is of the right type or class
if type(obj) is type(Type):
for c in self.classes:
if isinstance(obj, c): return 1
else:
for t in self.types:
if type(obj) == t: return 1
# If it is one of our sub-Type
for s in self.subs:
if s.include(obj): return 1
else:
return 0
def satisfiedby(self, obj):
"""Check to see if object has all the required attributes
Is this necessary? """
for a in self.attrs:
if not hasattr(obj, a):
print "%s has no attr %s" %(`obj`, a)
raise AssertionError
def add_type(self, t):
if t not in self.types: self.types.append(t)
def add_class(self, c):
if c not in self.classes: self.classes.append(c)
def add_sub(self, s):
if s not in self.subs: self.subs.append(s)
def add_attr(self, m):
if m not in self.attrs: self.attrs.append(m)
def add_obj(self, o):
if o not in self.objs: self.objs.append(o)
def add_typeof(self, o): self.add_type(type(o))
def add_classof(self, o): self.add_class(o.__class__)
def add_attrof(self, o):
for attr in dir(o): self.add_attr(attr)
def __repr__(self): return "Type(%s)" % `self.name`
def require(x, t):
try:
assert t.include(x)
except AssertionError:
print "%s is not of %s" % (`x`, t)
raise
t.satisfiedby(x)
def require_either(x, list):
for t in list:
if t.include(x):
t.satisfiedby(x)
return
else:
print "%s is not of any Type in %s" % (`x`, list)
raise AssertionError
def require_all(x, list):
for t in list: require(x,t)
#------------------------------------------------------------------
String = Type("String")
String.add_typeof("")
Integer = Type("Integer")
Integer.add_typeof(1)
Integer.add_typeof(1L)
Number = Type("Number")
Number.add_sub(Integer)
Float = Type("Float")
Float.add_typeof(0.0)
Number.add_sub(Float)
File = Type("File")
File.add_attr("close")
#------------------------------------------------------------------
if __name__ == "__main__":
def f(x):
require(x, Number)
print x*2
f(1)
f(2.3)
def f(x):
require_either(x, (Number, String))
print x*2
f("2")
import sys
File.add_obj(sys.stdout)
require(sys.stdout, File)
require_either("2", (Number, String))
#require_all("2", (Number, String))
--
http://mail.python.org/mailman/listinfo/python-list