Pythonic的方式来鼓励'使用factory-method实例化类

时间:2017-05-05 00:10:50

标签: python python-2.7

我希望在使用大量规则实现复杂类以正确实现它时防止错误。

例如,我提出了以下复杂类:

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;他们。 ; - )

2 个答案:

答案 0 :(得分:2)

TL / DR

解决这个问题的关键是在工厂和构造函数(__new__)中标记对象的原点,并检查初始化程序中的原点(__init__)。如果原点是warnings.warn()方法,则执行__new__。您可以通过在工厂中单独调用__new____init__来标记工厂中的原点,并在两者之间标记工厂原点。

序言

由于create_foo首选方法,因此将其用作默认值可能会更好。在构造函数中实现复杂的逻辑,并在用户不需要复杂逻辑的情况下,在文档中引用较轻的工厂作为替代。这避免了必须通知用户他们“做错了”的问题。

但是,假设最好将复杂逻辑保留在默认构造函数之外,那么您的问题是双重的:

  1. 您需要实现 - 并检测 - 多种创建方法。

    这第一部分很简单;标准库中的许多对象都有备用构造函数,因此在Python世界中这并不罕见。您可以使用已经创建的工厂方法,但最后我建议在工作中包含工厂的原因(这是为@classmethod创建的)。

    检测原点有点困难,但并不太难。您只需在实例化时标记原点并在初始化时检查原点。通过分别致电__new____init__来完成此操作。

  2. 您想要告知用户应该做什么,但仍允许他们做他们想做的事情。

    这正是创建warnings模块的原因。

  3. 使用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")