[Tutor] method, type?

Steven D'Aprano steve at pearwood.info
Wed Jan 6 19:31:31 EST 2016


On Wed, Jan 06, 2016 at 06:57:31PM +0000, Alan Gauld wrote:

> On 06/01/16 14:46, Steven D'Aprano wrote:
> > I don't understand what you mean by "Python doesn't support named 
> > constructors". It seems to me that this is the *only* sort of 
> > constructor that Python supports.
> 
> No, Python constructors have names(new/init) but they are not
> used explicitly (ie. by the client code)to create objects.

The default new/init constructor isn't, but any additional ones are. The 
classic example is dict.fromkeys. Other examples include:

    Decimal.from_float 
    fractions.from_float
    fractions.from_decimal
    datetime.fromordinal
    datetime.fromtimestamp



> By contrast languages like Delphi, Smalltalk, Objective C,
> some Lisps and, I think, Eiffel (and others?) all require an
> explicit call of a constructor method 

The fact that in Python the default constructor is called via the class 
name is, in my opinion, just a cosmetic difference: MyClass(x) versus 
MyClass.new(arg) is not that big a difference. It just means that 
one name is implicit.

Contrast that to the C++ style where the compiler can do multiple 
dispatch by the number and type of arguments:

# Using Python syntax again
class Spam:
    # four constructors
    def Spam(afloat): ...
    def Spam(astring): ...
    def Spam(inta): ...
    def Spam(inta, intb): ...

compared to the Python/Smalltalk/Delphi etc style where they all have to 
be named differently:

class Spam:
    def __new__(cls, afloat): ...
    def from_word(cls, astring): ...
    def from_count(cls, inta): ...
    def from_ratio(cls, inta, intb): ...


But if you want to insist that the default Python constructor of __new__ 
plus __init__ doesn't qualify as a "named constructor" because it is 
called implicitly rather than explicitly, I won't argue. I'll just say 
that Python has named constructors for the *alternate* (non-default) 
constructors :-)



> and there may be
> multiple constructors per class each with different names
> (usually describing how the construction occurs or the
> nature of the object constructed).

Exactly like in Python :-)


> > So the only way to have two different constructors is to give them 
> > different names.
> > 
> > Hence all constructors in Python are "named constructors".
> 
> But they are not native constructors such as those used in
> Smalltalk etc. They have to be factory methods that call new/init
> under the covers.

No they don't. It is *most common* for alternate constructors to hand 
over the actual work of building the instance to __new__, because DRY. 
Hence:

class Spam(object):
    def __new__(cls, alist):
        # do the real work here

    @classmethod
    def from_string(cls, astring):
        alist = astring.split()
        return cls(alist)


But that's not a hard rule. Any method can manually create an instance, 
well, any *new-style class* method. (Classic classes from Python 2 are 
different, because they can't have constructors except for the default.)

class Eggs(object):
    @classmethod
    def this_is_my_constructor(cls):
        instance = object.__new__(cls)
        instance.initiate_me()
        return instance
    def initiate_me(self):
        print("Initiating...")
        self.style = "hard boiled"
    # And just to prove that the default constructor isn't used:
    def __new__(cls):
        raise TypeError


e = Eggs.this_is_my_constructor()


So while you are right that most alternate constructors end up calling 
the default __new__, that's not compulsory. It's just convenient.


> > ... ensures that if you subclass the class, you automatically
> > get a valid constructor as well.
> 
> Wouldn't it return an instance of the superclass rather than
> the sub class? You'd need to override it wouldn't you?

Not if you write it correctly :-)

If you hard-code the name of the superclass into the method, then you 
will always get the superclass. That is bad.

# Don't do this, this is wrong.
class Spam(object):
    @classmethod
    def from_eggs(cls, eggs):
        args = process(eggs)
        return Spam(args)


# Do this instead.
class Spam(object):
    @classmethod
    def from_eggs(cls, eggs):
        args = process(eggs)
        return cls(args)




-- 
Steve


More information about the Tutor mailing list