我只有Java的OOP编程经验,刚刚开始使用Python开发项目,我开始意识到Python让我对一些对我来说很直观的事情持怀疑态度。所以我在Python中有一些关于OOP的问题。
场景:我正在编写一个可以发送电子邮件的程序。对于电子邮件,我们需要to
,from
,text
和subject
字段,而cc
和bcc
等其他字段是可选的。此外,将有一堆类将实现核心邮件功能,因此它们将派生自基类(Mailer
)。
以下是我的不完整代码段:
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"
问题:
send_mail
方法可以使用多个参数(from, to, subject, text, cc, bcc
等),在我的应用中只需要其中四个参数。由于该方法的参数数量太多,我决定创建一个名为MailRequest
的包装器对象,它将具有四个必要参数作为属性,所有其他参数可以在options
中定义字典。问题是,在这里,只看代码,没有办法说options
。是dict
还是list
?另外,查看send_email
方法,也无法确定mailReq
是什么。 这是不好的编程习惯吗?我应该做些什么吗?来自Java世界,编写代码让我感到非常不舒服,只需查看代码就无法知道参数是什么。我在Python中了解了注释,但我不想使用它们,因为它们仅在以后的版本中得到支持。
由于options
dict应该用于指定许多其他属性(cc
和bcc
只是其中两个),我创建了一个名为{的新类{1}}可以在选项dict中指定的所有选项作为MailRequestOptions
的静态字符串。 这也是一种不好的做法,还是有更好的方法可以做到这一点?我知道这不是特定的Python。
答案 0 :(得分:5)
Python是一种"duck-typed"语言;如果它像鸭子一样走路,像鸭子一样嘎嘎叫,它就是一只鸭子!或者,在实施术语中,如果作为mailReq
传递的对象具有from
,to
,subject
和text
属性,则不会真的很重要是否是MailRequest
。
如果你想记录界面(这当然是一个好主意),通常使用docstrings这样做。我喜欢the Google style,可以与sphinx-napoleon
一起使用来自动生成人类可读的文档,但其他文档可用。
def send_email(self, mailReq):
"""Send an email.
Args:
mailReq (MailRequest): the configuration for the email.
"""
...
关于你的第二个问题;将大量参数包装到容器对象中是pretty common pattern。在Python中,您可以选择使用“**kwargs
magic”(参见例如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
...
(请注意,from
是Python中的关键字,因此不能是参数的名称。)
这样做的优点是可以合理地自我记录 - 有四个必需的参数,以及一些任意的附加关键字配置选项(通常也会在文档字符串中记录)。
答案 1 :(得分:4)
在Python中,没有必要专门创建另一个对象。如果要包装邮件请求,可以使用字典:mailReq = {'from': 'johnsmith@british.com', 'to': '....', ...}
您应该尝试使用*args
和**kwargs
作为方法。它可以使选项更加简单:def send_mail(from, to, subject, text, **kwargs)
,然后可以使用例如kwargs['bcc']
来检索其他选项。 {{1}}。我相信这会更像Pythonic。
答案 2 :(得分:1)
虽然jonrsharpe提供了一个很好的解决方案,但我认为值得一提的是我的方法。
如前所述,在动态语言中,您不关心类型,只关心对象具有的接口(所谓的“鸭子”)。然后,您的MailRequest
对象是对逻辑上属于一起的参数进行分组的绝佳方法。然而,它没有实现应有的一切。这将是我的方法:
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
然后send_email
函数如下所示:
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
请注意,您只需记录mailReq
参数,指出传递的任何对象都应提供MailRequest
接口(至少部分)。这样,您可以将参数文档委托给MailRequest
类。
我比**kwargs
魔法更喜欢这种方法,因为参数在某些时候被明确地传递给了一个刚性签名,它以某种方式用作文档。缺点是冗长。
修改强>
如果你担心MailRequest
“构造函数中的参数爆炸”,解决方案是更深入地进行同一级别:再次组合。例如,您可能希望将选项分组到自己的类中:
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...
然后MailRequestClass
看起来像这样:
class MailRequest(object):
def __init__(self, from_, to, subject, text, options=None):
"""
:type options: MailRequestOpts
"""
if options is None:
options = MailRequestOpts()
# ...
self.options = options
如果你需要一个进程的50个参数,那么你无法避免在某些时候将所有参数传递给一个或多个分布式函数。您对它们进行分组的次数取决于您和您找到平衡点的位置。