Python 从 Java 程序员的角度看 OOP
Python OOP from a Java programmer's perspective
我只有 Java 的 OOP 编程经验,并且刚刚开始在 Python 从事一个项目,我开始意识到 Python 让我对一些事情持怀疑态度这对我来说很直观。所以我在 Python.
中有几个关于 OOP 的问题
场景:我正在编写一个发送电子邮件的程序。对于电子邮件,to
、from
、text
和 subject
字段将是必需的,其他字段如 cc
和 bcc
是可选的.此外,将有一堆 classes 将实现核心邮件功能,因此它们将派生自基础 class (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
只是其中的两个),我创建了一个新的 class 称为 MailRequestOptions
,所有选项都可以在选项字典中指定为 MailRequestOptions
的静态字符串。 这也是不好的做法吗,或者有更好的方法吗?我知道这不是 Python 具体的。
Python 是一种 "duck-typed" 语言;如果它走路像鸭子,叫起来像鸭子,那它就是鸭子!或者,在实现方面,如果作为 mailReq
传递的对象具有 from
、to
、subject
和 text
属性, 它不会是不是 MailRequest
真的很重要。
如果你想记录界面(这当然是个好主意),通常使用 docstrings to do so. I like the Google style,它可以与 sphinx-napoleon
一起使用来自动生成人类可读的文档, 但其他可用。
def send_email(self, mailReq):
"""Send an email.
Args:
mailReq (MailRequest): the configuration for the email.
"""
...
关于你的第二个问题;将大量参数包装到容器对象中是 pretty common pattern. In Python, you have the option of making things a bit simpler using "**kwargs
magic" (see e.g. 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中的关键字,不能作为参数名)
这具有合理的自我记录的优势 - 有四个必需的参数,加上一些任意的附加关键字配置选项(同样,通常会在文档字符串中记录)。
在Python中不需要专门创建另一个对象。如果要包装邮件请求,可以使用字典:mailReq = {'from': 'johnsmith@british.com', 'to': '....', ...}
您应该尝试使用 *args
和 **kwargs
作为方法。它可以使选项更简单:def send_mail(from, to, subject, text, **kwargs)
,然后可以使用例如检索其他选项kwargs['bcc']
。我相信这样会更 Pythonic.
虽然 jonrsharpe 提供了一个很好的解决方案,但我认为值得一提的是我的方法。
如前所述,在动态语言中您不关心类型,只关心对象具有的接口(所谓的 "duck")。然后,您的 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
class.
我更喜欢这种方法而不是 **kwargs
魔法,因为参数在某些时候被显式传递给严格的签名,它以某种方式用作文档。缺点是冗长。
编辑
如果您担心 MailRequest
"constructor" 中的争论激增,解决方案是更深入地执行相同的操作:再次分组。例如,您可能希望将选项分组到它自己的 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...
然后 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 个参数,则无法避免在某个时候将它们全部传递给一个或多个分布式函数。将它们分组多少次取决于您以及您在哪里找到平衡点。
我只有 Java 的 OOP 编程经验,并且刚刚开始在 Python 从事一个项目,我开始意识到 Python 让我对一些事情持怀疑态度这对我来说很直观。所以我在 Python.
中有几个关于 OOP 的问题场景:我正在编写一个发送电子邮件的程序。对于电子邮件,to
、from
、text
和 subject
字段将是必需的,其他字段如 cc
和 bcc
是可选的.此外,将有一堆 classes 将实现核心邮件功能,因此它们将派生自基础 class (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
只是其中的两个),我创建了一个新的 class 称为MailRequestOptions
,所有选项都可以在选项字典中指定为MailRequestOptions
的静态字符串。 这也是不好的做法吗,或者有更好的方法吗?我知道这不是 Python 具体的。
Python 是一种 "duck-typed" 语言;如果它走路像鸭子,叫起来像鸭子,那它就是鸭子!或者,在实现方面,如果作为 mailReq
传递的对象具有 from
、to
、subject
和 text
属性, 它不会是不是 MailRequest
真的很重要。
如果你想记录界面(这当然是个好主意),通常使用 docstrings to do so. I like the Google style,它可以与 sphinx-napoleon
一起使用来自动生成人类可读的文档, 但其他可用。
def send_email(self, mailReq):
"""Send an email.
Args:
mailReq (MailRequest): the configuration for the email.
"""
...
关于你的第二个问题;将大量参数包装到容器对象中是 pretty common pattern. In Python, you have the option of making things a bit simpler using "**kwargs
magic" (see e.g. 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中的关键字,不能作为参数名)
这具有合理的自我记录的优势 - 有四个必需的参数,加上一些任意的附加关键字配置选项(同样,通常会在文档字符串中记录)。
在Python中不需要专门创建另一个对象。如果要包装邮件请求,可以使用字典:
mailReq = {'from': 'johnsmith@british.com', 'to': '....', ...}
您应该尝试使用
*args
和**kwargs
作为方法。它可以使选项更简单:def send_mail(from, to, subject, text, **kwargs)
,然后可以使用例如检索其他选项kwargs['bcc']
。我相信这样会更 Pythonic.
虽然 jonrsharpe 提供了一个很好的解决方案,但我认为值得一提的是我的方法。
如前所述,在动态语言中您不关心类型,只关心对象具有的接口(所谓的 "duck")。然后,您的 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
class.
我更喜欢这种方法而不是 **kwargs
魔法,因为参数在某些时候被显式传递给严格的签名,它以某种方式用作文档。缺点是冗长。
编辑
如果您担心 MailRequest
"constructor" 中的争论激增,解决方案是更深入地执行相同的操作:再次分组。例如,您可能希望将选项分组到它自己的 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...
然后 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 个参数,则无法避免在某个时候将它们全部传递给一个或多个分布式函数。将它们分组多少次取决于您以及您在哪里找到平衡点。