Python OOP from the perspective of a Java programmer

I have only experience with OOP programming in Java and have just started working on a project in Python, and I am starting to understand that Python is skeptical of a few things that are intuitive to me. So I have a few questions about OOP in Python.

Scenario: I am writing a program that will send emails. Email will require the to , from , text and subject fields, and other fields, such as cc and bcc , will be optional. In addition, there will be a bunch of classes that will implement the basic functionality of mail, so they will be obtained from the base class ( Mailer ).

Below is my partial 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 several parameters ( from, to, subject, text, cc, bcc , etc.), and only four of them are really necessary in my application. Since the number of parameters for the method is too large, I decided to create a wrapper object named MailRequest , which will have four necessary parameters as properties, and all other parameters can be defined in the options dictionary. The problem is that here just by looking at the code, there is no way to say that there are options . Is it a dict or list ? In addition, looking at the send_email method, there is also no way to find out what mailReq . Is this bad programming practice? Should I do something else? Coming from the Java world, it’s very inconvenient for me to write code where you can’t tell what parameters are there just by looking at the code. I found out about annotations in Python, but I don't want to use them, since they are only supported in later versions.

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

+5
source share
3 answers
  • In Python, there is no need to specifically create another object. You can use the dictionary if you want to wrap a mail request: mailReq = {'from': ' johnsmith@british.com ', 'to': '....', ...}

  • You should try using *args and **kwargs for this method. It can make the parameters much simpler: def send_mail(from, to, subject, text, **kwargs) , then other parameters can be obtained using, for example, kwargs['bcc'] . I believe it will be more Pythonic.

+4
source

Python is a duck-typed language; if he walks like a duck, and cracks like a duck, it's a duck! Or, in terms of implementation, if an object passed as mailReq has attributes from , to , subject and text , it doesn't really matter if it is MailRequest .

If you want to document an interface (which is certainly a good idea), usually use docstrings for this. I like the Google style that you can use with sphinx-napoleon to automatically generate readable documentation, but others are available.

 def send_email(self, mailReq): """Send an email. Args: mailReq (MailRequest): the configuration for the email. """ ... 

Regarding your second question; combining a large number of arguments into a container object is a fairly common pattern . In Python, you have the opportunity to make things a little easier using " **kwargs magic" (see for example, 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 it cannot be a parameter name).

The advantage of this is that it is quite self-documenting - there are four required parameters, as well as some arbitrary additional keyword settings (which, again, will be documented in docstring).

+5
source

Although jonrsharpe provided a great solution, I think it's worth mentioning my approach.

As already mentioned, in dynamic languages ​​you do not need types, namely the interface that the object has (the so-called "duck"). Then your MailRequest object is a great way to group arguments that logically belong together. This, however, is not all that is needed. This will be my approach:

 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 

And then the send_email function will look like this:

 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 

Note that you are simply documenting the mailReq argument, indicating that any object passed in should provide the MailRequest interface (at least in part). This way you delegate the documentation of the arguments to the MailRequest class.

I prefer this approach to magic **kwargs because the arguments are explicitly passed at some point to a tough signature that somehow serves as documentation. The disadvantage is verbosity.

EDIT

If you are afraid of arguments MailRequest in the MailRequest constructor, the solution should make the same level even deeper: group again. For example, you can group the parameters into your own class:

 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... 

And then MailRequestClass will look like this:

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

In the end, if you need 50 arguments for a process, you cannot skip them in one or more distributed functions at any time. How many times you group them is up to you and where you find the balance.

+1
source

Source: https://habr.com/ru/post/1216595/


All Articles