python教学 - python类方法




在Python中有多个构造函数的干净,pythonic方法是什么? (8)

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

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

我无法为此找到明确的答案。 AFAIK,在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

你说什么? 有另一种方法吗?


一个人应该更喜欢已经发布的解决方案,但由于没有人提到这个解决方案,我认为这是值得一提的完整性。

可以修改@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

其实None什么比“魔术”价值更好:

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


如果你只有__init__使用num_holes=None作为默认是好的。

如果你想要多个独立的“构造函数”,你可以将它们作为类方法提供。 这些通常称为工厂方法。 在这种情况下,您可以将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()

我会使用继承。 特别是如果将会有更多的差异比洞的数量。 特别是如果高达将需要有不同的成员,然后巴马。

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


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

最好的答案是关于默认参数的上面的答案,但我很乐意写这个,它确实符合“多个构造函数”的法案。 使用风险自负。

那么new方法呢?

“典型的实现通过使用super(currentclass,cls).new(cls [,...])调用超类的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)

这就是我为了创建一个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__ 。 我省略了这个例子。


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




constructor