variable - python method constructor




O que é uma maneira limpa e python de ter múltiplos construtores em Python? (9)

Não consigo encontrar uma resposta definitiva para isso. AFAIK, você não pode ter várias funções __init__ em uma classe Python. Então, como resolvo esse problema?

Suponha que eu tenha uma classe chamada Cheese com a propriedade number_of_holes . Como posso ter duas maneiras de criar objetos de queijo ...

  1. um que leva um número de buracos como este: parmesan = Cheese(num_holes = 15)
  2. e um que não aceita argumentos e apenas randomiza a propriedade number_of_holes : gouda = Cheese()

Eu posso pensar em apenas uma maneira de fazer isso, mas isso parece meio desajeitado:

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

O que você disse? Existe outro caminho?


A melhor resposta é aquela acima sobre os argumentos padrão, mas eu me diverti escrevendo isso, e certamente se encaixa na conta de "múltiplos construtores". Use por sua conta e risco.

E quanto ao new método.

"Implementações típicas criam uma nova instância da classe invocando o método new () da superclasse usando super (currentclass, cls). New (cls [, ...]) com argumentos apropriados e modificando a instância recém-criada conforme necessário antes retornando. "

Assim, você pode fazer com que o novo método modifique sua definição de classe, anexando o método construtor apropriado.

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)

Como minha resposta inicial foi criticada com base no fato de que meus construtores de propósitos especiais não chamaram o construtor padrão (único), eu posto aqui uma versão modificada que honra os desejos que todos os construtores devem chamar de padrão:

class Cheese:
    def __init__(self, *args, _initialiser="_default_init", **kwargs):
        """A multi-initialiser.
        """
        getattr(self, _initialiser)(*args, **kwargs)

    def _default_init(self, ...):
        """A user-friendly smart or general-purpose initialiser.
        """
        ...

    def _init_parmesan(self, ...):
        """A special initialiser for Parmesan cheese.
        """
        ...

    def _init_gauda(self, ...):
        """A special initialiser for Gauda cheese.
        """
        ...

    @classmethod
    def make_parmesan(cls, *args, **kwargs):
        return cls(*args, **kwargs, _initialiser="_init_parmesan")

    @classmethod
    def make_gauda(cls, *args, **kwargs):
        return cls(*args, **kwargs, _initialiser="_init_gauda")

Essas são boas idéias para sua implementação, mas se você estiver apresentando uma interface de produção de queijos para um usuário. Eles não se importam com quantos buracos o queijo tem ou o que os internos entram para fazer o queijo. O usuário do seu código quer apenas "gouda" ou "parmesean", certo?

Então, por que não fazer isso:

# cheese_user.py
from cheeses import make_gouda, make_parmesean

gouda = make_gouda()
paremesean = make_parmesean()

E então você pode usar qualquer um dos métodos acima para realmente implementar as funções:

# 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)

Esta é uma boa técnica de encapsulamento, e eu acho que é mais Pythonic. Para mim esta maneira de fazer as coisas se encaixa mais na linha mais com a digitação de pato. Você está simplesmente pedindo um objeto gouda e não se importa realmente com a classe.


Eu usaria herança. Especialmente se houver mais diferenças do que o número de buracos. Especialmente se o Gouda precisar ter um conjunto de membros diferente do 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) 

Na verdade, None é muito melhor para valores "mágicos":

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

Agora, se você quiser total liberdade de adicionar mais parâmetros:

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())

Para explicar melhor o conceito de *args e **kwargs (você pode alterar esses nomes):

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


Por que você acha que sua solução é "desajeitada"? Pessoalmente, eu preferiria um construtor com valores padrão sobre múltiplos construtores sobrecarregados em situações como a sua (o Python não suporta a sobrecarga de métodos):

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

Para casos realmente complexos com muitos construtores diferentes, pode ser mais limpo usar funções de fábrica diferentes:

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

@classmethod
def create_cheddar(cls):
    # ...

No seu exemplo de queijo você pode querer usar uma subclasse Gouda de Cheese embora ...


Usando num_holes=None como o padrão é bom se você tiver apenas __init__ .

Se você quiser vários "construtores" independentes, pode fornecê-los como métodos de classe. Estes são geralmente chamados de métodos de fábrica. Nesse caso, você poderia ter o padrão para num_holes ser 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))

Agora crie um objeto como este:

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

Use num_holes=None como padrão, em vez disso. Em seguida, verifique se num_holes is None e, em caso afirmativo, randomize. Isso é o que eu geralmente vejo, de qualquer maneira.

Métodos de construção radicalmente diferentes podem justificar um método de classe que retorna uma instância de cls .


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