首页 文章

什么是在Python中拥有多个构造函数的干净,pythonic方式?

提问于
浏览
575

我可以__60632_在Python类中有多个 __init__ 函数 . 那么我该如何解决这个问题呢?

假设我有一个名为 Cheese 的类,其中包含 number_of_holes 属性 . 我怎样才能有两种创建奶酪对象的方法......

  • 需要像这样的一些洞: parmesan = Cheese(num_holes = 15)

  • 和一个不带参数,只是随机化 number_of_holes 属性: gouda = Cheese()

我只想到一种方法,但这似乎有点笨重:

class Cheese():
    def __init__(self, num_holes = 0):
        if (num_holes == 0):
            # randomize number_of_holes
        else:
            number_of_holes = num_holes

你说什么?还有另一种方式吗?

11 回答

  • 707

    为什么你认为你的解决方案“笨重”?就个人情况而言,我个人更喜欢一个默认值超过多个重载构造函数的构造函数(Python不支持方法重载):

    def __init__(self, num_holes=None):
        if num_holes is None:
            # Construct a gouda
        else:
            # custom cheese
        # common initialization
    

    对于具有许多不同构造函数的非常复杂的情况,使用不同的工厂函数可能更简洁:

    @classmethod
    def create_gouda(cls):
        c = Cheese()
        # ...
        return c
    
    @classmethod
    def create_cheddar(cls):
        # ...
    

    在您的奶酪示例中,您可能想要使用奶酪的Gouda子类...

  • 8

    这些是您实施的好主意,但如果您要向用户展示奶酪制作界面 . 他们并不关心奶酪有多少孔或者是什么内部制作奶酪 . 您的代码的用户只想要“gouda”或“parmesean”吗?

    那么为什么不这样做:

    # cheese_user.py
    from cheeses import make_gouda, make_parmesean
    
    gouda = make_gouda()
    paremesean = make_parmesean()
    

    然后你可以使用上面的任何方法来实际实现这些功能:

    # cheeses.py
    class Cheese(object):
        def __init__(self, *args, **kwargs):
            #args -- tuple of anonymous arguments
            #kwargs -- dictionary of named arguments
            self.num_holes = kwargs.get('num_holes',random_holes())
    
    def make_gouda():
        return Cheese()
    
    def make_paremesean():
        return Cheese(num_holes=15)
    

    这是一种很好的封装技术,我认为它更像是Pythonic . 对我来说,这种做事方式更适合鸭子打字 . 你只是要求一个gouda对象而你并不关心它是什么类 .

  • 542

    如果您将只使用 __init__ ,则使用 num_holes=None 作为默认值是正常的 .

    如果你想要多个独立的"constructors",你可以将它们作为类方法提供 . 这些通常称为工厂方法 . 在这种情况下,您可以将 num_holes 的默认值设为 0 .

    class Cheese(object):
        def __init__(self, num_holes=0):
            "defaults to a solid cheese"
            self.number_of_holes = num_holes
    
        @classmethod
        def random(cls):
            return cls(randint(0, 100))
    
        @classmethod
        def slightly_holey(cls):
            return cls(randint((0,33))
    
        @classmethod
        def very_holey(cls):
            return cls(randint(66, 100))
    

    现在创建这样的对象:

    gouda = Cheese()
    emmentaler = Cheese.random()
    leerdammer = Cheese.slightly_holey()
    
  • 17

    如果你想使用可选参数,所有这些答案都非常好,但另一种Pythonic可能是使用classmethod来生成工厂式伪构造函数:

    def __init__(self, num_holes):
    
      # do stuff with the number
    
    @classmethod
    def fromRandom(cls):
    
      return cls( # some-random-number )
    
  • 11

    而是使用 num_holes=None 作为默认值 . 然后检查是否 num_holes is None ,如果是,则随机化 . 无论如何,这就是我通常看到的 .

    更完全不同的构造方法可能需要一个返回 cls 实例的类方法 .

  • 18
    class Cheese:
        def __init__(self, *args, **kwargs):
            """A user-friendly initialiser for the general-purpose constructor.
            """
            ...
    
        def _init_parmesan(self, *args, **kwargs):
            """A special initialiser for Parmesan cheese.
            """
            ...
    
        def _init_gauda(self, *args, **kwargs):
            """A special initialiser for Gauda cheese.
            """
            ...
    
        @classmethod
        def make_parmesan(cls, *args, **kwargs):
            new = cls.__new__(cls)
            new._init_parmesan(*args, **kwargs)
            return new
    
        @classmethod
        def make_gauda(cls, *args, **kwargs):
            new = cls.__new__(cls)
            new._init_gauda(*args, **kwargs)
            return new
    
  • 10

    实际上 None 对"magic"值更好:

    class Cheese():
        def __init__(self, num_holes = None):
            if num_holes is None:
                ...
    

    现在,如果您想完全自由地添加更多参数:

    class Cheese():
        def __init__(self, *args, **kwargs):
            #args -- tuple of anonymous arguments
            #kwargs -- dictionary of named arguments
            self.num_holes = kwargs.get('num_holes',random_holes())
    

    为了更好地解释 *args**kwargs 的概念(您实际上可以更改这些名称):

    def f(*args, **kwargs):
       print 'args: ', args, ' kwargs: ', kwargs
    
    >>> f('a')
    args:  ('a',)  kwargs:  {}
    >>> f(ar='a')
    args:  ()  kwargs:  {'ar': 'a'}
    >>> f(1,2,param=3)
    args:  (1, 2)  kwargs:  {'param': 3}
    

    http://docs.python.org/reference/expressions.html#calls

  • 20

    最好的答案是关于默认参数的上面的答案,但我很乐意写这个,它确实符合“多个构造函数”的账单 . 使用风险由您自己承担 .

    怎么样new方法 .

    “典型实现通过使用super(currentclass,cls). new (cls [,...])调用超类的 new ()方法并使用适当的参数创建类的新实例,然后根据需要修改新创建的实例 . 归还它 . “

    因此,您可以通过附加适当的构造函数方法让 new 方法修改类定义 .

    class Cheese(object):
        def __new__(cls, *args, **kwargs):
    
            obj = super(Cheese, cls).__new__(cls)
            num_holes = kwargs.get('num_holes', random_holes())
    
            if num_holes == 0:
                cls.__init__ = cls.foomethod
            else:
                cls.__init__ = cls.barmethod
    
            return obj
    
        def foomethod(self, *args, **kwargs):
            print "foomethod called as __init__ for Cheese"
    
        def barmethod(self, *args, **kwargs):
            print "barmethod called as __init__ for Cheese"
    
    if __name__ == "__main__":
        parm = Cheese(num_holes=5)
    
  • 0

    这就是我为必须创建的 YearQuarter 类解决它的方法 . 我用一个名为 value 的参数创建了 __init__ . __init__ 的代码只决定 value 参数的类型并相应地处理数据 . 如果你想要多个输入参数,你只需将它们打包成一个元组并测试 value 是一个元组 .

    你这样使用它:

    >>> temp = YearQuarter(datetime.date(2017, 1, 18))
    >>> print temp
    2017-Q1
    >>> temp = YearQuarter((2017, 1))
    >>> print temp
    2017-Q1
    

    这就是 __init__ 和班上其他人的样子:

    import datetime
    
    
    class YearQuarter:
    
        def __init__(self, value):
            if type(value) is datetime.date:
                self._year = value.year
                self._quarter = (value.month + 2) / 3
            elif type(value) is tuple:               
                self._year = int(value[0])
                self._quarter = int(value[1])           
    
        def __str__(self):
            return '{0}-Q{1}'.format(self._year, self._quarter)
    

    当然,您可以使用多个错误消息展开 __init__ . 我在这个例子中省略了它们 .

  • 2

    人们肯定更喜欢已发布的解决方案,但由于还没有人提到这个解决方案,我认为值得一提的是完整性 .

    可以修改 @classmethod 方法以提供不调用默认构造函数( __init__ )的替代构造函数 . 而是使用 __new__ 创建实例 .

    如果无法根据构造函数参数的类型选择初始化类型,并且构造函数不共享代码,则可以使用此方法 .

    例:

    class MyClass(set):
    
        def __init__(self, filename):
            self._value = load_from_file(filename)
    
        @classmethod
        def from_somewhere(cls, somename):
            obj = cls.__new__(cls)  # Does not call __init__
            obj._value = load_from_somewhere(somename)
            return obj
    
  • 0

    我会使用继承 . 特别是如果存在比孔数更多的差异 . 特别是如果Gouda需要有不同的成员,那么Parmesan .

    class Gouda(Cheese):
        def __init__(self):
            super(Gouda).__init__(num_holes=10)
    
    
    class Parmesan(Cheese):
        def __init__(self):
            super(Parmesan).__init__(num_holes=15)
    

相关问题