fabrieksklasse, verkeerd aantal argumenten wordt doorgegeven aan constructor subklasse

Ik keek naar Python: uitzondering in de afzonderlijke module werkt fout waarbij een multi-purpose GnuLibError-klasse wordt gebruikt om 'in te staan' voor een verscheidenheid van verschillende fouten. Elke sub-fout heeft zijn eigen ID-nummer en string voor foutopmaak.

Ik dacht dat het beter zou zijn geschreven als een hiërarchie van uitzonderingsklassen, en was van plan dat te doen:

class GNULibError(Exception):
    sub_exceptions = 0  # patched with dict of subclasses once subclasses are created
    err_num = 0
    err_format = None

    def __new__(cls, *args):
        print("new {}".format(cls)) # DEBUG
        if len(args) and args[0] in GNULibError.sub_exceptions:
            print("  factory -> {} {}".format(GNULibError.sub_exceptions[args[0]], args[1:])) # DEBUG
            return super(GNULibError, cls).__new__(GNULibError.sub_exceptions[args[0]], *(args[1:]))
        else:
            print("  plain {} {}".format(cls, args)) # DEBUG
            return super(GNULibError, cls).__new__(cls, *args)

    def __init__(self, *args):
        cls = type(self)
        print("init {} {}".format(cls, args)) # DEBUG
        self.args = args
        if cls.err_format is None:
            self.message = str(args)
        else:
            self.message = "[GNU Error {}] ".format(cls.err_num) + cls.err_format.format(*args)

    def __str__(self):
        return self.message

    def __repr__(self):
        return '{}{}'.format(type(self).__name__, self.args)

class GNULibError_Directory(GNULibError):
    err_num = 1
    err_format = "destination directory does not exist: {}"

class GNULibError_Config(GNULibError):
    err_num = 2
    err_format = "configure file does not exist: {}"

class GNULibError_Module(GNULibError):
    err_num = 3
    err_format = "selected module does not exist: {}"

class GNULibError_Cache(GNULibError):
    err_num = 4
    err_format = "{} is expected to contain gl_M4_BASE({})"

class GNULibError_Sourcebase(GNULibError):
    err_num = 5
    err_format = "missing sourcebase argument: {}"

class GNULibError_Docbase(GNULibError):
    err_num = 6
    err_format = "missing docbase argument: {}"

class GNULibError_Testbase(GNULibError):
    err_num = 7
    err_format = "missing testsbase argument: {}"

class GNULibError_Libname(GNULibError):
    err_num = 8
    err_format = "missing libname argument: {}"

# patch master class with subclass reference
# (TO DO: auto-detect all available subclasses instead of hardcoding them)
GNULibError.sub_exceptions = {
    1: GNULibError_Directory,
    2: GNULibError_Config,
    3: GNULibError_Module,
    4: GNULibError_Cache,
    5: GNULibError_Sourcebase,
    6: GNULibError_Docbase,
    7: GNULibError_Testbase,
    8: GNULibError_Libname
}

Dit begint met GNULibError als een fabrieksklasse - als u het een foutnummer noemt dat bij een erkende subklasse hoort, retourneert het een object dat tot die subklasse behoort, anders wordt het als een standaardfouttype geretourneerd.

Op basis van deze code moet het volgende exact gelijk zijn (maar zijn dat niet):

e = GNULibError(3, 'missing.lib')
f = GNULibError_Module('missing.lib')

print e  # -> '[GNU Error 3] selected module does not exist: 3'
print f  # -> '[GNU Error 3] selected module does not exist: missing.lib'

Ik heb enkele strategische afdrukinstructies toegevoegd en de fout lijkt te zijn in GNULibError .__ nieuw __ :

>>> e = GNULibError(3, 'missing.lib')

new 
  factory ->  ('missing.lib',)  # good...
init  (3, 'missing.lib')        # NO!
                                            ^
                                           why?

Ik noem de subklasse-constructor als subklasse .__ nieuw __ (* args [1:]) - dit zou de 3, het subklasse-type ID moeten laten vallen - en toch krijgt zijn __ init __ nog steeds de 3 hoe dan ook! Hoe kan ik de argumentenlijst bijsnijden die wordt doorgegeven aan de -subklasse .__ init __ ?

1

3 antwoord

U kunt niet beïnvloeden wat wordt doorgegeven aan __ init __ , zolang u het doet met een "fabrieksklasse" zoals u nu hebt dat subklassen van zichzelf retourneert. De reden dat het argument "3" nog steeds wordt doorgegeven, is omdat u nog steeds een exemplaar van GNULibError retourneert uit __ nieuw __ . Tegen de tijd dat __ nieuwe __ wordt aangeroepen, is het te laat om te beslissen wat wordt doorgegeven aan __ init __ . Zoals vermeld in de documentatie (nadruk toegevoegd):

Als __ nieuw __ () een instantie van cls retourneert, wordt de methode __ init __ () van de nieuwe instantie aangeroepen, zoals __ init __ (zelf [ ... ]) , waarbij self de nieuwe instantie is en de overige argumenten dezelfde zijn als die zijn doorgegeven aan __ nieuwe __() .

Met andere woorden, wanneer u GNULibError (3, 'missing.lib') aanroept, is het te laat --- door de klasse te bellen met die argumenten, hebt u ervoor gezorgd dat dit de argumenten zijn die worden doorgegeven naar __ init __ . __ nieuwe __ kan een ander exemplaar retourneren dan dat u anders zou krijgen, maar het kan niet voorkomen dat de normale initialisatie gebeurt.

Zoals voorgesteld door @Ned Batchelder, kunt u beter een fabrieksfunctie gebruiken in plaats van een "fabrieksklasse", omdat een functie niet deze __ nieuwe __ / __ init __ machine heeft en je kunt gewoon een instantie van de gewenste klasse retourneren.

1
toegevoegd

Dit is veel ingewikkelder dan het zou moeten zijn. Probeer geen klasse te maken objecten van een andere klasse maken. Schrijf een fabrieksfunctie om uw uitzonderingen te maken en maak geen rommelen met __ nieuwe __ . Het is te lastig, zoals je ontdekt.

0
toegevoegd

In uw use case - ik ben het eens met Ned - Meer complex dat het moet zijn.

Je zou iets kunnen proberen zoals (gebaseerd op het feit dat je afgeleide klassen niets lijken te doen maar verschillen van foutmelding).

class GNULibError(Exception):
    pass # put logic code here

GNULibErrors = {
    1: type('GNULibError_Directory', (GNULibError,), {'message': 'suitable whatever here'})
}

En pas vanaf daar aan ...

0
toegevoegd