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:
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.
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 MailRequestOptions
s 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.
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).
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': '....', ...}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With