[Types-sig] QueryProtocol
Clark C. Evans
cce@clarkevans.com
Wed, 21 Mar 2001 17:30:08 -0500 (EST)
> > * __query__ is renamed __adapt__ (adapter pattern)
> > * __adapt__ now takes a class instance (instead of a DNS string)
> > * __adapt__ now returns None if the lookup was unsuccessful
> > * straw-man adapt function is detailed
> > * the title of this PEP is updated respectively
>
> This is very good. It actually can assimilate my earlier proposal for
> abstract type checking via a __type__ method.
>
> I also see that adapt() works to assimilate isinstance(), and probably
> could assimilate issubclass(). I support that work, but I see that it gives
> some complexity to the meaning of the return value -- in adapter-like
> cases, you want to return self or an adapter of self; in test-like cases,
> you want to return a boolean. I also think that is very cool.
I see two different things here. "Check" and "Adapt",
where Check is a subset of Adapt, i.e., if the current
object checks out, it does not need to be adapted.
...
Adapt may produce a brand new object which complies with
the protocol (either an interface or class or whatever)
identified, while I think Check is explicitly asking if
the *current* object (not it's adapted variety) passes muster.
However, "adapt" could easly call "check" for the trivial
case, or the functions could be merged with an argument
(checkonly=true/false). Below Is the "seperated" version.
After writing it out, I think I like the merged version
with the flag... your thoughts?
Note: the code below would have to be smartened up as
your sample implementation below demonstrates.
def adapt(obj,cls):
# first see if it is a no-brainer
if check(obj,cls): return obj
# the object may have the answer, so ask it about the identifier
adapt = getattr(obj, '__adapt__',None)
if adapt:
retval = adapt(cls)
if retval: return retval
# the identifier may have the answer, so ask it about the object.
#
# some code to do a reverse lookup based on the
# identifier (a future PEP)
#
return None
def check(obj,cls):
if isinstance(obj,cls):
return obj
#
# if cls is some other type of identifier (future PEP?)
# then do signature or other type checking here to
# verify that the *current* class is acceptable
#
return None
So that...
import adapter.example
from adapter import adapt
from adapter import check
x = adapter.example.EggsSpamAndHam()
adapt(x,adapter.example.SpamOnly).spam("Ni!")
adapt(x,adapter.example.EggsOnly).eggs("Ni!")
adapt(x,adapter.example.HamOnly).ham("Ni!")
adapt(x,adapter.example.EggsSpamAndHam).ham("Ni!")
if check(x,adapter.example.SpamOnly): print "SpamOnly"
if check(x,adapter.example.EggsOnly): print "EggsOnly"
if check(x,adapter.example.HamOnly): print "HamOnly"
if check(x,adapter.example.EggsSpamAndHam): print "EggsAndSpam"
if check(x,adapter.example.KnightsWhoSayNi): print "NightsWhoSayNi"
adapt(x,adapter.example.KnightsWhoSayNi).spam("Ni!")
produces...
spam!Ni!
eggs!Ni!
ham!Ni!
ham!Ni!
SpamOnly
EggsAndSpam
Traceback (innermost last):
File "<interactive input>", line 1, in ?
File "c:\work\testadapter.py", line 14, in testadapter
AttributeError: KnightsWhoSayNi
Note:
HamOnly got left out... perhaps we need a __check__ also?
Given that we would then need a __check__ and an __adapt__,
perhaps an optional argument to adapt (and __adapt__)
called checkonly=0 would be good...
> I like "adapt" better than "query".
Would "wrap" be a better name? I personally like adapt better;
since an instance of a class is *already* perfectly adapted to act on
behalf of that class. I also think that this function is a candidate
to be an operator in the
The two quotes you mention in the PEP, to me at least, boil down to:
> Quote 1: "The object may have the answer, so ask it about
> Quote 2: "The identifier may have the answer, so ask it about the object."
Yes. This is exactly it. Hope you don't mind me stealing the
quotes for in-line source code comments.
> This makes me think of adapt() as a binary op just as +, ^, divmod, etc.
It's not quite a binary operator, since the first
argument is an object, and the second.
However, in your example below, "like" returns 1/0,
I'd rather have it return the object if like is true,
and None if like is false. This allows for nesting.
like(like(obj,ClassX),ClassY)
> A word that works nicely as an operator name, especially a simple word that
> novices can grasp quickly, would be ideal as a name for this new feature.
>
> Suggestions:
>
> 1) "like"
>
> like(obj, ident, options=None)
> # future operator "like" and "not like"
> a like b # equiv to like(obj,ident)
> a not like b # equiv to not like(obj,ident,"test")
>
> 2) "is" with builtin isa() or even just adapt()
>
> a is b # adapt(a,b) as adapt written below
> a is not b # not adapt(a,b,"test")
> adapt(obj, ident, options=None)
Hmm. I actually like "adapt" better since a *new* object
can be created. With a "check_only" argument...
As for the boolean syntax:
a isa b --> adapt(a,b,check_only=true)
a isa b isa c --> adapt(adapt(a,b,check_only=true),c,check_only=true)
I don't think "adapt(a,b,check_only=false)" should
have a boolean operator syntax.
...
I'll incorporate your code below... although I now
think it should return an object, with a check-only flag.
Does this gell?
> Suggested impls for adapt:
>
> # the like version
> # ! uses builtins/ops as shorthand
>
> def like(obj, ident, options=None):
> # if same object, TRUE
> if obj is ident: return 1
> obj_type = type(obj)
> # if one is the concrete type of another,
> # or if they have the same concrete type, TRUE
> if obj_type is ident: return 1
> ident_type = type(obj)
> if obj_type is ident_type or obj is ident_type: return 1
>
> # seems that isinstance() and issubclass() can be implemented
> # here if we return true cases and let the false cases fall
> # through to the __like__ check.
>
> retval = 0
> if ident_type is ClassType:
> # isinstance
> if obj_type is InstanceType and isinstance(obj, ident): return 1
> # issubclass
> elif obj_type is ClassType and issubclass(obj, ident): return 1
>
> # __like__, if a "builtin", is actually in the type methods
> # table, not a real named attribute. obj.__like__ is just
> # the way for instances to emulate the builtin method. Right?
> # So the code below is pseudo-code for what would happen
> # at the C level.
>
> # try the obj's type first
> if obj_type.slot('__like__'):
> retval = obj_type.slot('__like__')(obj, ident)
> # else try the ident
> if ident_type.slot('__like__'):
> retval = ident_type.slot('__like__')(ident, obj)
>
> # options hook (if still needed)
> return retval
>
> And the implementations for some built-in types:
>
> InstanceType:
> if hasattr(self, "__like__"): return self.__like__(ident)
>
> All the others are wide open. If the types module gets some abstract type
> objects like Mapping or Sequence or even things like Mutable, the __like__
> slot exists to implement a type hierarchy.
>
> I considered using ClassType's slot function to implement isinstance() and
> issubclass(), or at least issubclass(). Maybe that's still a good idea.
>
> Clark, are our thoughts synchronizing in any way? How can I assist in PEP
> or implementation?
Yes. Very much so. I'd be fun to have a partner-in-crime. I put
the "provisional" PEP (as it does not have a number yet> on the
Wiki as: http://www.zope.org/Members/michel/types-sig/AdaptCheck
Clark