On Sat, 2007-03-17 at 12:27 +1100, Edward d'Auvergne wrote:
The attached file is a Python module, try importing it and then playing with its contents. The module contains two singletons - 'Singleton' and 'NewSingleton'. Both are instantiated in the same manner as new relax data storage object (implementation B which we voted for), the class name being masked by the instance reference. NewSingleton is derived from the Singleton base class demonstrating inheritance. QID! These singletons are two completely different objects (in different memory space) which can be used for completely different tasks and can be used by completely different parts of the program all while being true singletons - only one instance of each will ever exist in the program. If you cannot see this, I challenge you to write a counter example module demonstrating that what this extremely basic module is doing cannot be done.
What this module is doing can be done (of course), but it breaks the Singleton design because:
isinstance(NewSingleton, Singleton.__class__)
True Contrary to your previous assertions, isinstance is aptly named, and correctly identifies the inheritance relationship between these two objects. In other words, because NewSingleton inherits from Singleton.__class__ (ie. the class, not the instance of the same name), NewSingleton is an instance of Singleton.__class__ This is fundamental to the nature of inheritance, and is the basis of our dissagreements. Again, the classic inheritance metaphor: class Apple is a subclass of the class Fruit, and GrannySmith is a subclass of the Apple. Your argument that an object cannot be an instance of more than one class flies in the face of reason - quite clearly, any object which is an instance of the class GrannySmith, is also an instance of Apple and of Fruit. This is essential to the nature of sub-classing, in both common logic and in OO programing. Perhaps it is helpful to consider the distinction between Inheritance and Delegation. Both achieve similar things in terms of code reuse and behavioural modification, but they do so in quite different ways, and have important functional differences. Given some base class with lots of methods that I want to access, while making a few changes:
class Base:
... def methodA(self): ... print "A" ... def methodB(self): ... print "B" ... # lots more methods... I could inherit from Base, and change the functionality:
class Inherited(Base):
... def methodA(self): ... print "a\n" Or I could delegate to Base those methods that I don't redefine:
class Delegated:
... def methodA(self): ... print "a\n" ... def __getattr__(self, attr): ... # If attribute lookup fails, delegate to Base ... return getattr(Base(), attr) Instances of the two classes, Inherited and Delegated, are functionally very similar: they have all of the same methods, most of which are identical to those of Base. There is one important difference:
isinstance(Inherited, Base)
True
isinstance(Delegated, Base)
False A great example of inheritance in the sense I am describing comes from the Python exceptions system. All python exceptions are an instance of the base class Exception, and from there ther exists a hierachy of exceptions: ArithmeticError is a subclass of Exception, and in turn is a base class for all of the standard maths errors (OverflowError, ZeroDivisionError, etc); likewise LookupError derives from Exception, and is base class for KeyError and IndexError. This hierachy is very powerful, because it allows us to handle different types of error in different ways:
try:
... errorProneFunction(args) ... except LookupError: ... dealWithLookupError(args) ... except ArithmeticError: ... dealWithArithmeticError(args) ... except: ... # All other errors ... dealWithOtherError(args) Suppose there is a case that I want to catch as an exception, that is maths-related. Although it is not one of the standard maths errors, I want to handle it in the same way (with the function dealWithArithmeticError() ). I simply need to subclass from ArithmeticError:
class MyMathsError(ArithmeticError):
... pass This works because an exception which is an instance of MyMathsError is also an instance of ArithmeticError, so the statement except ArithmeticError will catch MyMathsError, as well as OverflowError, ZeroDivisionError and the rest. Importantly, no amount of delegation will make this work:
class AnotherMathsError:
... def __getattr__(self, attr): ... # If attribute lookup fails, delegate to Base ... return getattr(ArithmeticError(), attr) AnotherMathsError is not caught by except ArithmeticError, because it lacks the requisite inheritance relationship - it is not an instance of ArithmeticError. So, to restate what I think is the orthodox OO understanding of inheritance: any object which is an instance of a sub-class S is also an instance of all of the classes which are base-classes of S. This is stated somewhat obtusely at http://en.wikipedia.org/wiki/Inheritance_% 28computer_science%29 in the second paragraph: "Inheritance is also sometimes called generalization, because the is-a relationships represent a hierarchy between classes of objects. For instance, a "fruit" is a generalization of "apple", "orange", "mango" and many others. One can consider fruit to be an abstraction of apple, orange, etc. Conversely, since apples are fruit (i.e. an apple is-a fruit), apples may naturally inherit all the properties common to all fruit, such as being a fleshy container for the seed of a plant." and (similarly obscurely) in almost all basic descriptions of Object Orientated coding. This is critically important, because it allows client code to use instances of the sub-class as if they were instances of the base-class, without knowing anything about the properties (or even the existance) of the sub-class. Chris