我希望在使用大量规则实现复杂类以正确实现它时防止错误。
例如,我提出了以下复杂类:
import math
sentinel = object()
class Foo( object ):
def __init__( self, a, c, d, g, b=sentinel, e=sentinel, f=sentinel, h=sentinel,
i=sentinel ):
# sentinel parameters are only needed in case other parameters have some value, and in
# some cases should be None, __init__ contains only simple logic.
...
def create_foo( a, c, d, g ):
# contains the difficult logic to create a Foo-instance correctly, e.g:
b = ( int( math.pi * 10**a ) / float(10**a) )
if c == "don't care":
e = None
f = None
elif c == 'single':
e = 3
f = None
else:
e = 6
f = 10
if g == "need I say more":
h = "ni"
i = "and now for something completely different"
elif g == "Brian":
h = "Always look at the bright side of life"
i = None
else:
h = None
i = "Always look at the bright side of death"
return Foo( a=a, b=b, c=c, d=d, e=e, f=f, g=g, h=h, i=i )
由于create_foo包含正确创建Foo实例的逻辑,我希望鼓励用户使用它。 什么是最好的pythonic方式来做到这一点。
*是的我知道我不能强迫人们使用工厂功能,因此我想“鼓励”#39;他们。 ; - )
答案 0 :(得分:2)
解决这个问题的关键是在工厂和构造函数(__new__
)中标记对象的原点,并检查初始化程序中的原点(__init__
)。如果原点是warnings.warn()
方法,则执行__new__
。您可以通过在工厂中单独调用__new__
和__init__
来标记工厂中的原点,并在两者之间标记工厂原点。
由于create_foo
是首选方法,因此将其用作默认值可能会更好。在构造函数中实现复杂的逻辑,并在用户不需要复杂逻辑的情况下,在文档中引用较轻的工厂作为替代。这避免了必须通知用户他们“做错了”的问题。
但是,假设最好将复杂逻辑保留在默认构造函数之外,那么您的问题是双重的:
您需要实现 - 并检测 - 多种创建方法。
这第一部分很简单;标准库中的许多对象都有备用构造函数,因此在Python世界中这并不罕见。您可以使用已经创建的工厂方法,但最后我建议在工作中包含工厂的原因(这是为@classmethod
创建的)。
检测原点有点困难,但并不太难。您只需在实例化时标记原点并在初始化时检查原点。通过分别致电__new__
和__init__
来完成此操作。
您想要告知用户应该做什么,但仍允许他们做他们想做的事情。
这正是创建warnings
模块的原因。
warnings
您可以使用warnings
module向用户发送消息,并仍允许他们控制消息。通过执行以下操作通知用户他们可能想要使用工厂:
import warnings
warnings.warn("It is suggested to use the Foo factory.", UserWarning)
这样,用户可以filter the warnings out if they wish。
warnings
警告快速侧边栏:请注意,一旦执行了上述warnings.warn
消息,默认情况下,在您执行warnings.resetwarnings()
或重新启动Python会话之前,它不会再次出现。
换句话说,除非您更改设置,否则用户只会在第一次创建Foo
时看到该消息。这可能是也可能不是你想要的。
使用warnings.warn
需要跟踪foo
的原始方法,并在未使用工厂时调用警告。您可以相对简单地完成以下操作。
首先,添加__new__
方法并在__init__
方法的末尾进行原点检查:
class Foo( object ):
def __new__(cls, *args, **kwargs):
inst = super(Foo, cls).__new__(cls)
# mark the instance origin:
inst.origin == cls.__new__
return inst
def __init__( self, a, c, d, g, b=sentinel, e=sentinel, f=sentinel,
h=sentinel, i=sentinel ):
#### simple logic ####
if self.origin == type(self).__new__:
warnings.warn("It is suggested to use the {} factory for \
instance creation.".format(type(self).__name__), UserWarning)
然后在工厂中,实例化并初始化新对象单独,并在两者之间设置原点:
def create_foo( a, c, d, g ):
#### complex logic #####
# instantiate using the constructor directly:
f = Foo.__new__(Foo, a, b, c, d, e, f, g, h, i )
# replace the origin with create_foo:
f.origin = create_foo
# NOW initialize manually:
f.__init__(a, b, c, d, e, f, g, h, i )
return f
现在您可以检测到来自Foo
的位置,并且如果他们没有使用工厂函数,则会向用户发出警告(默认情况下一次):
>>> Foo()
>>> __main__:8: UserWarning: It is suggested to use the Foo factory for instance creation.
Foo
子类化另一个建议调整:我会考虑将您的工厂函数作为备用构造函数添加到类中,然后(在您的用户警告中)建议用户使用该构造函数(而不是工厂函数本身)进行实例化。这将允许Foo
的子类使用工厂方法,并仍然从工厂接收子类的实例,而不是Foo
。
允许Foo
以外的课程使用工厂需要对工厂进行一些小的改动:
def create_foo( foo_cls, a, c, d, g ):
'''Contains Foo creation logic.'''
#### complex logic #####
f = foo_cls.__new__(foo_cls, a, b, c, d, e, f, g, h, i )
# replace the marked origin with create_foo
f.origin = create_foo
# now initialize
f.__init__(a, b, c, d, e, f, g, h, i )
return f
现在我们将添加备用构造函数:
class Foo( object ):
#### other methods ####
def create(cls, *args, **kwargs):
inst = create_foo(cls, *args, **kwargs)
return inst
现在我们可以:
f = Foo.create( a, c, d, g )
但我们也可以:
class Bar(Foo):
pass
b = Bar.create( a, c, d, g )
我们仍然看到这个:
>>> Bar()
>>> __main__:8: UserWarning: It is suggested to use the Bar factory for instance creation.
答案 1 :(得分:1)
您可以强烈限制操作/属性的排序。
意味着在调用Foo
方法之前阻止用户访问某些类create_foo
方法! Serializer Module
示例代码: -
class Foo(object):
def __init__(self, a, b = sentinel):
self.a = a
self._create_foo_called = False
def create_foo(self):
self.b = "complex value"
self._create_foo_called = True
def do_something(self):
if(self._create_foo_called):
# WHATEVER
else:
raise AssertionError("Please call create_foo() method")