[Python-Dev] type categories

Oren Tirosh oren-py-d@hishome.net
Tue, 27 Aug 2002 08:18:28 +0300


On Mon, Aug 26, 2002 at 08:39:44PM -0500, jepler@unpythonic.net wrote:
> On Mon, Aug 26, 2002 at 05:56:52PM -0400, Guido van Rossum wrote:
> > > It gets harder if you want to remove a method or marker. The problem is 
> > > that there is currently no way to mask inherited attributes. This will
> > > require either a language extension that will allow you to del them or 
> > > using some other convention for this purpose.
> > 
> > Can't you use this?
> > 
> > def B:
> >    def foo(self): pass
> > 
> > def C:
> >    foo = None # Don't implement foo
> 
> This comes closer:
> 
>     def raise_attributeerror(self):
>         raise AttributeError
> 
>     RemoveAttribute = property(raise_attributeerror)
> 
>     class A:
>         def f(self): print "method A.f"
>         def g(self): print "method A.g"
> 
>     class B:
>         f = RemoveAttribute

Yes, that's a good solution. But it should be some special builtin 
out-of-band value, not a user-defined property.

> writing 'b.f' will raise AttributeError, but unfortunately hasattr(B, 'f')
> will still return True.

This isn't necessarily a problem but hasattr could be taught about this 
out-of-band value.

--

Proposed hierarchy for categories, types and interfaces:

+category
   +type
     +int
     +str
       etc.
   +interface
     +Iattribute
     +Icallsignature
     +Iunion
     +Iintersection
       etc.

Both types and interfaces define a set. The set membership test is the 
'isinstance' function (implemented by a new slot). For types the set 
membership is defined by inheritance - the isinstance handler will get the 
first argument's type and crawl up the __bases__ DAG to see if it finds the 
itself.  Interfaces check the object's form instead of its ancestry.

An Iattribute interface checks for the presence of a single attribute and
applies another interface check to its value. An Icallsignature interface 
checks if the argument is a callable object with a specified number of 
arguments, default arguments, etc. An Iintersection interface checks that 
the argument matches a set of categories. 

example:

    interface readable:
        def read(bytes: int): str
        def readline(): str
        def readlines(): [str]

is just a more convenient way to write:

    readable = Iintersection(
        Iattribute('read', Icallsignature(str, ('bytes', int) )),
        Iattribute('readline', Icallsignature(str)),
        Iattribute('readlines', Icallsignature(Ilistof(str)))
    )

The name 'readable' is simply bound to the resulting object; interfaces are
defined by their value, not their name.  The types of arguments and return 
values will not be checked at first and only serve as documentation. Note
that they don't necessarily have to be types - they can be interfaces, too.
For example, 'str|int' in an interface declaration will be coverted to 
Iunion(str, int).

    >>>isinstance(file('/dev/null'), readable)
    True
    >>>isinstance(MyFileLikeClass(), readable)
    True

The MyFileLIkeClass or file classes do not have to be explicitly declared 
as implementing the readable interface.  The benefit of explit inteface
declarations is that you will get an error if you write a method that does
not match the declaration. If you try to implement two conflicting
interfaces this can also be detected immediately - the intersection of the 
two interfaces will reduce to the empty interface.  For now this will only
catch the same method name with different number of arguments but in the 
future it may detect conflicting argument or return value types.

  doesn't-have-anything-better-to-do-at-6-am-ly yours,

	Oren