Qual é o objetivo dos métodos de classe?


250

Estou me ensinando Python e minha lição mais recente foi que Python não é Java e, portanto, passei um tempo transformando todos os meus métodos de classe em funções.

Agora percebo que não preciso usar métodos de classe para o que faria com staticmétodos em Java, mas agora não tenho certeza de quando os usaria. Todo o conselho que posso encontrar sobre os métodos da classe Python é da mesma maneira que novatos como eu devem ficar longe deles, e a documentação padrão é a mais opaca quando os discutimos.

Alguém tem um bom exemplo de uso de um método Class em Python ou pelo menos alguém pode me dizer quando os métodos Class podem ser sensatamente usados?

Respostas:


178

Os métodos de classe são para quando você precisa ter métodos que não são específicos para nenhuma instância específica, mas ainda envolvem a classe de alguma forma. O mais interessante sobre eles é que eles podem ser substituídos por subclasses, algo que simplesmente não é possível nos métodos estáticos de Java ou nas funções no nível de módulo do Python.

Se você possui uma classe MyClasse uma função no nível do módulo que opera no MyClass (fábrica, esboço de injeção de dependência, etc.), faça a classmethod. Em seguida, estará disponível para subclasses.


Entendo. Mas, e se eu apenas quiser categorizar um método, que não precise do MyClass, mas ainda faça sentido se agrupar em outro MyClass? Eu posso pensar em movê-lo para o módulo auxiliar, embora ..
swdev 11/11

Eu tenho uma pergunta relacionada à original, talvez você possa responder ao mesmo tempo? Qual a maneira de chamar métodos de classe de um objeto é "melhor" ou "mais idiomático": obj.cls_mthd(...)ou type(obj).cls_mthd(...)?
Alexey

63

Métodos de fábrica (construtores alternativos) são de fato um exemplo clássico de métodos de classe.

Basicamente, os métodos de classe são adequados sempre que você deseja ter um método que naturalmente se encaixe no espaço de nomes da classe, mas não está associado a uma instância específica da classe.

Como exemplo, no excelente módulo unipath :

Diretório atual

  • Path.cwd()
    • Retorna o diretório atual real; por exemplo Path("/tmp/my_temp_dir"),. Este é um método de classe.
  • .chdir()
    • Torne-se o diretório atual.

Como o diretório atual é amplo para o processo, o cwdmétodo não possui uma instância específica à qual deve ser associado. No entanto, alterar o cwddiretório de uma determinada Pathinstância deve realmente ser um método de instância.

Hmmm ... como Path.cwd()realmente retorna uma Pathinstância, acho que poderia ser considerado um método de fábrica ...


1
Sim ... métodos de fábrica foram a primeira coisa que me veio à mente. Também nesse sentido são coisas que implementam um padrão Singleton, como: getAndOptionallyCreateSomeSingleInstanceOfSomeResource ()
Jemenake

3
Em python, esses são métodos estáticos, não métodos de classe.
21419 Jaykul

46

Pense desta maneira: os métodos normais são úteis para ocultar os detalhes da expedição: você pode digitar myobj.foo()sem se preocupar se o foo()método é implementado pela myobjclasse do objeto ou por uma de suas classes pai. Os métodos de classe são exatamente análogos a isso, mas com o objeto de classe: eles permitem que você chame MyClass.foo()sem ter que se preocupar se foo()é implementado especialmente MyClassporque precisava de sua própria versão especializada ou se está permitindo que sua classe pai lide com a chamada.

Os métodos de classe são essenciais quando você está configurando ou computando antes da criação de uma instância real, porque até que a instância exista, você obviamente não poderá usar a instância como o ponto de despacho para as chamadas do método. Um bom exemplo pode ser visualizado no código-fonte SQLAlchemy; dê uma olhada no dbapi()método de classe no seguinte link:

https://github.com/zzzeek/sqlalchemy/blob/ab6946769742602e40fb9ed9dde5f642885d1906/lib/sqlalchemy/dialects/mssql/pymssql.py#L47

Você pode ver que o dbapi()método que um back-end do banco de dados usa para importar a biblioteca de banco de dados específica do fornecedor necessária sob demanda, é um método de classe porque ele precisa ser executado antes que as instâncias de uma determinada conexão com o banco de dados sejam criadas - mas não pode seja uma função simples ou estática, porque eles desejam chamar outros métodos de suporte que precisem ser escritos de maneira mais específica nas subclasses do que na classe pai. E se você despacha para uma função ou classe estática, "esquece" e perde o conhecimento sobre qual classe está inicializando.


Link quebrado, por favor ajuste
piertoni

28

Recentemente, eu queria uma classe de log muito leve que produzisse quantidades variáveis ​​de saída, dependendo do nível de log que poderia ser definido programaticamente. Mas eu não queria instanciar a classe toda vez que desejava gerar uma mensagem de depuração, erro ou aviso. Mas eu também queria encapsular o funcionamento desse recurso de registro e torná-lo reutilizável sem a declaração de quaisquer globais.

Então, usei variáveis ​​de classe e o @classmethoddecorador para conseguir isso.

Com minha classe simples de Log, eu poderia fazer o seguinte:

Logger._level = Logger.DEBUG

Então, no meu código, se eu quisesse cuspir um monte de informações de depuração, simplesmente precisava codificar

Logger.debug( "this is some annoying message I only want to see while debugging" )

Erros podem ser colocados com

Logger.error( "Wow, something really awful happened." )

No ambiente de "produção", posso especificar

Logger._level = Logger.ERROR

e agora, apenas a mensagem de erro será exibida. A mensagem de depuração não será impressa.

Aqui está a minha turma:

class Logger :
    ''' Handles logging of debugging and error messages. '''

    DEBUG = 5
    INFO  = 4
    WARN  = 3
    ERROR = 2
    FATAL = 1
    _level = DEBUG

    def __init__( self ) :
        Logger._level = Logger.DEBUG

    @classmethod
    def isLevel( cls, level ) :
        return cls._level >= level

    @classmethod
    def debug( cls, message ) :
        if cls.isLevel( Logger.DEBUG ) :
            print "DEBUG:  " + message

    @classmethod
    def info( cls, message ) :
        if cls.isLevel( Logger.INFO ) :
            print "INFO :  " + message

    @classmethod
    def warn( cls, message ) :
        if cls.isLevel( Logger.WARN ) :
            print "WARN :  " + message

    @classmethod
    def error( cls, message ) :
        if cls.isLevel( Logger.ERROR ) :
            print "ERROR:  " + message

    @classmethod
    def fatal( cls, message ) :
        if cls.isLevel( Logger.FATAL ) :
            print "FATAL:  " + message

E algum código que o testa um pouco:

def logAll() :
    Logger.debug( "This is a Debug message." )
    Logger.info ( "This is a Info  message." )
    Logger.warn ( "This is a Warn  message." )
    Logger.error( "This is a Error message." )
    Logger.fatal( "This is a Fatal message." )

if __name__ == '__main__' :

    print "Should see all DEBUG and higher"
    Logger._level = Logger.DEBUG
    logAll()

    print "Should see all ERROR and higher"
    Logger._level = Logger.ERROR
    logAll()

10
Isso não me parece bom método exemplo de classe são boas para porque ele poderia ter sido feito de uma forma menos complexa, apenas fazendo todas as funções de nível módulo de métodos de classe e acabar com a classe completamente.
22480 martineau

10
Oh, ainda mais importante, o Python fornece uma classe de log muito simples de usar. Pior do que criar uma solução abaixo do ideal, reinventei a roda. Tapa de mão dupla. Mas fornece um exemplo de trabalho, pelo menos. Mas não tenho certeza se concordo em me livrar da classe em um nível conceitual. Às vezes, você deseja encapsular o código para facilitar a reutilização, e os métodos de log são um ótimo alvo para reutilização.
Marvo

3
Por que você não usaria um método estático?
bdforbes


11

Quando um usuário faz login no meu site, um objeto User () é instanciado a partir do nome de usuário e senha.

Se eu precisar de um objeto de usuário sem que o usuário esteja lá para efetuar login (por exemplo, um usuário administrador pode excluir outra conta de usuário, é necessário instanciar esse usuário e chamar seu método de exclusão):

Eu tenho métodos de classe para pegar o objeto de usuário.

class User():
    #lots of code
    #...
    # more code

    @classmethod
    def get_by_username(cls, username):
        return cls.query(cls.username == username).get()

    @classmethod
    def get_by_auth_id(cls, auth_id):
        return cls.query(cls.auth_id == auth_id).get()

9

Eu acho que a resposta mais clara é a de AmanKow . Tudo se resume a como você deseja organizar seu código. Você pode escrever tudo como funções no nível do módulo, que estão agrupadas no espaço para nome do módulo, ou seja,

module.py (file 1)
---------
def f1() : pass
def f2() : pass
def f3() : pass


usage.py (file 2)
--------
from module import *
f1()
f2()
f3()
def f4():pass 
def f5():pass

usage1.py (file 3)
-------------------
from usage import f4,f5
f4()
f5()

O código de procedimento acima não está bem organizado, como você pode ver após apenas três módulos, fica confuso, o que cada método faz? Você pode usar nomes descritivos longos para funções (como em java), mas ainda assim seu código fica incontrolável muito rapidamente.

A maneira orientada a objetos é dividir seu código em blocos gerenciáveis, ou seja, classes e objetos e funções podem ser associados a instâncias de objetos ou a classes.

Com as funções de classe, você obtém outro nível de divisão no seu código em comparação com as funções no nível do módulo. Assim, você pode agrupar funções relacionadas dentro de uma classe para torná-las mais específicas a uma tarefa que você atribuiu a essa classe. Por exemplo, você pode criar uma classe de utilitário de arquivo:

class FileUtil ():
  def copy(source,dest):pass
  def move(source,dest):pass
  def copyDir(source,dest):pass
  def moveDir(source,dest):pass

//usage
FileUtil.copy("1.txt","2.txt")
FileUtil.moveDir("dir1","dir2")

Dessa forma, é mais flexível e mais sustentável, você agrupa funções e fica mais óbvio o que cada função faz. Além disso, você evita conflitos de nome, por exemplo, a cópia da função pode existir em outro módulo importado (por exemplo, cópia de rede) que você usa no seu código; portanto, quando você usa o nome completo FileUtil.copy (), remove o problema e as duas funções de cópia pode ser usado lado a lado.


1
Para usar f1 (), f2 () e assim por diante, como você fez, você não deve usar o módulo import * e o uso import *?
Jblasco

@Jblasco Corrigi as instruções de importação para remover a confusão, sim, se você importar o módulo, precisará prefixar as funções com o nome do módulo. ou seja, módulo de importação -> module.f1 () etc
firephil

Eu discordo desta resposta; Python não é Java. Aparentemente, a questão do código incontrolável surge apenas devido à escolha deliberada de nomes ruins no exemplo. Se, em vez disso, você replicasse os FileUtilmétodos de classe como funções de um file_utilmódulo, seria comparável e não abusaria do OOP quando nenhum objeto realmente existir (na verdade, você poderia argumentar que é preferível, porque você não acaba com from file_util import FileUtiloutra verbosidade). Os conflitos de nome também podem ser evitados no código processual, ao import file_utilinvés de from file_util import ....
ForgottenUmbrella

7

Honestamente? Eu nunca encontrei um uso para staticmethod ou classmethod. Ainda estou para ver uma operação que não pode ser feita usando uma função global ou um método de instância.

Seria diferente se o python usasse membros privados e protegidos mais como o Java. Em Java, preciso de um método estático para acessar os membros privados de uma instância para fazer as coisas. No Python, isso raramente é necessário.

Normalmente, vejo pessoas usando métodos estáticos e métodos de classe quando tudo o que eles realmente precisam fazer é usar os espaços de nome no nível do módulo do python.


1
Privado: _variable_name e protegido: __variable_name
Bradley Kreider

1
Um uso indispensável é o setUpClass e o tearDownClass da unittest. Você está usando unittests, certo? :)
dbn

7

Ele permite escrever métodos de classe genéricos que você pode usar com qualquer classe compatível.

Por exemplo:

@classmethod
def get_name(cls):
    print cls.name

class C:
    name = "tester"

C.get_name = get_name

#call it:
C.get_name()

Se você não usar, @classmethodpoderá fazê-lo com a palavra-chave self, mas ela precisará de uma instância da Class:

def get_name(self):
    print self.name

class C:
    name = "tester"

C.get_name = get_name

#call it:
C().get_name() #<-note the its an instance of class C

5

Eu trabalhava com PHP e recentemente me perguntei: o que está acontecendo com esse método de classe? O manual do Python é muito técnico e possui poucas palavras, portanto não ajudará no entendimento desse recurso. Eu estava pesquisando no Google e encontrei a resposta -> http://code.anjanesh.net/2007/12/python-classmethods.html .

Se você estiver com preguiça de clicar nele. Minha explicação é mais curta e abaixo. :)

em PHP (talvez nem todos vocês conheçam PHP, mas essa linguagem é tão direta que todos devem entender do que estou falando), temos variáveis ​​estáticas como esta:


class A
{

    static protected $inner_var = null;

    static public function echoInnerVar()
    {
        echo self::$inner_var."\n";
    }

    static public function setInnerVar($v)
    {
        self::$inner_var = $v;
    }

}

class B extends A
{
}

A::setInnerVar(10);
B::setInnerVar(20);

A::echoInnerVar();
B::echoInnerVar();

A saída será nos dois casos 20.

No entanto, em python, podemos adicionar o decorador @classmethod e, portanto, é possível obter as saídas 10 e 20, respectivamente. Exemplo:


class A(object):
    inner_var = 0

    @classmethod
    def setInnerVar(cls, value):
        cls.inner_var = value

    @classmethod
    def echoInnerVar(cls):
        print cls.inner_var


class B(A):
    pass


A.setInnerVar(10)
B.setInnerVar(20)

A.echoInnerVar()
B.echoInnerVar()

Inteligente, não é?


Um possível problema com o exemplo do Python é que, se ele B.setInnerVar(20)foi deixado de fora, ele seria impresso 10duas vezes (em vez de gerar um erro na segunda chamada echoInnerBar () que não inner_varfoi definida. #
Martineau

5

Os métodos de classe fornecem um "açúcar semântico" (não sei se esse termo é amplamente usado) - ou "conveniência semântica".

Exemplo: você tem um conjunto de classes representando objetos. Você pode querer ter o método de classe all()ou find()escrever User.all()ou User.find(firstname='Guido'). Isso pode ser feito usando funções no nível do módulo, é claro ...


Seu exemplo assume, é claro, que a classe está acompanhando todas as suas instâncias e pode acessá-las após a criação - algo que não é feito automaticamente.
22480 martineau

1
"açúcar semântico" soa como uma boa combinação para "açúcar sintático".
XTL

3

O que me ocorreu, vindo de Ruby, é que o chamado método de classe e o chamado método de instância são apenas uma função com significado semântico aplicado ao seu primeiro parâmetro, que é passado silenciosamente quando a função é chamada como método de um objeto (ie obj.meth()).

Normalmente esse objeto deve ser uma instância, mas o @classmethod decorador do método altera as regras para passar uma classe. Você pode chamar um método de classe em uma instância (é apenas uma função) - o primeiro argumento será sua classe.

Por ser apenas uma função , ela só pode ser declarada uma vez em um determinado escopo (ou seja, classdefinição). Se segue, portanto, como uma surpresa para um Rubyist, você não pode ter um método de classe e um método de instância com o mesmo nome .

Considere isto:

class Foo():
  def foo(x):
    print(x)

Você pode chamar foouma instância

Foo().foo()
<__main__.Foo instance at 0x7f4dd3e3bc20>

Mas não em uma aula:

Foo.foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method foo() must be called with Foo instance as first argument (got nothing instead)

Agora adicione @classmethod:

class Foo():
  @classmethod
  def foo(x):
    print(x)

Chamar uma instância agora passa sua classe:

Foo().foo()
__main__.Foo

assim como chamar uma classe:

Foo.foo()
__main__.Foo

É apenas a convenção que determina que usamos selfpara esse primeiro argumento em um método de instância e clsem um método de classe. Não usei nenhum aqui para ilustrar que é apenas uma discussão. Em Ruby, selfé uma palavra - chave.

Contraste com Ruby:

class Foo
  def foo()
    puts "instance method #{self}"
  end
  def self.foo()
    puts "class method #{self}"
  end
end

Foo.foo()
class method Foo

Foo.new.foo()
instance method #<Foo:0x000000020fe018>

O método da classe Python é apenas uma função decorada e você pode usar as mesmas técnicas para criar seus próprios decoradores . Um método decorado envolve o método real (no caso @classmethoddele passa o argumento de classe adicional). O método subjacente ainda está lá, oculto, mas ainda acessível .


nota de rodapé: escrevi isso depois que um conflito de nome entre uma classe e um método de instância despertou minha curiosidade. Estou longe de ser um especialista em Python e gostaria de comentar se algo está errado.


"que é passado silenciosamente quando a função é chamada como método de um objeto" - acredito que isso não seja exato. AFICT, nada é "passado silenciosamente" em Python. OMI, Python é muito mais sensível que Ruby nesse sentido (exceto super()). AFICT, a "mágica" acontece quando um atributo é definido ou lido. A chamada obj.meth(arg)em Python (ao contrário de Ruby) significa simplesmente (obj.meth)(arg). Nada é passado silenciosamente para qualquer lugar; obj.methé apenas um objeto que pode ser chamado que aceita um argumento a menos que a função da qual foi criado.
27418 Alexey

obj.meth, por sua vez, significa simplesmente getattr(obj, "meth").
27418 Alexey

Faz sentido ter uma resposta personalizada para pessoas provenientes de outros idiomas comuns. No entanto, uma coisa que falta nessa conversa, que a une, é a noção de um descritor Python . Funções são descritores , e é assim que o primeiro argumento "automagicamente" passa uma instância quando uma função é um atributo para a classe. É também como os métodos de classe são implementados, e uma implementação de python pura é fornecida no HOWTO que eu vinculei. O fato de que ele também é um decorador é uma espécie de auxiliar
juanpa.arrivillaga

2

Este é um tópico interessante. Minha opinião é que o método de classe python opera como um singleton em vez de uma fábrica (que retorna uma instância de uma classe produzida). A razão de ser um singleton é que existe um objeto comum que é produzido (o dicionário), mas apenas uma vez para a classe, mas compartilhado por todas as instâncias.

Para ilustrar isso aqui é um exemplo. Observe que todas as instâncias têm uma referência ao dicionário único. Este não é o padrão de fábrica como eu o entendo. Provavelmente isso é muito exclusivo do python.

class M():
 @classmethod
 def m(cls, arg):
     print "arg was",  getattr(cls, "arg" , None),
     cls.arg = arg
     print "arg is" , cls.arg

 M.m(1)   # prints arg was None arg is 1
 M.m(2)   # prints arg was 1 arg is 2
 m1 = M()
 m2 = M() 
 m1.m(3)  # prints arg was 2 arg is 3  
 m2.m(4)  # prints arg was 3 arg is 4 << this breaks the factory pattern theory.
 M.m(5)   # prints arg was 4 arg is 5

2

Eu estava me fazendo a mesma pergunta algumas vezes. E mesmo que os caras aqui tenham se esforçado para explicar, IMHO, a melhor resposta (e a mais simples) que eu encontrei é a descrição do método Class na documentação do Python.

Há também referência ao método estático. E caso alguém já conheça métodos de instância (que eu assumo), essa resposta pode ser a peça final para juntar tudo ...

Uma elaboração mais aprofundada e detalhada sobre este tópico também pode ser encontrada na documentação: A hierarquia de tipos padrão (role para baixo até a seção Métodos de instância )


1

@classmethodpode ser útil para instanciar facilmente objetos dessa classe a partir de recursos externos. Considere o seguinte:

import settings

class SomeClass:
    @classmethod
    def from_settings(cls):
        return cls(settings=settings)

    def __init__(self, settings=None):
        if settings is not None:
            self.x = settings['x']
            self.y = settings['y']

Em outro arquivo:

from some_package import SomeClass

inst = SomeClass.from_settings()

O acesso ao inst.x fornecerá o mesmo valor que as configurações ['x'].


0

Uma classe define um conjunto de instâncias, é claro. E os métodos de uma classe trabalham nas instâncias individuais. Os métodos de classe (e variáveis) são um local para pendurar outras informações relacionadas ao conjunto de instâncias.

Por exemplo, se sua turma define um conjunto de alunos, você pode querer variáveis ​​ou métodos de classe que definam coisas como o conjunto de notas em que os alunos podem ser membros.

Você também pode usar métodos de classe para definir ferramentas para trabalhar em todo o conjunto. Por exemplo, Student.all_of_em () pode retornar todos os alunos conhecidos. Obviamente, se seu conjunto de instâncias tiver mais estrutura do que apenas um conjunto, você poderá fornecer métodos de classe para conhecer essa estrutura. Students.all_of_em (grade = 'juniors')

Técnicas como essa tendem a levar ao armazenamento de membros do conjunto de instâncias em estruturas de dados enraizadas nas variáveis ​​de classe. Você precisa tomar cuidado para evitar frustrar a coleta de lixo.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.