Hi, I have just joined python-dev and I saw your very interesting proposal=20 for implementing intefaces. > I have an idea for an interface mechnism for Python, and I'd like to see if > anyone likes it before writing an actual PEP. [...] I like it a lot! Anyway, if it can be implemented in python as is, what is=20 the point of the PEP? Making the 'interface' root class and/or InterfaceError=20 builtins, maybe? I have some comments which I thought I would bounce. I'll organize these attending to the activities they relate to. Don't hesitate to tell me if I'm=20 sayig something stupid. :) Define an interface =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D In your example, it seems that class Foo(interface): def default_foo(self, a, b): "Docstring for foo method." print "Defaults can be handy." [I added the a, b arguments to illustrate one point below] Does the following:=20 - Defines the requirement that all objects that implement Foo have a foo=20 function - Defines that foo should accept arguments of the form (a, b) maybe? - Sets a doc string for the foo method. - Sets a default implementation for the method. =20 Some questions on this: - Can an interface only define method names (and maybe argument formats)?=20 I think it would be handy to let it expose attributes. - Is method names (and maybe format of arguments) the only thing you can=20 'promise' in the interface? In other words, is that the only type of=20 guarantee that code that works against the interface can get? I think a __check__(self, obj) special method in interfaces would be a simple way to boost their flexibility. - For the uses you have given to the prefix_methodname notation so far, I don't think it's really needed. Isn't the following sufficient? =20 class Foo(interface): =20 def foo(self, a, b): "foo docstring" # nothing here; no default definition =20 def bar(self, a, b): pass # no docstring, _empty definition_ =20 This has the side effect that a method with no default definition and no doc string is a SyntaxError. Is this too bad?=20 - It would maybe be hard to figure out what such a method is supposed to do, so you _should_ provide a docstring.=20 - If you're in a hurry, an empty docstring will do the trick. While in=20 'quick and dirty mode' you probably won't be using interfaces a lot,=20 anyway. Defaults look indeed useful, but the really crucial aspect to lay down=20 in an interface definition is what does it guarantee on the objects that implement it. If amalgamating this with default defs would otherwise=20 obscure it (there's another issue I'm addressing below), I think defaults=20 belong more properly to a class that implements the interface, not to the=20 interface definition itself. I guess knowing what other uses you have in mind for the prefix_methodname=20 notation could be useful to decide whether it's warranted. Check whether an object implements an interface =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D >From your examples, I get it that an object implements an interface iff it is an instance of a class that implements that interface.=20 So I guess any checking of the requirements expressed by the interface is=20 done at the time when you bind() a class to an interface. This paradigm perfectly fits static and strongly typed languages, but it falls a bit short of the flexibility of python IMO. You can do very funny=20 things to classes and objects at runtime, which can break any assumptions=20 based on object class. In your example: def foo_proc(foo_arg): foo_proxy =3D Foo(foo_arg) ... x =3D foo_proxy.foo(a, b) [added a, b again] imagine foo_proc may only really cares that foo_arg is an object that has=20 a foo() method that takes (a, b) arguments (this is all Foo guarantees). * Will the Foo() call check this, or will it just check that some class in foo_arg's bases is bound to the Foo interface?=20 In the second case, if someone has been fiddling with foo_arg or some of its base classes, foo_arg.foo() may no longer exist or it may have a different signature. =20 * Why should Foo() _always_ fail for objects that _do_ meet the requirements=20 expressed by the interface Foo but have _not_ declared that they implement=20 the interface? If the point is to avoid false positives, interfaces=20 with this concern may still make the class check: class Foo(interface): def __call__(self, obj): error =3D __check__(obj) if error: raise InterfaceError, error else: return self.proxy(obj) =20 def __check__(self, obj): if not hasattr(obj, "foo"): return "No method foo found" ... return interface.check_class(self, obj) Making such check optional allows implicit (not declared) interface=20 satisfaction for those who want it. This should extend the applicability of=20 interfaces. And this brings up another problem with defaults: they would increase false=20 positives. What if an interface wants to provide defaults for all its methods?=20 Will then any object match it? This would force additional checking.=20 Even thought this doesn't look like a big issue to me, I think it's cleanest=20 to leave validation for interfaces and implementation for classes. Declare that an object implements an interface or part of it =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D Foo.bind(SomeClass) Problems: * I agree the declaration had better be included in the class definition, at=20 least as an option.=20 * Declarative better than procedural, for this purpose. * Only classes (not instances) can declare that they implement an interface.=20 * Indexing notation to 'resolve' methods a bit counterintuitive. What about: # interfaces=20 class Foo(interface): def foo(self, a, b):=20 ... def clash1(self, x): ... def clash2(self): ... =20 =20 class Bar(interface): def bar(self, x, y): ... def clash1(self, a, s, d, f): ... def clash2(self):=20 # actually equivalent to Foo.clash2 # we should really factor this out in one interface # but imagine we can't do so for some reason... ... =20 =20 # implementations class SomeClass: # promise that all instances of SomeClass will implement Foo and Bar __implements__ =3D (Foo, Bar)=20 # automatically assumed to implement Foo.foo() =20 def foo(self, a, b):=20 ... =20 # There is not automatic name clash resolution. InterfaceError unless=20 # we resolve these explicitly =20 def fclash1(self, x): ... fclash1.__implements__ =3D Foo.clash1 # maybe we should require this=20 # to be a tuple too? =20 def bclash1(self, x): ... bclash1.__implements__ =3D Bar.clash1 =20 def clash2(self): ... clash2.__implements__ =3D (Foo.clash2, Bar.clash2) # or maybe this is=20 =09 # not really needed? =09 # seems to reflect bad=20 =09 # design anyway # 'Remove' an interface from a subclass without actually removing it from=20 # the base (or just cut the search with negative result): =20 class Child(SomeClass): __implements__ =3D (-Foo,) # will be found before the 'Foo' in = the base=20 # class # an object that is *not* an instance of a class that implements Foo wants=20 # to play the Foo obj =3D Child() obj.foo =3D lambda a, b: ... obj.clash1 =3D lambda x: ... obj.for_the_fun_of_it =3D lambda: ... obj.__implements__ =3D (Foo,) # will be found before the '-Foo' in = the # class obj.for_the_fun_of_it.__implements__ =3D Foo.clash2 # object dict will be=20 # searched first Restrict =3D=3D=3D=3D=3D=3D=3D=3D This is, to make sure that an object is only accessed in the ways=20 defined in the interface (via the proxy).=20 This should be optional too, but your syntax does this nicely; you can call Foo() as an assertion of sorts and ignore the result. Note that the __implements__ method resolution magic would require=20 that you get a proxy, though. What do you think? Esteban.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4