I use 2 classes to manage emails:
- one
MailServer
to handle the mail server itself (host, login, etc.) - one
EMail
to 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 ?
CodePudding user response:
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
MailServer
could function with some other implementation of theEMail
class? - Is there a situation where this
EMail
class 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:
EMailServer
depends onMailServer
andEMail
MailServer
depends on the generic typeMessageLike
EMail
depends onMessageLike
as well, and presumably depends onMailServer
in your proxy methods (but not onEMailServer
, it should work with anyMailServer[EMail]
)MessageLike
depends on none of the others
No cycles whatsoever, and our implementation is more generic.