3
\$\begingroup\$

I have a function which sends an email via outlook when given text, a subject, and recipients shown below:

def __Emailer(text, subject, recipient, auto=True):
    import win32com.client as win32   

    outlook = win32.Dispatch('outlook.application')
    mail = outlook.CreateItem(0)
    if type(recipient) == type([]):
        for name in recipient:
            mail.Recipients.Add(name)
    else:
        mail.To = recipients
    mail.Subject = subject
    mail.HtmlBody = text
    if auto:
        mail.send
    else:
        mail.Display(True)

Now, I know that type comparisons in Python are strongly discouraged, but I can't think of a better way to do this. I want it to send to multiple addresses if recipient is a list using the mail.Recipients.Add(name), and mail.To if there is only one name (not given as a list). How can I accomplish this without using nasty type comparisons?

\$\endgroup\$
2
\$\begingroup\$

In Python, it is part of the mentality to check for behavior rather than for exact type.

Your method doesn't even need recipients to be a list—it can be any iterable (e.g. a set) so it can be used in the for loop. Strings, however, are also iterable, so you need to tell them apart.

One way to check if something is a string is to look for a string-specific method, such as strip.

def __Emailer(text, subject, recipients, auto=True):
    import win32com.client as win32   

    outlook = win32.Dispatch('outlook.application')
    mail = outlook.CreateItem(0)

    if hasattr(recipients, 'strip'):
        recipients = [recipients]

    for recipient in recipients:
        mail.Recipients.Add(recipient)

    mail.Subject = subject
    mail.HtmlBody = text

    if auto:
        mail.send
    else:
        mail.Display(True)
\$\endgroup\$
  • \$\begingroup\$ That's beautiful. I thought of doing something like this with try...except, but I'd actually have to execute code to do that, this is much better! \$\endgroup\$ – wnnmaw Jan 7 '14 at 19:00
  • 1
    \$\begingroup\$ mail.Recipients.Add(recipient) should fail if you try to add a list. If so, you could duck type off that behavior. This is more of design problem of the calling function or the function being called. \$\endgroup\$ – dansalmo Jan 7 '14 at 19:39
  • \$\begingroup\$ @dansalmo But why would recipient be a list? It would only be the case if you passed a nested list to recipients. \$\endgroup\$ – Dan Jan 7 '14 at 20:00
  • \$\begingroup\$ Yes, Just pass what is received, then if mail.Recipients.Add(recipient) is a list, it will fail, then you can iterate over it. I meant to comment on the original question above, where recipient might be a list or string. \$\endgroup\$ – dansalmo Jan 7 '14 at 20:05
  • \$\begingroup\$ Ah, I got it! I'm not feeling sure about duck type something outside of Python's bounds though (Add is from Outlook COM API). \$\endgroup\$ – Dan Jan 7 '14 at 20:08
2
\$\begingroup\$

Sometimes type checking makes for more readable code than duck typing, and this is probably one of those cases. What you shouldn't do is compare the types the way you are doing, it is better to use isinstance instead. You could check directly for the input being a string as:

if isinstance(recipient, str):
    mail.To = recipients
else:
    for name in recipient:
        mail.Recipients.Add(name)

You may also want to look at the ABCs (Abstract Base Classes) in the collections module, which provide a convenient way to check for certain general attributes in an object.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.