Noam Raphael <[EMAIL PROTECTED]> wrote: > However, I'm not sure if this solves my practical problem - testing > whether all abstract methods were implemented. I think that usually, you > can't write a test which checks whether an abstract method did what it > should have, since different implementations do different things. I
Still, there must be some _protocol_ about what those abstract methods are all about... otherwise you _are_ just talking about an *interface*, the way I use the terms (I didn't invent that usage, I just find it very useful in practice). An interface is "just syntax" -- method names and signatures; a protocol adds semantics and pragmatics (the distinction between the last two may only be meaningful to somebody with a background in linguistics, and it's not important here anyway). If some method is a hook which may do anything including nothing then it's most often more useful not to make it abstract (or absent) in the base class, but rather to define it there as a no-op. This way, you reduce the boilerplate in all subclasses: they only override the methods they NEED to override. Besides no-op, this could also apply to other semantics that were "a very likely possibility" -- but it's unusual for any given semantics to be "very likely", except "first, do no harm";-) When you design a protocol, _make it testable_. Say that you start with the idea of the protocol having two methods: def frambozzle(self): ''' must make the instance frambozzled ''' def unframbozzle(self): ''' must make the instance not-frambozzled ''' This protocol is not complete. Add a third method: def isframbozzled(self): ''' return a true value iff the instance is frambozzled ''' NOW you have a testable protocol! Sorry for the toy-ness of the example, but I don't want to post for hundreds of lines. Beyond the direct advantages of being able to write much better tests, experience teaches that a testable protocol tends to have extra resilience and usefulness for perfectly reasonable uses that the protocol designer might not have anticipated. E.g., with the testable protocol one can write def toggle_frambozzling(some_instance): if some_instance.isframbozzled(): some_instance.unframbozzle() else: some_instance.frambozzle() Note that we're only using semantics here, aka abstract semantics, not pragmatics (just to illuminate the semi-relevant distinction I was using above). Abstract semantics are those you can fully capture by axioms related to methods that are part of the protocol itself, without for example needing to refer to the external world, or other objects. You get pragmatics when such references are there, e.g., informal specs of the kind "the shipping costs of a frambozzled instance are expected to not exceed that of an unframbozzled instance, and normally to be below; on the other hand, frambozzled instances need only be marshalable on a single platform and version of Python and of this framework, while unframbozzled instances must ensure marshaling cross-platform on any version of Python >= 2.3.4 and version of this framework >= 0.8.2". In practice, only projects with relatively high formality tend to write things down to this level, of course, but generally there is some less formal pragmatic understanding of what it's all about. For example, in a recent thread about hashing &c I pointed out that adding a method such as "def __hash__(self): return 23" can ensure semantically correct hashing of any class; OTOH, it DOES badly violate pragmatics (and produces horrid performance!-), because hash values are MEANT to "differentiate" among different instances (they don't _hafta_, but they're expected to do SOME level of reasonable effort!-). > don't even know how you can test whether an abstract method was > implemented - should you run it and see if it raises a > NotImplementedError? Makes sense to me, yes. assertRaises seems good;-). > But with what arguments? If you're going to use your abstract base class for ANY purpose, you'd better have SOME idea of the signature with which you can call those methods which its subclasses must implement! Otherwise, it's all for naught: you get zero polymorphism, period. Say that subclass A has def whoa(self): ... while subclass B has def whoa(self, andmore): ... then you can NEVER call whoa without knowing which subclass you have an instance of -- no polymorphism! It's fine if a subclass wants to have a WIDER signature (add OPTIONAL args to those specified in the base class, even a *any if you wish), but there must be SOME commonality. Well then, call according to that commonality, just like any USE of you abstract base class will do. This points out that, if you wish to do checks at 'class' statement time which determine whether a class is instantiable, the signature of the methods should be part of those checks. Fortunately, module inspect makes it reasonably easy to get and check methods' signatures. > And even if you find a way > to test whether a method was implemented, I still think that the > duplication of code isn't very nice - you have both in your class > definition and in your test suite a section which says only "method > so-and-so should be implemented." Then it may be best to ONLY have the test suite -- don't put the method in the ABC at all. If you WANT the method in the ABC, for documentation purposes, well then, that's not duplication of code, it's documentation, which IS fine (just like it's quite OK to have some of the same info in a Tutorial document, in a Reference one, AND in a class's docstring!). If you don't want to have the duplication your unit tests become easier: you just getattr from the class (don't even have to bother instantiating it, ain't it great!), and check the result with inspect. > I think that making abstract methods a different object really makes > sense - they are just something else. Functions (and methods) define > what the computer should do. Abstract methods define what the > *programmer* should do. Unit tests define what the programmer should do, to a large extent, much better than other approaches do (until it comes to the integration of components, but then, abstract methods ain't much use there either; tests of other ilks may be preferable;-). ITRW (in the real world) you MAY find yourself in situations where you know unit-tests WILL be skimped on. Then you're in crisis mode from the word go, and taking what little precautions you can (including custom metaclasses that in practice won't be bypassed and can enforce a tiny sliver of sanity in what will inevitably remain a crazy environment) may be among the band-aids you can try. Besides, part of your investment in building that infrastructure does get repaid in getting some errors diagnosed earlier (though not as thoroughly and early as unit tests might, but, we're starting from the sad RW assumption that _some_ deuced coders won't do them, and we're just trying to reduce "totally dead" to "mostly dead"...;-). Alex -- http://mail.python.org/mailman/listinfo/python-list