python - with - Qual é o propósito dos métodos de classe?




python with django pdf (10)

Eu estou ensinando a mim mesmo Python e minha mais recente lição foi que Python não é Java , e então eu 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 métodos static em Java, mas agora não tenho certeza de quando usá-los. Todos os conselhos que eu posso encontrar sobre os métodos da classe Python estão na linha de novatos como eu devem evitá-los, e a documentação padrão é mais opaca ao discuti-los.

Alguém tem um bom exemplo de usar um método de classe em Python ou pelo menos alguém pode me dizer quando os métodos de classe podem ser usados ​​de maneira sensata?


Construtores alternativos são o exemplo clássico.


Ele permite que você escreva 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 usa @classmethod você pode fazer isso com a palavra-chave self mas precisa de uma instância de 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

Eu acho que a resposta mais clara é a da AmanKow . Tudo se resume a como você deseja organizar seu código. Você pode escrever tudo como funções de nível de módulo que são envolvidas no namespace 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 processual acima não é bem organizado, como você pode ver depois de apenas 3 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 rápido.

A maneira orientada a objeto é decompor 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ê ganha outro nível de divisão em seu código em comparação com as funções de nível de módulo. Assim, você pode agrupar funções relacionadas dentro de uma classe para torná-las mais específicas para uma tarefa atribuída 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")

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


Eu costumava trabalhar com PHP e recentemente eu estava me perguntando, o que está acontecendo com esse método de classe? O manual do Python é muito técnico e muito curto em palavras, por isso não ajuda a entender esse recurso. Eu estava googling e googling e encontrei resposta -> http://code.anjanesh.net/2007/12/python-classmethods.html .

Se você é preguiçoso para clicar nele. Minha explicação é mais curta e abaixo. :)

em PHP (talvez nem todos vocês saibam PHP, mas esta linguagem é tão simples que todos devem entender o que eu estou falando) nós 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á em ambos os casos 20.

No entanto, em python, podemos adicionar @classmethod decorator e, portanto, é possível ter a saída 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 é?


Honestamente? Eu nunca encontrei um uso para staticmethod ou classmethod. Eu ainda tenho que 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 poder acessar membros particulares de uma instância para fazer coisas. Em Python, isso raramente é necessário.

Normalmente, vejo pessoas usando staticmethods e classmethods quando tudo o que realmente precisam fazer é usar melhor os namespaces de nível de módulo do python.


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

Normalmente, esse objeto deve ser uma instância, mas o decorador do método @classmethod 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 argyment será sua classe.

Por ser apenas uma função , ela só pode ser declarada uma vez em qualquer escopo dado (por exemplo, definição de class ). Se segue, portanto, como uma surpresa para um Rubyist, que 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 foo em uma 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 pela sua classe:

Foo().foo()
__main__.Foo

como chamando em uma classe:

Foo.foo()
__main__.Foo

É apenas uma convenção que dita que usamos self para esse primeiro argumento em um método de instância e cls em um método de classe. Eu não usei aqui para ilustrar que é apenas um argumento. Em Ruby, self é uma palavra - chave.

Contraste com o 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 de 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 de @classmethod ele passa o argumento de classe adicional). O método subjacente ainda está lá, oculto, mas ainda acessível .

nota de rodapé: eu escrevi isso depois que um confronto de nomes entre uma classe e um método de instância despertou minha curiosidade. Estou longe de ser um especialista em Python e gostaria de comentários se algo estiver errado.


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. A coisa 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 de nível de módulo do Python.

Se você tem uma classe MyClass , e uma função de nível de módulo que opera no MyClass (fábrica, stub de injeção de dependência, etc), faça dela um classmethod . Então estará disponível para subclasses.


Os 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ê quiser ter um método que se ajuste naturalmente ao namespace da classe, mas não esteja 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 atual; por exemplo, Path("/tmp/my_temp_dir") . Este é um método de classe.
  • .chdir()
    • Faça auto o diretório atual.

Como o diretório atual é todo o processo, o método cwd não possui uma instância específica com a qual ele deve ser associado. No entanto, alterar o cwd para o diretório de uma determinada instância do Path deve, de fato, ser um método de instância.

Hmmm ... como Path.cwd() , de fato, retornar uma instância do Path , eu acho que poderia ser considerado um método de fábrica ...


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

Se eu precisar de um objeto de usuário sem o usuário estar lá para efetuar login (por exemplo, um usuário administrador pode querer excluir outra conta de usuário, então eu preciso 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()

Recentemente, eu queria uma classe de logging muito leve que produzisse quantidades variáveis ​​de saída, dependendo do nível de registro que poderia ser programaticamente definido. Mas eu não queria instanciar a classe toda vez que eu quisesse enviar uma mensagem de erro, erro ou aviso. Mas eu também queria encapsular o funcionamento dessa instalação madeireira e torná-la reutilizável sem a declaração de quaisquer globais.

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

Com minha classe Logging simples, 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, eu simplesmente tinha que 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á enviada. A mensagem de depuração não será impressa.

Aqui está minha aula:

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




class-method