O que significa @classmethod e @staticmethod para principiante?
@staticmethod
em python? Preciso de saber a diferença e o significado.
Tanto quanto sei, diz a uma classe que é um método que deve ser herdado em subclasses, ou... algum. Mas para quê isso? Por que não definir apenas o método da classe sem adicionar @classmethod
ou @staticmethod
ou quaisquer definições @
?
TL; dr: quando devo usá-los, Porque devo usá-los, e Como devo usá-los?
sou bastante avançado com C++, por isso usar conceitos de programação mais avançados não deve ser um problema. Sinta-se à vontade para me dar um exemplo C++ correspondente, se possível.
11 answers
Embora classmethod
e staticmethod
sejam bastante semelhantes, há uma pequena diferença no uso para ambas as entidades: classmethod
deve ter uma referência a um objeto de classe como o primeiro parâmetro, enquanto staticmethod
não pode ter parâmetros de todo.
Exemplo
class Date(object):
def __init__(self, day=0, month=0, year=0):
self.day = day
self.month = month
self.year = year
@classmethod
def from_string(cls, date_as_string):
day, month, year = map(int, date_as_string.split('-'))
date1 = cls(day, month, year)
return date1
@staticmethod
def is_date_valid(date_as_string):
day, month, year = map(int, date_as_string.split('-'))
return day <= 31 and month <= 12 and year <= 3999
date2 = Date.from_string('11-09-2012')
is_date = Date.is_date_valid('11-09-2012')
Explicação
Vamos assumir um exemplo de uma classe, lidando com a informação da data (esta será a nossa placa):class Date(object):
def __init__(self, day=0, month=0, year=0):
self.day = day
self.month = month
self.year = year
Esta classe, obviamente, poderia ser usada para armazenar informações sobre determinadas datas (sem informação do fuso-horário; vamos assumir que todas as datas são apresentadas em UTC).
Aqui temos __init__
, um inicializador típico de instâncias de classe Python, que recebe argumentos como um típico instancemethod
, tendo o primeiro argumento não-opcional (self
) que contém uma referência a uma instância recém-criada.
Método Da Classe
Temos algumas tarefas que podem ser bem feitas usandoclassmethod
s.
vamos supor que queremos criar um monte de instâncias de classe Data informação proveniente de uma fonte exterior codificada como uma cadeia de caracteres com o formato "dd-mm-AAAA". Suponha que temos que fazer isso em diferentes lugares no código fonte de nosso projeto.
Então o que temos de fazer aqui é:- processar uma string para receber dia, mês e ano como três variáveis inteiras ou uma tupla de 3 itens que consiste nessa variável.
- instanciar {[13] } passando esses valores para a chamada de inicialização.
day, month, year = map(int, string_date.split('-'))
date1 = Date(day, month, year)
Para este efeito, o C++ pode implementar essa funcionalidade com sobrecarga, mas o Python não tem esta sobrecarga. Em vez disso, podemos usar classmethod
. Vamos criar outro construtor.
@classmethod
def from_string(cls, date_as_string):
day, month, year = map(int, date_as_string.split('-'))
date1 = cls(day, month, year)
return date1
date2 = Date.from_string('11-09-2012')
Vamos olhar mais atentamente para a implementação acima, e rever quais as vantagens que temos aqui:
-
Implementámos o processamento de string de datas num só lugar e agora é reutilizável.
Encapsulamento funciona bem aqui (se você acha que pode implementar string analisando como uma única função em outro lugar, esta solução se encaixa muito melhor no paradigma OOP).
-
cls
é um objeto que possui a própria classe, Não uma instância da classe. É muito fixe, porque se herdarmos a nossa classe, todas as crianças terão definido também.
Método Estático
E quanto a...? É bastante semelhante aclassmethod
mas não toma quaisquer parâmetros obrigatórios (como um método de classe ou método de instância o).
Vamos ver o próximo caso de uso.
temos uma data que queremos validar de alguma forma. Esta tarefa também está logicamente ligada à classe Date
que usamos até agora, mas não requer instanciação dela.
staticmethod
pode ser útil. Vamos ver o próximo pedaço de código:
@staticmethod
def is_date_valid(date_as_string):
day, month, year = map(int, date_as_string.split('-'))
return day <= 31 and month <= 12 and year <= 3999
# usage:
is_date = Date.is_date_valid('11-09-2012')
Então, como podemos ver pelo uso de staticmethod
, não temos qualquer acesso ao que a classe é - - - é basicamente apenas uma função, chamada sintaticamente como um método, mas sem acesso ao objeto e seus internos (campos e outros métodos), enquanto classmetod o faz.
@classmethod
sobre @staticmethod
quando você está criando um construtor adicional.
No exemplo acima, Rostyslav utilizou o @classmethod
from_string
como uma fábrica para criar objetos Date
a partir de outros parâmetros inaceitáveis. O mesmo pode ser feito com @staticmethod
Como é mostrado no código abaixo:
class Date:
def __init__(self, month, day, year):
self.month = month
self.day = day
self.year = year
def display(self):
return "{0}-{1}-{2}".format(self.month, self.day, self.year)
@staticmethod
def millenium(month, day):
return Date(month, day, 2000)
new_year = Date(1, 1, 2013) # Creates a new Date object
millenium_new_year = Date.millenium(1, 1) # also creates a Date object.
# Proof:
new_year.display() # "1-1-2013"
millenium_new_year.display() # "1-1-2000"
isinstance(new_year, Date) # True
isinstance(millenium_new_year, Date) # True
Assim, tanto new_year
como millenium_new_year
são instâncias da classe Date
.
Mas, se observe atentamente, o processo de fábrica é difícil de codificar para criar objetos Date
não importa o que aconteça. O que isso significa é que mesmo que a classe Date
seja subclassificada, as subclasses ainda criarão um objeto simples Date
(sem qualquer propriedade da subclasse). Veja no exemplo abaixo:
class DateTime(Date):
def display(self):
return "{0}-{1}-{2} - 00:00:00PM".format(self.month, self.day, self.year)
datetime1 = DateTime(10, 10, 1990)
datetime2 = DateTime.millenium(10, 10)
isinstance(datetime1, DateTime) # True
isinstance(datetime2, DateTime) # False
datetime1.display() # returns "10-10-1990 - 00:00:00PM"
datetime2.display() # returns "10-10-2000" because it's not a DateTime object but a Date object. Check the implementation of the millenium method on the Date class
datetime2
não é um exemplo de DateTime
? WTF? Bem, isso é por causa do decorador usado.
Na maioria dos casos, isto é indesejado. Se o que você quer é um método de fábrica que está ciente da classe isso chamou-lhe, então @classmethod
é o que você precisa.
Reescrever o Date.millenium
como (essa é a única parte do código acima que muda)
@classmethod
def millenium(cls, month, day):
return cls(month, day, 2000)
Assegura que o class
não é codificado, mas sim aprendido. cls
pode ser qualquer subclasse. O resultado object
será justamente um exemplo de cls
. Vamos testar isso.
datetime1 = DateTime(10, 10, 1990)
datetime2 = DateTime.millenium(10, 10)
isinstance(datetime1, DateTime) # True
isinstance(datetime2, DateTime) # True
datetime1.display() # "10-10-1990 - 00:00:00PM"
datetime2.display() # "10-10-2000 - 00:00:00PM"
A razão é, como você sabe agora, @classmethod
foi usado em vez de @staticmethod
@classmethod
significa: quando este método é chamado, passamos a classe como o primeiro argumento em vez de a instância dessa classe (como normalmente fazemos com métodos). Isto significa que você pode usar a classe e suas propriedades dentro desse método em vez de uma instância particular.
@staticmethod
significa: quando este método é chamado, Nós não passamos uma instância da classe para ele (como normalmente fazemos com métodos). Isso significa que você pode colocar uma função dentro de uma classe, mas você não pode acessar a instância de que classe (isto é útil quando o seu método não usa a instância).
Quando utilizar cada
@staticmethod
função não é nada mais do que uma função definida dentro de uma classe. É chamável sem instanciar a classe em primeiro lugar. A sua definição é imutável através da herança.
- o Python não tem de instanciar um método de encadernação para um objecto.
- facilita a legibilidade do Código: ver @staticmetod , sabemos que o método não depende do Estado do próprio objecto;
@classmethod
função também callable without instantiatiating the class, but its definition follows Sub class, not Parent class, via inheritance, can be overridden by subclass. Isso porque o primeiro argumento para a função @classmethod
deve ser sempre cls (class)
.
- Métodos de fábrica , que são usados para criar uma instância para uma classe usando, por exemplo, algum tipo de pré-processamento.
- métodos estáticos chamando métodos estáticos : Se você dividir um método estático em vários métodos estáticos, você não deve codificar o nome da classe, mas usar métodos da classe
Aqui é um bom link para este tópico.
Usaria-se @classmethod
quando quisesse mudar o comportamento do método baseado no qual a subclasse chama o método. lembre-se que temos uma referência à classe chamadora em um método de classe.
Ao usar estática, gostaria que o comportamento permanecesse inalterado nas subclasses
Exemplo:
class Hero:
@staticmethod
def say_hello():
print("Helllo...")
@classmethod
def say_class_hello(cls):
if(cls.__name__=="HeroSon"):
print("Hi Kido")
elif(cls.__name__=="HeroDaughter"):
print("Hi Princess")
class HeroSon(Hero):
def say_son_hello(self):
print("test hello")
class HeroDaughter(Hero):
def say_daughter_hello(self):
print("test hello daughter")
testson = HeroSon()
testson.say_class_hello() #Output: "Hi Kido"
testson.say_hello() #Outputs: "Helllo..."
testdaughter = HeroDaughter()
testdaughter.say_class_hello() #Outputs: "Hi Princess"
testdaughter.say_hello() #Outputs: "Helllo..."
uma pequena compilação
@método estático Uma maneira de escrever um método dentro de uma classe sem referência ao objeto que está sendo chamado. Então, não há necessidade de passar argumentos implícitos como self ou cls. Ele é escrito exatamente da mesma forma que escrito fora da classe, mas não é de nenhum uso em python porque se você precisa encapsular um método dentro de uma classe uma vez que este método precisa ser a parte dessa classe @staticmethod é útil nisso caso.
@classmetod É importante quando você quer escrever um método de fábrica e por este atributo personalizado pode ser anexado em uma classe. Este(s) atributo (s) pode ser sobreposto na classe herdada.
Uma comparação entre estes dois métodos pode ser a seguinte
O que significa
@classmethod
e@staticmethod
?
- um método é uma função no espaço de nomes de um objeto, acessível como um atributo.
- um método regular (ou seja, instância) obtém a instância (normalmente chamamos-lhe
self
) como o primeiro argumento implícito. - A classe o método obtém a classe (normalmente o chamamos de
cls
) como o primeiro argumento implícito. - A estática o método não obtém nenhum primeiro argumento implícito (como um função regular).
Não precisas de decorador nenhum. Mas com o princípio de que você deve minimizar o número de argumentos para funções (veja Coder limpo), eles são úteis para fazer exatamente isso.Quando devo usá-los, por que devo usá-los, e como devo usá-los?
class Example(object):
def regular_instance_method(self):
"""A function of an instance has access to every attribute of that
instance, including its class (and its attributes.)
Not accepting at least one argument is a TypeError.
Not understanding the semantics of that argument is a user error.
"""
return some_function_f(self)
@classmethod
def a_class_method(cls):
"""A function of a class has access to every attribute of the class.
Not accepting at least one argument is a TypeError.
Not understanding the semantics of that argument is a user error.
"""
return some_function_g(cls)
@staticmethod
def a_static_method():
"""A static method has no information about instances or classes
unless explicitly given. It just lives in the class (and thus its
instances') namespace.
"""
return some_function_h()
Para métodos de instância e métodos de classe, não aceitar pelo menos um argumento é um erro tipográfico, mas não entender a semântica desse argumento é um erro do utilizador.
(definir some_function
's, por exemplo:
some_function_h = some_function_g = some_function_f = lambda x=None: x
E isto vai funcionar.)
Pesquisas pontilhadas em instâncias e classes:
Uma pesquisa pontilhada sobre uma instância é realizada nesta ordem-procuramos por:
- um descritor de dados no espaço de nomes da classe (como uma propriedade)
- dados no caso
__dict__
- um descritor de não-dados no espaço de nomes da classe (métodos).
instance = Example()
instance.regular_instance_method
E os métodos são atributos chamáveis:
instance.regular_instance_method()
Métodos de instância
O argumento, self
, é implicitamente dado através da pesquisa pontilhada.
Você deve acessar os métodos de instância a partir de instâncias da classe.
>>> instance = Example()
>>> instance.regular_instance_method()
<__main__.Example object at 0x00000000399524E0>
Métodos de classe
O argumento, cls
, é implicitamente dado através de pesquisa pontilhada.
Pode aceder a este método através de uma instância ou da classe (ou subclasses).
>>> instance.a_class_method()
<class '__main__.Example'>
>>> Example.a_class_method()
<class '__main__.Example'>
Métodos estáticos
Não argumentos são dados implicitamente. Este método funciona como qualquer função definida (por exemplo) num espaço de nomes de módulos, excepto que pode ser pesquisada
>>> print(instance.a_static_method())
None
Cada uma destas são progressivamente mais restritivas na informação que passam pelo método versus os métodos de instância. Use-os quando não precisar da informação.Novamente, quando devo usá-los, por que devo usá-los?
Isto torna as suas funções e métodos mais fáceis de raciocinar sobre e para unidade.
Qual é a razão mais fácil?def function(x, y, z): ...
Ou
def function(y, z): ...
Ou
def function(z): ...
As funções com menos argumentos são mais fáceis de raciocinar. Eles também são mais fáceis de se unificar.
Isto é semelhante a exemplo, classe e métodos estáticos. Tendo em mente que quando temos uma instância, também temos sua classe, novamente, pergunte a si mesmo, o que é mais fácil de raciocinar?:
def an_instance_method(self, arg, kwarg=None):
cls = type(self) # Also has the class of instance!
...
@classmethod
def a_class_method(cls, arg, kwarg=None):
...
@staticmethod
def a_static_method(arg, kwarg=None):
...
Exemplos de construção
Aqui estão algumas das minhas favoritas. exemplos de compilação:O método estático str.maketrans
era uma função no módulo string
, mas é muito mais conveniente para ele ser acessível a partir do espaço de nomes str
.
>>> 'abc'.translate(str.maketrans({'a': 'b'}))
'bbc'
O método da classe dict.fromkeys
devolve um novo dicionário instanciado a partir de um iterável de chaves:
>>> dict.fromkeys('abc')
{'a': None, 'c': None, 'b': None}
Quando subclassificado, vemos que obtém a informação da classe como um método da classe, o que é muito útil:
>>> class MyDict(dict): pass
>>> type(MyDict.fromkeys('abc'))
<class '__main__.MyDict'>
O meu conselho-conclusão
Usa métodos estáticos quando não o fizeres. precisa dos argumentos de classe ou instância, mas a função está relacionada com o uso do objeto, e é conveniente para a função estar no espaço de nomes do objeto.
Use métodos de classe quando você não precisa de informação de instância, mas precisa da informação de classe talvez para sua outra classe ou métodos estáticos, ou talvez ele mesmo como um construtor. (Você não codificaria a classe para que as subclasses pudessem ser usadas aqui.)
-
@staticmethod
não precisa de si próprio ou cls como primeiro parâmetro do método -
@staticmethod
e@classmethod
a função repartida pode ser chamada por instância ou variável de classe -
A função decorada impacta uma espécie de "propriedade imutável" que a herança de subclasses não pode sobrepor a sua função de classe base que é embrulhada por um decorador
@staticmethod
. -
@classmethod
necessita de cls (nome da classe, pode alterar o nome da variável se quiser, mas não é aconselhável) como primeiro parâmetro da função -
{[2] } sempre usado pela subclasse manner, a herança da subclasse pode alterar o efeito da função da classe base, ou seja
@classmethod
a função da classe base embrulhada pode ser substituída por diferentes subclasses.
class PythonBook:
def __init__(self, name, author):
self.name = name
self.author = author
def __repr__(self):
return f'Book: {self.name}, Author: {self.author}'
Sem um @classmethod, você deve trabalhar para criar instâncias um a um e eles são scartted.
book1 = PythonBook('Learning Python', 'Mark Lutz')
In [20]: book1
Out[20]: Book: Learning Python, Author: Mark Lutz
book2 = PythonBook('Python Think', 'Allen B Dowey')
In [22]: book2
Out[22]: Book: Python Think, Author: Allen B Dowey
Como por exemplo com @classmetod
class PythonBook:
def __init__(self, name, author):
self.name = name
self.author = author
def __repr__(self):
return f'Book: {self.name}, Author: {self.author}'
@classmethod
def book1(cls):
return cls('Learning Python', 'Mark Lutz')
@classmethod
def book2(cls):
return cls('Python Think', 'Allen B Dowey')
Testa-o:
In [31]: PythonBook.book1()
Out[31]: Book: Learning Python, Author: Mark Lutz
In [32]: PythonBook.book2()
Out[32]: Book: Python Think, Author: Allen B Dowey
Vês? Instâncias são criadas com sucesso dentro de uma definição de classe e elas são coletadas juntas.
Em conclusão, @classmetod decorator converter a o método convencional a um método de fábrica, usando métodos de classe, torna possível adicionar tantos Construtores alternativos quanto necessário.
O método da classe pode modificar o estado da classe,ligando-se à classe e contém cls como parâmetro.
O método estático não pode modificar o estado da classe, liga-se à classe e não conhece a classe ou instância
class empDetails:
def __init__(self,name,sal):
self.name=name
self.sal=sal
@classmethod
def increment(cls,name,none):
return cls('yarramsetti',6000 + 500)
@staticmethod
def salChecking(sal):
return sal > 6000
emp1=empDetails('durga prasad',6000)
emp2=empDetails.increment('yarramsetti',100)
# output is 'durga prasad'
print emp1.name
# output put is 6000
print emp1.sal
# output is 6500,because it change the sal variable
print emp2.sal
# output is 'yarramsetti' it change the state of name variable
print emp2.name
# output is True, because ,it change the state of sal variable
print empDetails.salChecking(6500)