Do you make frequent use of Abstract Base Classes (ABCs), prefer to use an ordinary super-class for the same purpose, or steer-clear? Are they more-usually employed when the project includes an extensive design stage, and the meta-class integral to some hierarchy of entities?
Previous Friday Finkings have arisen from in-house discussions within a Python dev.team which deserved a wider airing or debate. This contains (with due apologies) excessive personal reflection and comments abstracted from posts to this Discussion List. Many of us have used Python for years but never touched ABCs. Apparently then, we don't need to be aware of them! Colleagues 'here' have said "never used it" and/or "never seen the need", and similar. Often pythonista learn how to define class(es), and then to inherit or compose further classes. Everything works swimmingly. If it ain't broke, don't fix it! So, we have no reason to advance our programming capabilities/knowledge. Indeed an earlier post voiced the suggestion that we could get on quite happily without knowing about/using ABCs. Aside: Certainly the earliest iteration of Python meta-classes/ABCs (related, but confusingly different - am not going there!) 'arrived' at about the same time as did I - back at Python 1.n - but I've only ever/started using ABCs, since circa 3.5. Thus, from personal learning-experience: one can only understand and use abstract-classes after gaining a thorough understanding and facility with using (ordinary) class-es. (perhaps?) Conducting (what some people call) extensive research*, we will be told that an ABC is: "a way to overload isinstance() and issubclass()". When would we use those two functions which introspect a class-hierarchy? isinstance(object, classinfo) = is the object argument an instance of the classinfo argument issubclass(class, classinfo) = is the class argument a subclass of the classinfo argument (etc) Luv-a-duck! How does this fit with duck-typing and EAFP? (sorry, terrible attempt at a joke) Aren't we supposed to *presume* that we our sub-class/instance is just that, and we should just presume to use its data and methods (wrapped in a try...except in case 'forgiveness' might be necessary)? Perhaps with the increasing number of DataScience folk using Python (?), we do seem to be finding a reasonable proportion of Python improvements/updates seemingly heading in the LBYL direction... OK, let's go-with-the-flow. There are two 'problems' with the isinstance()/issubclass() approach: - it's not very duck-y - when it 'happens'. So, let's say we want to *execute* a particular method against the current data-structure. We can only do this if the class (or its hierarchy) includes said method. Thus: data.method( arguments... ) However, we are concerned that the instance may not be part of a hierarchy offering this method. What can we do? - EAFP: wrap in try...except - LBYL: check for method by assuring the hierarchy, eg isinstance() Each incurring the consequential-problem: but what then? The problem is not 'discovered' until execution-time - worst-case scenario: it is discovered by some user (who is no-longer our friend). If we are really going LBYL, surely we want to discover the problem as early as possible, ie preferably whilst coding+testing? Using an ABC we can do just that, because the presence/absence of methods is checked when the sub-class is defined. If it does not implement all of the required methods, the sub-class will fail. Yes, we could do that by coding a super-class with methods that contain only: raise NotImplementedError() but when would a sub-class's error in not implementing the method then be 'discovered'? Personal aside/illustration/confession: I implemented an application which used "plug-ins" - effectively filters/actions. The first couple of use-cases were easy - they were integral to the application-design. As is the way of these things, few months later a request arrived for another plug-in. Of course, I was very happy with the plug-in which I coded oh-so-quickly. Trouble is, I *forgot* to implement one of the required methods, and mis-spelled another. Yes, if I had a scrap of humility, I should have re-read my original code. Sigh! Forget the apocryphal "will your future self remember in six months' time", as I have aged (and most of us do!), I've moved my philosophy to one of trying to avoid having to remember - firstly in six *weeks'* time, and more recently: six *days'*! All was greatly improved by implementing an ABC as a guide, or a check-list, even, a template - as proven by later requests for yet more 'plug-ins'... Please remember that 'umble scribe is not an "OOP-native", starting this career way-back when mainframes were powered by dinosaurs in tread-mills. I am told this is what other languages refer to as an "interface". In some ways I visualise the ABC as a "mixin" - perhaps better-understood when the ABC does include an inheritable method. (feel free to add/discuss/correct...) Back to the "Finking": When faced with a 'green-field' project, and a dev.team sits down to create a solid overview design, I've noticed architects and experienced designers 'see' where meta-classes should be employed and specify (and are probably the ones to build), accordingly. My experience with smaller projects is that TDD directs us to develop one functional-step at a time. Once successfully tested, we re-factor and consider qualitative factors. Either the former or latter step may involve a decision to sub-class an existing structure, or similar. It is possible that such may include opportunity (even, highlight that it would be good sense) to employ an ABC and derive from there - because at this time we may better-appreciate opportunities for re-use, plus having the tests in-place reduces fear of breakage! However, as described, my usage seems to be less about data-structures and more like a check-list of functionality - which I might otherwise forget to implement in a sub-class. So, does that imply that when there is a wide and detailed design stage meta-classes may be employed 'from the word go'; whereas if we employ a more bottom-up approach, they only make an appearance as part of a 'make it better' process? Web.Refs: (warning - much of the stuff 'out there' on the web is dated and possibly even misleading to users of 'today's Python'!) https://www.python.org/dev/peps/pep-3119/ https://docs.python.org/3/library/abc.html https://docs.python.org/3/library/functions.html#isinstance https://docs.python.org/3/library/functions.html#issubclass https://idioms.thefreedictionary.com/Lord+love+a+duck! https://www.tutorialspoint.com/abstract-base-classes-in-python-abc https://devtut.github.io/python/abstract-base-classes-abc.html https://everyday.codes/python/abstract-classes-and-meta-classes-in-python/ * ie the top two 'hits' returned by DuckDuckGo - mere "research" involves only taking the first/"I feel lucky" Sample code: from abc import ABC, abstractmethod class Image( ABC ): def __init__( self, name )->None: self.name = name @abstractmethod def load( self, filename:str ): """Load image from file into working-memory.""" @abstractmethod def save( self, filename:str ): """Save edited image to file-system.""" try: i = Image() except TypeError: print( "Sorry, can't instantiate i/Image directly" ) class Non_Image( Image ): def load_file( self, filename:str ): """This looks reasonable.""" try: n = Non_Image( "Under Exposed" ) except TypeError: print( "Sorry, n/Non_Image does not look good" ) class PNG_Image( Image ): def load( self, filename:str ): """Load PNG file.""" def save( self, filename:str ): """Save to PNG file.""" p = PNG_Image( "Picture of the Artist as a Young Man" ) print( "p has instantiated successfully" ) -- Regards, =dn -- https://mail.python.org/mailman/listinfo/python-list