Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

166
Views
Herencia de clases en las clases de datos de Python 3.7

Actualmente estoy probando las nuevas construcciones de clases de datos introducidas en Python 3.7. Actualmente estoy atascado tratando de heredar una clase principal. Parece que el orden de los argumentos está estropeado por mi enfoque actual, de modo que el parámetro bool en la clase secundaria se pasa antes que los otros parámetros. Esto está causando un error de tipo.

 from dataclasses import dataclass @dataclass class Parent: name: str age: int ugly: bool = False def print_name(self): print(self.name) def print_age(self): print(self.age) def print_id(self): print(f'The Name is {self.name} and {self.name} is {self.age} year old') @dataclass class Child(Parent): school: str ugly: bool = True jack = Parent('jack snr', 32, ugly=True) jack_son = Child('jack jnr', 12, school = 'havard', ugly=True) jack.print_id() jack_son.print_id()

Cuando ejecuto este código obtengo este TypeError :

 TypeError: non-default argument 'school' follows default argument

¿Cómo puedo solucionar esto?

over 4 years ago · Santiago Trujillo
3 answers
Answer question

0

La forma en que las clases de datos combinan atributos le impide usar atributos con valores predeterminados en una clase base y luego usar atributos sin valores predeterminados (atributos posicionales) en una subclase.

Esto se debe a que los atributos se combinan comenzando desde la parte inferior del MRO y creando una lista ordenada de los atributos en el orden en que se ven por primera vez; las anulaciones se mantienen en su ubicación original. Así que Parent comienza con ['name', 'age', 'ugly'] , donde ugly tiene un valor predeterminado, y luego Child agrega ['school'] al final de esa lista (con ugly ya en la lista). Esto significa que terminas con ['name', 'age', 'ugly', 'school'] y debido a que school no tiene un valor predeterminado, esto da como resultado una lista de argumentos no válidos para __init__ .

Esto está documentado en PEP-557 Clases de datos , en herencia :

Cuando el decorador @dataclass está creando la clase de datos, examina todas las clases base de la clase en MRO inverso (es decir, comenzando en el object ) y, para cada clase de datos que encuentra, agrega los campos de esa clase base. a un mapeo ordenado de campos. Después de agregar todos los campos de la clase base, agrega sus propios campos a la asignación ordenada. Todos los métodos generados utilizarán este mapeo de campos combinado, calculado y ordenado. Dado que los campos están en orden de inserción, las clases derivadas anulan las clases base.

y bajo Especificación :

TypeError se generará si un campo sin un valor predeterminado sigue a un campo con un valor predeterminado. Esto es cierto cuando esto ocurre en una sola clase o como resultado de la herencia de clase.

Tienes algunas opciones aquí para evitar este problema.

La primera opción es usar clases base separadas para forzar campos con valores predeterminados en una posición posterior en el pedido MRO. Evite a toda costa establecer campos directamente en las clases que se utilizarán como clases base, como Parent .

La siguiente jerarquía de clases funciona:

 # base classes with fields; fields without defaults separate from fields with. @dataclass class _ParentBase: name: str age: int @dataclass class _ParentDefaultsBase: ugly: bool = False @dataclass class _ChildBase(_ParentBase): school: str @dataclass class _ChildDefaultsBase(_ParentDefaultsBase): ugly: bool = True # public classes, deriving from base-with, base-without field classes # subclasses of public classes should put the public base class up front. @dataclass class Parent(_ParentDefaultsBase, _ParentBase): def print_name(self): print(self.name) def print_age(self): print(self.age) def print_id(self): print(f"The Name is {self.name} and {self.name} is {self.age} year old") @dataclass class Child(Parent, _ChildDefaultsBase, _ChildBase): pass

Extrayendo campos en clases base separadas con campos sin valores predeterminados y campos con valores predeterminados, y un orden de herencia cuidadosamente seleccionado, puede producir un MRO que coloca todos los campos sin valores predeterminados antes que aquellos con valores predeterminados. El MRO invertido (ignorar object ) para Child es:

 _ParentBase _ChildBase _ParentDefaultsBase _ChildDefaultsBase Parent

Tenga en cuenta que Parent no establece ningún campo nuevo, por lo que aquí no importa que termine 'último' en el orden de la lista de campos. Las clases con campos sin valores predeterminados ( _ParentBase y _ChildBase ) preceden a las clases con campos con valores predeterminados ( _ParentDefaultsBase y _ChildDefaultsBase ).

El resultado son las clases Parent e Child con un mismo campo anterior, mientras que Child sigue siendo una subclase de Parent :

 >>> from inspect import signature >>> signature(Parent) <Signature (name: str, age: int, ugly: bool = False) -> None> >>> signature(Child) <Signature (name: str, age: int, school: str, ugly: bool = True) -> None> >>> issubclass(Child, Parent) True

y así puedes crear instancias de ambas clases:

 >>> jack = Parent('jack snr', 32, ugly=True) >>> jack_son = Child('jack jnr', 12, school='havard', ugly=True) >>> jack Parent(name='jack snr', age=32, ugly=True) >>> jack_son Child(name='jack jnr', age=12, school='havard', ugly=True)

Otra opción es usar solo campos con valores predeterminados; aún puede cometer un error al no proporcionar un valor school , al generar uno en __post_init__ :

 _no_default = object() @dataclass class Child(Parent): school: str = _no_default ugly: bool = True def __post_init__(self): if self.school is _no_default: raise TypeError("__init__ missing 1 required argument: 'school'")

pero esto altera el orden de los campos; school termina después de ugly :

 <Signature (name: str, age: int, ugly: bool = True, school: str = <object object at 0x1101d1210>) -> None>

y un verificador de sugerencias de tipo se quejará de que _no_default no es una cadena.

También puede usar el proyecto attrs , que fue el proyecto que inspiró dataclasses . Utiliza una estrategia de fusión de herencia diferente; extrae los campos anulados en una subclase al final de la lista de campos, por lo que ['name', 'age', 'ugly'] en la Parent se convierte en ['name', 'age', 'school', 'ugly'] en la clase Child ; al anular el campo con un valor predeterminado, attrs permite la anulación sin necesidad de hacer un baile MRO.

attrs admite la definición de campos sin sugerencias de tipo, pero sigamos con el modo de sugerencia de tipo compatible configurando auto_attribs=True :

 import attr @attr.s(auto_attribs=True) class Parent: name: str age: int ugly: bool = False def print_name(self): print(self.name) def print_age(self): print(self.age) def print_id(self): print(f"The Name is {self.name} and {self.name} is {self.age} year old") @attr.s(auto_attribs=True) class Child(Parent): school: str ugly: bool = True
over 4 years ago · Santiago Trujillo Report

0

Puede usar atributos con valores predeterminados en las clases principales si los excluye de la función de inicio. Si necesita la posibilidad de anular el valor predeterminado en init, extienda el código con la respuesta de Praveen Kulkarni.

 from dataclasses import dataclass, field @dataclass class Parent: name: str age: int ugly: bool = field(default=False, init=False) @dataclass class Child(Parent): school: str jack = Parent('jack snr', 32) jack_son = Child('jack jnr', 12, school = 'havard') jack_son.ugly = True

O incluso

 @dataclass class Child(Parent): school: str ugly = True # This does not work # ugly: bool = True jack_son = Child('jack jnr', 12, school = 'havard') assert jack_son.ugly
over 4 years ago · Santiago Trujillo Report

0

Tenga en cuenta que con Python 3.10 , ahora es posible hacerlo de forma nativa con clases de datos.

Dataclasses 3.10 agregó el atributo kw_only (similar a attrs). Le permite especificar qué campos son solo palabras clave, por lo que se establecerán al final de init , sin causar un problema de herencia.

Tomando directamente de la publicación del blog de Eric Smith sobre el tema , hay dos razones por las que las personas solicitaban esta función:

  • Cuando una clase de datos tiene muchos campos, especificarlos por posición puede volverse ilegible. También requiere que para la compatibilidad con versiones anteriores, todos los campos nuevos se agreguen al final de la clase de datos. Esto no siempre es deseable.
  • Cuando una clase de datos hereda de otra clase de datos y la clase base tiene campos con valores predeterminados, todos los campos de la clase derivada también deben tener valores predeterminados.

Lo que sigue es la forma más sencilla de hacerlo con este nuevo argumento, pero hay varias formas de usarlo para usar la herencia con valores predeterminados en la clase principal:

 from dataclasses import dataclass @dataclass(kw_only=True) class Parent: name: str age: int ugly: bool = False @dataclass(kw_only=True) class Child(Parent): school: str ch = Child(name="Kevin", age=17, school="42") print(ch.ugly)

Eche un vistazo a la publicación de blog vinculada anteriormente para obtener una explicación más detallada de kw_only.

Salud !

PD: Como es bastante nuevo, tenga en cuenta que su IDE aún podría generar un posible error, pero funciona en tiempo de ejecución

over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!