I use 2 classes to manage emails:
- one
MailServerto handle the mail server itself (host, login, etc.) - one
EMailto store the parsed data from each email.
The MailServer class has a member which stores the n latest emails as a list of EMail objects. The EMail class has a reference to the parent MailServer such that we can call server operations (move, delete) within the scope of Email through proxy methods.
class EMail(object):
def delete(self):
self.mailserver.delete_email(self.uid)
def __init__(self, email_content:bytes, mailserver):
self.mailserver = mailserver
#… parse email content, set self.uid
class MailServer(object):
def delete_email(self, email_uid:int):
# Delete email from server by uid
def __init__(self, host, login, password):
# … connect, login into a server, fetch emails
self.emails = []
for email in email_queue:
self.emails.append(EMail(email_content, self))
I found this logic allows for efficient implementation, since everything is handled from the EMail instance through proxy methods that take care of dispatching the relevant data from emails (flags, IDs, etc.) when calling the underlying server operations.
But I’m a big fan of static typesetting introduced in Python 3.6 for IÂ have lost too many hours troubleshooting edge effects of implicit type casting in Python, and now they also enable auto-completion features in IDE.
So I would like to statically set the type of the mailserver:MailServer in the arguments of EMail.__init__(). But of course, it yields a NameError: name 'MailServer' is not defined if I do that.
C/C++ have a way to prototype objects ahead of declaring them, is there a similar mechanism in Python ?
>Solution :
What you have right now, from a static typing perspective, is two classes that have a strong dependency on each other. An EMail cannot exist without a MailServer and a MailServer cannot exist without knowing what an EMail is. With that tight coupling, you either need to define the classes in the same module or break the dependency.
If the classes are in the same module, then the annotations future import will save you. Assuming you’re on Python 3.7 or newer, you can put
from __future__ import annotations
at the top of your file (above all of your other imports), and then types which refer to things defined later in the file will be accepted by type checkers and the Python runtime alike.
However, this sort of tight coupling is often a sign of brittle design. So if you want to break this dependency, or if your classes are defined in different files from each other, you’ll want to look into dependency inversion. Basically, ask yourself the following two questions:
- Is there a situation in which a
MailServercould function with some other implementation of theEMailclass? - Is there a situation where this
EMailclass could work without a mail server?
Given that you’re attached to your proxy method implementation, I’m assuming the answer to (2), at least in your current coding style, is "no". However, I think you could make the MailServer type abstract.
Consider having MailServer be abstracted over the type of emails. Something like (just a mock-up, intended to be expanded for your use case),
class MessageLike(object):
...
class EMail(MessageLike):
...
_MessageType = TypeVar(bound=MessageLike)
class MailServer(Generic[_MessageType]):
emails: List[_MessageType]
class EMailServer(MailServer[EMail]):
...
Now we have the following dependencies:
EMailServerdepends onMailServerandEMailMailServerdepends on the generic typeMessageLikeEMaildepends onMessageLikeas well, and presumably depends onMailServerin your proxy methods (but not onEMailServer, it should work with anyMailServer[EMail])MessageLikedepends on none of the others
No cycles whatsoever, and our implementation is more generic.