Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python OOP from a Java programmer's perspective

Tags:

python

oop

I only have OOP programming experience in Java and have just started working on a project in Python and I'm starting to realize Python makes me skeptical about a few things that are intuitive to me. So I have a few questions about OOP in Python.

Scenario: I'm writing a program that will send emails. For an email, the to, from, text and subject fields will be required and other fields like cc and bcc will be optional. Also, there will be a bunch of classes that will implement the core mail functionality, so they will derive from a base class (Mailer).

Following is my incomplete code snippet:

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"

Questions:

  1. The send_mail method can take multiple parameters (from, to, subject, text, cc, bcc, etc.) and only four of them are really required in my app. Since the number of parameters for the method is too high, I decided to create a wrapper object called MailRequest, which will have the four necessary parameters as properties, and all other parameters may be defined in the options dictionary. Problem is, here, just looking at the code there's no way to say that options is. Is it a dict or a list? Also, looking at the send_email method, there's also no way to tell what mailReq is. Is this bad programming practice? Should I be doing something else? Coming from the Java world, it makes me very uncomfortable to write code where you can't tell what the parameters are by just looking at the code. I got to know about annotations in Python, but I don't want to use them since they're only supported in later versions.

  2. Since the options dict should be used to specify many other properties (cc and bcc are just two of them), I've created a new class called MailRequestOptions with all of the options that may be specified inside the options dict as MailRequestOptionss static strings. Is this bad practice as well, or is there a better way to do this? This is not really Python specific, I know.

like image 550
GrowinMan Avatar asked Apr 01 '15 09:04

GrowinMan


2 Answers

Python is a "duck-typed" language; if it walks like a duck, and quacks like a duck, it's a duck! Or, in implementation terms, if the object passed as mailReq has the from, to, subject and text attributes, it doesn't really matter whether or not it's a MailRequest.

If you want to document the interface (which is certainly a good idea), it is conventional to use docstrings to do so. I like the Google style, which can be used with sphinx-napoleon to auto-generate human-readable documentation, but others are available.

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

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

    """
    ...

As to your second question; wrapping lots of arguments into a container object is a pretty common pattern. In Python, you have the option of making things a bit simpler using "**kwargs magic" (see e.g. What does ** (double star) and * (star) do for parameters?):

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

(note that from is a keyword in Python, so can't be the name of a parameter).

This has the advantage of being reasonably self-documenting - there are four required parameters, plus some arbitrary additional keyword configuration options (which, again, would normally be documented in the docstring).

like image 120
jonrsharpe Avatar answered Oct 19 '22 18:10

jonrsharpe


  1. In Python there is no need to specifically create another object. You can use a dictionary if you want to wrap the mail request: mailReq = {'from': '[email protected]', 'to': '....', ...}

  2. You should try to use *args and **kwargs for the method. It can make the options much simpler: def send_mail(from, to, subject, text, **kwargs), then other options can be retrieved using e.g. kwargs['bcc']. I believe this would be more Pythonic.

like image 24
Alex Avatar answered Oct 19 '22 17:10

Alex