una referencia procedimientos por paso pasar parametros parametro opcionales metodo llamar lista funciones como python python-3.5

python - referencia - ¿Debería una clase convertir tipos de parámetros en el momento inicial? ¿Si es así, cómo?



paso de parametros python (8)

Definí una clase con 5 variables de instancia

class PassPredictData: def __init__(self, rating, name, lat, long, elev): self.rating = rating # rest of init code

Quiero asegurarme de:

  • rating es una int
  • name es una str
  • lat , long , elev son flotadores

Al leer mi archivo de entrada, todo funciona creando una lista de objetos basada en mi clase. Cuando comienzo a comparar valores, obtuve resultados raros, ya que las variables de instancia seguían siendo cadenas.

¿Es la "forma más pitonica" de emitir los valores ya que el objeto se está creando usando int(string) y float(string) cuando se llama al constructor o se debe hacer este casting con la lógica dentro de la clase?


Definir tipos de campos personalizados

Una forma es definir sus propios tipos de campo y hacer la conversión y el manejo de errores en ellos. Los campos se basarán en descriptors . Esto es algo que vas a encontrar en los modelos Django , Flask-SQLAlchemy , DRF-Fields etc.

Tener dichos campos personalizados te permitirá __init__ , validarlos y esto funcionará no solo en __init__ , sino en cualquier lugar en el que intentemos asignarle un valor.

class Field: type = None def __init__(self, default=None): self.value = default def __get__(self, instance, cls): if instance is None: return self return self.value def __set__(self, instance, value): # Here we could either try to cast the value to # desired type or validate it and throw an error # depending on the requirement. try: self.value = self.type(value) except Exception: raise ValueError(''Failed to cast {value!r} to {type}''.format( value=value, type=self.type )) class IntField(Field): type = int class FloatField(Field): type = float class StrField(Field): type = str class PassPredictData: rating = IntField() name = StrField() lat = FloatField() long = FloatField() elev = FloatField() def __init__(self, rating, name, lat, long, elev): self.rating = rating self.name = name self.lat = lat self.long = long self.elev = elev

Manifestación:

>>> p = PassPredictData(1.2, ''foo'', 1.1, 1.2, 1.3) >>> p.lat = ''123'' >>> p.lat 123.0 >>> p.lat = ''foo'' ... ValueError: Failed to cast ''foo'' to <class ''float''> >>> p.name = 123 >>> p.name ''123''

Use un analizador estático

Otra opción es usar analizadores estáticos como Mypy y detectar los errores antes de que se ejecute el programa. El siguiente código utiliza la sintaxis de Python 3.6 , pero también puede hacer que funcione con otras versiones realizando algunos cambios.

class PassPredictData: rating: int name: str lat: float long: float elev: float def __init__(self, rating: int, name: str, lat: float, long: float, elev: float) -> None: self.rating = rating self.name = name self.lat = lat self.long = long self.elev = elev PassPredictData(1, 2, 3, 4, 5) PassPredictData(1, ''spam'', 3.1, 4.2, 5.3) PassPredictData(1.2, ''spam'', 3.1, 4.2, 5)

Cuando ejecutamos Mypy en esto obtenemos:

/so.py:15: error: Argument 2 to "PassPredictData" has incompatible type "int"; expected "str" /so.py:17: error: Argument 1 to "PassPredictData" has incompatible type "float"; expected "int"


Dijiste que no puedes usar bibliotecas de terceros, pero otros pueden encontrar esta pregunta. El typeguard vale la pena mencionar aquí.

from typeguard import typechecked class PassPredictData: @typechecked def __init__(self, rating: int, name: str, lat: float, long: float, elev: float): ...

Por cierto. por defecto, el decorador se desactiva cuando Python se ejecuta en modo optimizado ( -O ). Y es fácil desactivar los controles cuando está seguro de que no son necesarios.

Por cierto. Quizás el lat , long , elev debería ser numbers.Real con el molde dentro del constructor para float ;)


EDITAR: (editar porque el tema de la pregunta ha cambiado) No recomendaría el tipo de conversión de parámetros en la hora de inicio . Por ejemplo:

class PassPredictData: def __init__(self, rating, name, lat, long, elev): self.rating = int(rating) self.name = str(name) ...

En mi opinión, este tipo de conversión implícita es peligroso por algunas razones.

  1. Implícitamente convierte el tipo de parámetro a otro sin dar advertencia es muy engañoso
  2. No generará ninguna excepción si los usuarios pasan de tipo no deseado. Esto va de la mano con el casting implícito. Esto podría evitarse mediante la verificación explícita de tipos.
  3. El tipo de conversión silenciosa viola el tipado de pato

En lugar de convertir el tipo de parámetros, es mejor verificar el tipo de parámetro en la hora de inicio. Este enfoque evitaría los tres problemas anteriores. Para lograr esto, puede usar la verificación de tipo fuerte de typedecorator Me gusta porque es simple y muy legible

Para Python2 [edit: dejar esto como referencia cuando se solicite OP]

from typedecorator import params, returns, setup_typecheck, void, typed class PassPredictData: @void @params(self=object, rating = int, name = str, lat = float, long = float, elev = float) def __init__(self, rating, name, lat, long, elev): self.rating = rating self.name = name self.lat = lat self.long = long self.elev = elev setup_typecheck() x = PassPredictData(1, "derp" , 6.8 , 9.8, 7.6) #works fine x1 = PassPredictData(1.8, "derp" , 6.8 , 9.8, 7.6) #TypeError: argument rating = 1.8 doesn''t match signature int x2 = PassPredictData(1, "derp" , "gagaga" , 9.8, 7.6) #TypeError: argument lat = ''gagaga'' doesn''t match signature float x3 = PassPredictData(1, 5 , 6.8 , 9.8, 7.6) #TypeError: argument name = 5 doesn''t match signature str

Para Python3 puede usar la sintaxis de anotación :

class PassPredictData1: @typed def __init__(self : object, rating : int, name : str, lat : float, long : float, elev : float): self.rating = rating setup_typecheck() x = PassPredictData1(1, 5, 4, 9.8, 7.6)

arroja un error:

TypeError: argumento nombre = 5 no coincide con la signatura str


En Python 3.5+ puede usar sugerencias de tipo y el módulo de escritura .

class PassPredictData: def __init__(self, rating: int, name: str, lat: float, long: float, elev: float): self.rating = rating #rest of init code

Tenga en cuenta que estos son solo consejos. Python en realidad no hace nada con ellos, como mostrar un error si se usa el tipo incorrecto.


Incluso sin depender de bibliotecas externas, puede definir su propio decorador de verificación de tipos simple en solo unas pocas líneas. Esto usa el módulo de inspect de Core-Python para obtener los nombres de los parámetros, pero incluso sin él, podrías zip los args con una lista de tipos, aunque esto dificultará el uso de kwargs .

import inspect def typecheck(**types): def __f(f): def _f(*args, **kwargs): all_args = {n: a for a, n in zip(args, inspect.getargspec(f).args)} all_args.update(kwargs) for n, a in all_args.items(): t = types.get(n) if t is not None and not isinstance(a, t): print("WARNING: Expected {} for {}, got {}".format(t, n, a)) return f(*args, **kwargs) return _f return __f class PassPredictData: @typecheck(rating=int, name=str, elev=float) def __init__(self, rating, name, lat=0.0, long=0.0, elev=0.0): self.rating = rating p = PassPredictData(5.1, "foo", elev=4) # WARNING: Expected <class ''int''> for rating, got 5.1 # WARNING: Expected <class ''float''> for elev, got 4

En lugar de imprimir una advertencia, por supuesto también podría generar una excepción. O, utilizando el mismo enfoque, también podría simplemente (intentar) convertir los parámetros al tipo esperado:

def typecast(**types): def __f(f): def _f(*args, **kwargs): all_args = {n: a for a, n in zip(args, inspect.getargspec(f).args)} all_args.update(kwargs) for n, a in all_args.items(): t = types.get(n) if t is not None: all_args[n] = t(a) # instead of checking, just cast return f(**all_args) # pass the map with the typecast params return _f return __f class PassPredictData: @typecast(rating=int, name=str, elev=float) def __init__(self, rating, name, lat=0.0, long=0.0, elev=0.0): print([rating, name, lat, long, elev]) p = PassPredictData("5", "foo", elev="3.14") # Output of print: [5, ''foo'', 0.0, 0.0, 3.14]

O una versión más simple, sin inspect , pero que no funciona para kwargs y que requiere proporcionar el tipo para cada parámetro, incluido self (o None para ningún tipo de conversión):

def typecast(*types): def __f(f): def _f(*args): return f(*[t(a) if t is not None else a for a, t in zip(args, types)]) return _f return __f class PassPredictData: @typecast(None, int, str, float, float, float) def __init__(self, rating, name, lat=0.0, long=0.0, elev=0.0): print([rating, name, lat, long, elev])


Parece que hay un millón de formas de hacerlo, pero esta es la fórmula que uso:

class PassPredictData(object): types = {''lat'' : float, ''long'' : float, ''elev'' : float, ''rating'': int, ''name'' : str, } def __init__(self, rating, name, lat, long, elev): self.rating = rating [rest of init code] @classmethod def from_string(cls, string): [code to parse your string into a dict] typed = {k: cls.types[k](v) for k, v in parsed.items()} return cls(**typed)

Algo que es bueno acerca de esto: puedes usar directamente un re.groupdict() para producir tu dict (como un ejemplo):

parsed = re.search(''(?P<name>/w): Latitude: (?P<lat>/d+), Longitude: (?P<long>/d+), Elevation: (?P<elev>/d+) meters. (?P<rating>/d)'', some_string).groupdict()


Personalmente, haría cualquier análisis de cadenas antes de pasar los valores al constructor, a menos que el análisis sea una (o la ) responsabilidad explícita de la clase. Prefiero que mi programa falle porque no emití un valor explícito que ser demasiado flexible y terminar en una situación similar a Javascript 0 == "0" . Dicho esto, si quieres aceptar cadenas como parámetros, puedes llamar a int(my_parameter) o float(my_parameter) según sea necesario en el constructor y eso asegurará que sean números sin importar si pasas un número, una cadena o incluso un booleano .

En caso de que quiera saber más sobre la seguridad de tipo en Python, puede echar un vistazo a las anotaciones de tipo , que son compatibles con mypy tipo como mypy , y el paquete de rasgos para la seguridad de tipo en los atributos de clase.


Si escribe import this en el intérprete de Python, obtendrá "The Zen of Python, por Tim Peters". Las primeras tres líneas parecen aplicarse a su situación:

Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex.

Recomiendo implementar tu clase de esta manera:

class PassPredictData: def __init__(self, rating, name, lat, long, elev): self.rating = int(rating) self.name = str(name) self.lat = float(lat) self.long = float(long) self.elev = float(elev)

Esta es la implementación que mencionas en tu pregunta. Es simple y explícito . La belleza está en el ojo del espectador.

Respuestas a los Comentarios

La implementación es explícita para el escritor de la clase versus alguna otra solución que oculta la conversión de tipo detrás de algún mecanismo opaco.

Existe un argumento válido de que no es obvio a partir de la firma de la función cuáles son los tipos de parámetros esperados. Sin embargo, la pregunta implica que todos los parámetros se pasan como cadenas. En ese caso, el tipo esperado es str para todos los parámetros del constructor. Tal vez el título de la pregunta no describa claramente el problema. Tal vez un título mejor sería " Aplicar tipos de variables de instancia al pasar cadenas como parámetros al constructor ".