Frage Python OOP aus der Perspektive eines Java-Programmierers


Ich habe nur OOP-Programmiererfahrung in Java und habe gerade begonnen, an einem Projekt in Python zu arbeiten, und ich beginne zu realisieren, dass Python mich skeptisch gegenüber einigen Dingen macht, die für mich intuitiv sind. Also habe ich ein paar Fragen über OOP in Python.

Szenario: Ich schreibe ein Programm, das E-Mails sendet. Für eine E-Mail, die to, from, text und subject Felder werden benötigt und andere Felder mögen cc und bcc wird optional sein. Außerdem wird es eine Reihe von Klassen geben, die die Kern-Mail-Funktionalität implementieren, so dass sie von einer Basisklasse (Mailer).

Im Folgenden finden Sie meinen unvollständigen Codeausschnitt:

class Mailer(object):
    __metaclass__ == abc.ABCMeta

    def __init__(self,key):
        self.key = key

    @abc.abstractmethod
    def send_email(self, mailReq):
        pass


class MailGunMailer(Mailer):
    def __init__(self,key):
        super(MailGunMailer, self).__init__(key)

    def send_email(self, mailReq):
        from = mailReq.from
        to = mailReq.to
        subject= mailReq.subject
        text = mailReq.text
        options = getattr(mailReq,'options',None)
        if(options != None):
            if MailRequestOptions.BCC in options:
                #use this property
                pass
            if MailRequestOptions.CC in options:
                #use this property
                pass



class MailRequest():
    def __init__(self,from,to,subject,text):
        self.from = from
        self.to = to
        self.subject = subject
        self.text = text

    def set_options(self,options):
        self.options = options

class MailRequestOptions():
    BCC = "bcc"
    CC = "cc"

Fragen:

  1. Das send_mail Methode kann mehrere Parameter (from, to, subject, text, cc, bccusw.) und nur vier von ihnen sind wirklich in meiner App erforderlich. Da die Anzahl der Parameter für die Methode zu hoch ist, entschied ich mich für ein Wrapper-Objekt namens MailRequest, die die vier notwendigen Parameter als Eigenschaften haben, und alle anderen Parameter können in der definiert werden options Wörterbuch. Das Problem ist, hier nur den Code zu betrachten, es gibt keine Möglichkeit, das zu sagen options ist. Ist es ein dict oder ein list? Auch beim Betrachten der send_email Methode, es gibt auch keine Möglichkeit, was zu sagen mailReq ist. Ist das eine schlechte Programmierpraxis? Sollte ich etwas anderes machen? Da ich aus der Java-Welt komme, ist es für mich sehr ungemütlich, Code zu schreiben, bei dem man die Parameter nicht einfach durch einen Blick auf den Code erkennen kann. Ich habe über Anmerkungen in Python erfahren, aber ich möchte sie nicht verwenden, da sie nur in späteren Versionen unterstützt werden.

  2. Seit der options dict sollte verwendet werden, um viele andere Eigenschaften anzugeben (cc und bcc sind nur zwei von ihnen), ich habe eine neue Klasse namens erstellt MailRequestOptions mit allen Optionen, die in den Optionen angegeben werden können MailRequestOptionsS statische Zeichenfolgen. Ist das auch eine schlechte Übung, oder gibt es einen besseren Weg? Das ist nicht wirklich Python-spezifisch, das weiß ich.


5
2018-04-01 09:33


Ursprung


Antworten:


  1. In Python muss kein anderes Objekt speziell erstellt werden. Sie können ein Wörterbuch verwenden, wenn Sie die Mail-Anfrage umbrechen möchten: mailReq = {'from': 'johnsmith@british.com', 'to': '....', ...}

  2. Sie sollten versuchen zu verwenden *args und **kwargs für die Methode. Es kann die Optionen viel einfacher machen: def send_mail(from, to, subject, text, **kwargs), dann können andere Optionen unter Verwendung z.B. kwargs['bcc']. Ich glaube, das wäre mehr Pythonic.


4
2018-04-01 09:45



Python ist ein "Ente-Typ" Sprache; Wenn es wie eine Ente geht und quakt wie eine Ente, ist es eine Ente! Oder, in der Umsetzung ausgedrückt, wenn das Objekt als übergeben wurde mailReq hat die from, to, subject und text Attribute, es ist nicht wirklich wichtig ob es ist oder nicht MailRequest.

Wenn Sie die Schnittstelle dokumentieren möchten (was sicherlich eine gute Idee ist), ist es üblich zu verwenden Docstrings um es zu tun. ich mag der Google-Stil, die mit verwendet werden können sphinx-napoleon um automatisch lesbare Dokumente zu generieren, aber andere sind verfügbar.

def send_email(self, mailReq):
    """Send an email.

    Args:
      mailReq (MailRequest): the configuration for the email.

    """
    ...

Was deine zweite Frage betrifft; viele Argumente in ein Containerobjekt einzufügen ist a ziemlich allgemeines Muster. In Python haben Sie die Möglichkeit, die Dinge ein wenig einfacher zu machen "**kwargs Zauber" (siehe z.B. Was macht ** (Doppelstern) und * (Stern) für Parameter?):

def send_email(self, from_, to, subject, text, **config):
    ...
    bcc = config.get('bcc', [])  # option from config or a default
    ...

(beachten Sie, dass from ist ein Schlüsselwort in Python, also kann nicht der Name eines Parameters sein).

Dies hat den Vorteil, dass es sich in ausreichendem Maße selbst dokumentiert - es gibt vier erforderliche Parameter sowie einige beliebige zusätzliche Konfigurationsoptionen für Schlüsselwörter (die wiederum normalerweise im Docstring dokumentiert wären).


5
2018-04-01 09:41



Obwohl jonrsharpe eine ausgezeichnete Lösung geliefert hat, ist es meiner Meinung nach sinnvoll, meinen Ansatz zu erwähnen.

Wie bereits erwähnt, interessieren Sie sich in dynamischen Sprachen nicht für Typen, sondern nur für die Schnittstelle, die das Objekt hat (die sogenannte "Ente"). Dann, dein MailRequest Objekt ist eine hervorragende Möglichkeit, die logisch zusammengehörenden Argumente zu gruppieren. Es implementiert jedoch nicht alles, was es sollte. Das wäre mein Ansatz:

class MailRequest(object):
    def __init__(self, from_, to, subject, text, bcc=None, cc=None):
        # I am asuming a good default for bbc is an empty list. If none
        # is fine, just remove the None checks.
        # Dont get confused about this, it just avoids a pitfall with
        # mutable default arguments. There are other techniques however

        if bcc is None:
            bcc = []

        if cc is None:
            cc = []

        self.from_ = from_
        self.to = to
        self.subject = subject
        self.text = text
        self.bcc = bcc
        self.cc = cc

    # No options needed

Und dann die send_email Funktion würde wie folgt aussehen:

def send_email(self, mailReq):
    """
    :type mailReq: MailRequest

    """
    from_ = mailReq.from_
    to = mailReq.to
    subject= mailReq.subject
    text = mailReq.text
    bcc = mailReq.bcc
    cc = mailReq.cc

Beachten Sie, dass Sie nur die mailReq Argument, das darauf hinweist, dass jedes übergebene Objekt den MailRequest Schnittstelle (zumindest teilweise). Auf diese Weise delegieren Sie die Dokumentation von Argumenten an die MailRequest Klasse.

Ich bevorzuge diesen Ansatz gegenüber dem **kwargs magisch weil Argumente irgendwann explizit an eine starre Signatur übergeben werden, die irgendwie als Dokumentation dient. Der Nachteil ist die Ausführlichkeit.

BEARBEITEN

Wenn Sie Angst vor der Explosion der Argumente in der MailRequest "Konstruktor", die Lösung besteht darin, dasselbe eine Ebene tiefer zu machen: Gruppe erneut. Zum Beispiel möchten Sie vielleicht die Optionen in eine eigene Klasse gruppieren:

class MailRequestOpts(object):

    def __init__(self, bbc=None, cc=None, colour=None, lights='blue', blink=True):
        # ...
        self.bbc = bbc
        self.cc = cc 
        self.colour = colour
        # etc...

Und dann die MailRequestClass würde so aussehen:

class MailRequest(object):
    def __init__(self, from_, to, subject, text, options=None):
        """
        :type options: MailRequestOpts

        """
        if options is None:
            options = MailRequestOpts()

        # ...
        self.options = options

Wenn Sie am Ende 50 Argumente für einen Prozess benötigen, können Sie nicht vermeiden, dass sie irgendwann an eine oder mehrere verteilte Funktionen übergeben werden. Wie oft du sie gruppierst, hängt von dir ab und wo du das Gleichgewicht findest.


1
2018-04-01 10:43