Explicación de @classmethod y @staticmethod de Python

Python es un lenguaje único en el sentido de que es bastante fácil de aprender, dada su sintaxis directa, pero aun así es extremadamente poderoso. Hay muchas más características u...

Python es un lenguaje único en el sentido de que es bastante fácil de aprender, dada su sintaxis directa, pero aun así es extremadamente poderoso. Hay muchas más funciones debajo del capó de las que te imaginas. Si bien podría referirme a bastantes cosas diferentes con esta declaración, en este caso estoy hablando de los decoradores @classmethod y @ método estático. Para muchos de sus proyectos, probablemente no necesitó o encontró estas características, pero puede encontrar que son más útiles de lo que esperaba. No es tan obvio cómo crear métodos estáticos de Python, que es donde entran estos dos decoradores.

En este artículo te estaré contando qué hace cada uno de estos decoradores, sus diferencias y algunos ejemplos de cada uno.

El decorador @classmethod

Este decorador existe para que pueda crear métodos de clase que se pasan al objeto de clase real dentro de la llamada a la función, al igual que self se pasa a cualquier otro método de instancia ordinario en una clase.

En esos métodos de instancia, el argumento self es el objeto de instancia de clase en sí mismo, que luego se puede usar para actuar sobre los datos de instancia. Los métodos @classmethod también tienen un primer argumento obligatorio, pero este argumento no es una instancia de clase, en realidad es la propia clase sin instanciar. Entonces, mientras que un método de clase típico podría verse así:

1
2
3
4
5
6
7
class Student(object):

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

scott = Student('Scott',  'Robinson')

En su lugar, se usaría un método @classmethod similar:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Student(object):

    # Constructor removed for brevity

    @classmethod
    def from_string(cls, name_str):
        first_name, last_name = map(str, name_str.split(' '))
        student = cls(first_name, last_name)
        return student

scott = Student.from_string('Scott Robinson')

Esto sigue muy bien el patrón de fábrica estático, encapsulando la lógica de análisis dentro del propio método.

El ejemplo anterior es muy simple, pero puedes imaginar ejemplos más complicados que lo hagan más atractivo. Imagínese si un objeto ‘Estudiante’ pudiera serializarse en muchos formatos diferentes. Podrías usar esta misma estrategia para analizarlos todos:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Student(object):

    # Constructor removed for brevity

    @classmethod
    def from_string(cls, name_str):
        first_name, last_name = map(str, name_str.split(' '))
        student = cls(first_name, last_name)
        return student

    @classmethod
    def from_json(cls, json_obj):
        # parse json...
        return student

    @classmethod
    def from_pickle(cls, pickle_file):
        # load pickle file...
        return student

El decorador se vuelve aún más útil cuando te das cuenta de su utilidad en las subclases. Dado que el objeto de la clase se le proporciona dentro del método, también puede usar el mismo @classmethod para las subclases.

El decorador @staticmethod

El decorador @staticmethod es similar a @classmethod en que se puede llamar desde un objeto de clase no instanciado, aunque en este caso no se pasa ningún parámetro cls a su método. Entonces, un ejemplo podría verse así:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Student(object):

    # Constructor removed for brevity

    @staticmethod
    def is_full_name(name_str):
        names = name_str.split(' ')
        return len(names) > 1

Student.is_full_name('Scott Robinson')   # True
Student.is_full_name('Scott')            # False

Dado que tampoco se pasa ningún objeto self, eso significa que tampoco tenemos acceso a ningún dato de instancia y, por lo tanto, este método tampoco se puede llamar en un objeto instanciado.

Estos tipos de métodos no suelen estar destinados a crear o crear instancias de objetos, pero pueden contener algún tipo de lógica relacionada con la clase en sí, como un método auxiliar o de utilidad.

@classmethod vs @staticmethod

Lo más obvio entre estos decoradores es su capacidad para crear métodos estáticos dentro de una clase. Estos tipos de métodos se pueden invocar en objetos de clase no instanciados, al igual que las clases que usan la palabra clave static en Java.

En realidad, solo hay una diferencia entre estos dos decoradores de métodos, pero es una de las principales. Probablemente notó en las secciones anteriores que los métodos @classmethod tienen un parámetro cls enviado a sus métodos, mientras que los métodos @staticmethod no.

Este parámetro cls es el objeto de clase del que hablamos, lo que permite que los métodos @classmethod creen fácilmente una instancia de la clase, independientemente de cualquier herencia que se produzca. La falta de este parámetro cls en los métodos @staticmethod los convierte en verdaderos métodos estáticos en el sentido tradicional. Su objetivo principal es contener la lógica perteneciente a la clase, pero esa lógica no debería tener ninguna necesidad de datos de instancia de clase específicos.

Un ejemplo más largo

Ahora veamos otro ejemplo donde usamos ambos tipos juntos en la misma clase:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# static.py

class ClassGrades:

    def __init__(self, grades):
        self.grades = grades

    @classmethod
    def from_csv(cls, grade_csv_str):
        grades = map(int, grade_csv_str.split(', '))
        cls.validate(grades)
        return cls(grades)


    @staticmethod
    def validate(grades):
        for g in grades:
            if g < 0 or g > 100:
                raise Exception()

try:
    # Try out some valid grades
    class_grades_valid = ClassGrades.from_csv('90, 80, 85, 94, 70')
    print 'Got grades:', class_grades_valid.grades

    # Should fail with invalid grades
    class_grades_invalid = ClassGrades.from_csv('92, -15, 99, 101, 77, 65, 100')
    print class_grades_invalid.grades
except:
    print 'Invalid!'
1
2
3
$ python static.py
Got grades: [90, 80, 85, 94, 70]
Invalid!

Observe cómo los métodos estáticos incluso pueden funcionar junto con from_csv llamando a validate usando el objeto cls. Ejecutar el código anterior debería imprimir una serie de calificaciones válidas y luego fallar en el segundo intento, por lo que se imprimirá "¡No válido!".

Conclusión

En este artículo, vio cómo funcionan los decoradores @classmethod y @staticmethod en Python, algunos ejemplos de cada uno en acción y cómo se diferencian entre sí. Con suerte, ahora puede aplicarlos a sus propios proyectos y usarlos para continuar mejorando la calidad y la organización de su propio código.

  • ¿Alguna vez ha usado estos decoradores y, de ser así, cómo? ¡Cuéntanos en los comentarios!*