7.4. Metaprogramming Type
7.4.1. SetUp
>>> from pprint import pprint
7.4.2. Recap
Type is a class
Type takes 1 or 3 arguments
>>> type(1)
<class 'int'>
>>> type(1.2)
<class 'float'>
>>> type(1,2)
Traceback (most recent call last):
TypeError: type() takes 1 or 3 arguments
>>> type((1,2))
<class 'tuple'>
7.4.3. Type Working Modes
type()
takes 1 or 3 argumentstype()
has two working modestype(obj)
returns a class of an objecttype()
takesstr
,tuple
,dict
type(name, bases, attrs)
creates a new class
First mode (one argument), will return a class of an object:
>>> type(1)
<class 'int'>
Second mode (three arguments), will create a new class:
>>> type('User', (), {})
<class '__main__.User'>
type()
creates classes. It is like a Higgs bozon
of Python. While Higgs bozon gives mass to the massless
particles, the type
gives life to the the non-existent
classes.
7.4.4. Class Name
First argument is a
str
with a name for a new classtype(name, bases, attrs)
creates a new class
The following code:
>>> User = type('User', (), {})
Is equivalent to:
>>> class User:
... pass
7.4.5. Class Bases
Second argument is a
tuple
with base classesMind, that one element
tuple
must have a comma
The following code:
>>> Account = type('Account', (), {})
>>> User = type('User', (Account,), {})
Is equivalent to:
>>> class Account:
... pass
>>>
>>> class User(Account):
... pass
7.4.6. Class Variables
Third argument is a
dict
with class variablesMind, that those are class variables
Class variables share state between all instances
The following code:
>>> User = type('User', (), {
... 'AGE_MIN': 18,
... 'AGE_MAX': 65,
... })
Is equivalent to:
>>> class User:
... AGE_MIN = 18
... AGE_MAX = 65
7.4.7. Class Methods
Third argument (
dict
) can also contains references to functionsThere is not a big difference, how Python distinguishes between class variables and class methods
This is the reason, why we collectively call them class attributes
This functions can take
self
as an argument, but this is not a requirementFunction names are not necessary to be the same as in the class
Dictionary keys decide, how the method will be called later on
The following code:
>>> def login():
... print('User login')
...
>>> def logout(self):
... print('User logout')
...
>>> def myinit(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
...
>>> User = type('User', (), {
... '__init__': myinit,
... 'login': login,
... 'logout': logout,
... })
Is equivalent to:
>>> class User:
... def __init__(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
...
... def login():
... print('User login')
...
... def logout(self):
... print('User logout')
7.4.8. All Together
The following code:
>>> def login():
... print('User login')
...
>>> def logout(self):
... print('User logout')
...
>>> def myinit(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
...
...
>>> Account = type('Account', (), {})
>>> User = type('User', (Account,), {
... 'AGE_MIN': 18,
... 'AGE_MAX': 65,
... '__init__': myinit,
... 'login': login,
... 'logout': logout,
... })
Is equivalent to:
>>> class Account:
... pass
...
>>> class User(Account):
... AGE_MIN = 18
... AGE_MAX = 65
...
... def __init__(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
...
... def login():
... print('User login')
...
... def logout(self):
... print('User logout')
7.4.9. What is a class?
>>> def login():
... print('User login')
...
>>> def logout(self):
... print('User logout')
...
>>> def myinit(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
...
...
>>> Account = type('Account', (), {})
>>> User = type('User', (Account,), {
... 'AGE_MIN': None,
... 'AGE_MAX': None,
... '__init__': myinit,
... 'login': login,
... 'logout': logout,
... })
>>> hex(id(None))
0x1062527f0
>>>
>>> hex(id(myinit))
0x103fb4540
>>>
>>> hex(id(login))
0x1064e3910
>>>
>>> hex(id(logout))
0x106082830
The class is effectively a dict with references to memory addresses of other objects: functions, variables, etc.
>>>
... User = {
... 'AGE_MIN': 0x1062527f0, # None
... 'AGE_MAX': 0x1062527f0, # None
... '__init__': 0x103fb4540, # myinit
... 'login': 0x1064e3910, # login
... 'logout': 0x106082830, # logout
... }
Mind, how similar this is to C language struct:
class User:
firstname: str
lastname: str
mark = User()
mark.firstname = "Mark"
mark.lastname = "Watney"
print(mark.firstname)
print(mark.lastname)
struct User {
char firstname[30];
char lastname[30];
};
mark = (struct User*) malloc(sizeof(struct User));
mark->firstname = "Mark";
mark->lastname = "Watney";
printf(mark->firstname);
printf(mark->lastname);
This is not a coincidence. Python is written in C language.
This makes Python a very powerful language, because it can
use all the power of C language. It can be also transpiled to
C for example with using cython
or mypyc
.
7.4.10. Dynamic Class Creation
Classes can be created dynamically
This is a powerful feature of Python
It allows to create classes on the fly
We don't have a class named User
:
>>> User()
Traceback (most recent call last):
NameError: name 'User' is not defined
We can create it dynamically together with other classes,
by using type()
. Registering it in globals()
namespace will allow us to use it later on:
>>> for classname in ['User', 'Staff', 'Admin']:
... globals()[classname] = type(classname, (), {})
Now we have a class named User
:
>>> User
<class '__main__.User'>
And we can create an instance of it:
>>> User()
<__main__.User object at 0x...>
We also have classes Staff
and Admin
.
7.4.11. Use Case - 1
Staticmethod and Classmethod
>>> class User:
... @staticmethod
... def login():
... print('User login')
...
... @classmethod
... def logout(cls):
... print('User logout')
Is equivalent to:
>>> def login():
... print('User login')
>>>
>>> def logout(cls):
... print('User logout')
>>>
>>> User = type('User', (), {
... 'login': staticmethod(login),
... 'logout': classmethod(logout),
... })
7.4.12. Use Case - 2
Dunder Methods
>>> class User:
... def __init__(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
...
... def __str__(self):
... return f'{self.firstname} {self.lastname}'
...
... def __repr__(self):
... clsname = self.__class__.__name__
... firstname = self.firstname
... lastname = self.lastname
... return f'{clsname}({firstname=}, {lastname=})'
Is equivalent to:
>>> def myinit(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
>>>
>>> def mystr(self):
... return f'{self.firstname} {self.lastname}'
>>>
>>> def myrepr(self):
... clsname = self.__class__.__name__
... firstname = self.firstname
... lastname = self.lastname
... return f'{clsname}({firstname=}, {lastname=})'
>>>
>>>
>>> User = type('User', (), {
... '__init__': myinit,
... '__str__': mystr,
... '__repr__': myrepr,
... })
Usage:
>>> mark = User('Mark', 'Watney')
>>>
>>> print(mark)
Mark Watney
>>>
>>> mark
User(firstname='Mark', lastname='Watney')
7.4.13. Use Case - 1
Create a class function
login
dynamically usinglambda
expression
>>> User = type('User', (), {
... 'firstname': None,
... 'lastname': None,
... 'login': lambda: print('User login'),
... })
>>> User.login()
User login
>>> result = vars(User)
>>> pprint(result)
mappingproxy({'__dict__': <attribute '__dict__' of 'User' objects>,
'__doc__': None,
'__module__': '__main__',
'__weakref__': <attribute '__weakref__' of 'User' objects>,
'firstname': None,
'lastname': None,
'login': <function <lambda> at 0x...>})
7.4.14. Use Case - 2
Create classes dynamically from data, using
species
as class names
>>> DATA = [
... ('sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species'),
... (5.8, 2.7, 5.1, 1.9, 'virginica'),
... (5.1, 3.5, 1.4, 0.2, 'setosa'),
... (5.7, 2.8, 4.1, 1.3, 'versicolor'),
... (6.3, 2.9, 5.6, 1.8, 'virginica'),
... (6.4, 3.2, 4.5, 1.5, 'versicolor'),
... (4.7, 3.2, 1.3, 0.2, 'setosa'),
... (7.0, 3.2, 4.7, 1.4, 'versicolor'),
... (7.6, 3.0, 6.6, 2.1, 'virginica'),
... ]
>>>
>>>
>>> class Iris:
... def __init__(self, **kwargs):
... self.__dict__ = kwargs
...
... def __repr__(self):
... clsname = self.__class__.__name__
... values = tuple(vars(self).values())
... return f'{clsname}{values}'
>>>
>>>
>>> header, *rows = DATA
>>> result = []
>>>
>>> for *features,species in rows:
... features = dict(zip(header, features))
... clsname = species.capitalize()
... if clsname not in globals():
... globals()[clsname] = type(clsname, (Iris,), {})
... cls = globals()[clsname]
... iris = cls(**features)
... result.append(iris)
>>>
>>> result
[Virginica(5.8, 2.7, 5.1, 1.9),
Setosa(5.1, 3.5, 1.4, 0.2),
Versicolor(5.7, 2.8, 4.1, 1.3),
Virginica(6.3, 2.9, 5.6, 1.8),
Versicolor(6.4, 3.2, 4.5, 1.5),
Setosa(4.7, 3.2, 1.3, 0.2),
Versicolor(7.0, 3.2, 4.7, 1.4),
Virginica(7.6, 3.0, 6.6, 2.1)]
>>>
>>> vars(result[0])
{'sepal_length': 5.8,
'sepal_width': 2.7,
'petal_length': 5.1,
'petal_width': 1.9}
7.4.15. Use Case - 3
Dynamic Classes 2
>>> from dataclasses import dataclass
>>> from itertools import zip_longest
>>>
>>>
>>> DATA = [
... ('sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species'),
... (5.8, 2.7, 'virginica'),
... (5.1, 3.5, 1.4, 0.2, 'setosa'),
... (5.7, 'versicolor'),
... (6.3, 2.9, 5.6, 1.8, 'virginica'),
... (6.4, 3.2, 4.5, 1.5, 'versicolor'),
... (4.7, 3.2, 1.3, 'setosa'),
... (7.0, 3.2, 4.7, 1.4, 'versicolor'),
... (7.6, 3.0, 'virginica'),
... ]
>>>
>>>
>>> @dataclass(init=False)
... class Iris:
... def __init__(self, **kwargs):
... self.__dict__ = kwargs
>>>
>>>
>>> result = []
>>> header, *rows = DATA
>>>
>>> for *features,species in rows:
... features = dict(zip_longest(header, features, fillvalue=None))
... clsname = species.capitalize()
... if clsname not in globals():
... globals()[clsname] = type(clsname, (Iris,), {})
... cls = globals()[clsname]
... iris = cls(**features)
... result.append(iris)
>>>
>>> result
[Virginica(5.8, 2.7, None, None, None),
Setosa(5.1, 3.5, 1.4, 0.2, None),
Versicolor(5.7, None, None, None, None),
Virginica(6.3, 2.9, 5.6, 1.8, None),
Versicolor(6.4, 3.2, 4.5, 1.5, None),
Setosa(4.7, 3.2, 1.3, None, None),
Versicolor(7.0, 3.2, 4.7, 1.4, None),
Virginica(7.6, 3.0, None, None, None)]
>>>
>>> vars(result[0])
{'sepal_length': 5.8,
'sepal_width': 2.7,
'petal_length': None,
'petal_width': None,
'species': None}
7.4.16. Use Case - 4
>>> from dataclasses import dataclass
>>>
>>>
>>> DATA = [
... ('sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species'),
... (5.8, 2.7, 5.1, 1.9, 'virginica'),
... (5.1, 3.5, 1.4, 0.2, 'setosa'),
... (5.7, 2.8, 4.1, 1.3, 'versicolor'),
... (6.3, 2.9, 5.6, 1.8, 'virginica'),
... (6.4, 3.2, 4.5, 1.5, 'versicolor'),
... (4.7, 3.2, 1.3, 0.2, 'setosa'),
... ]
>>>
>>>
>>> @dataclass
... class Iris:
... sepal_length: float
... sepal_width: float
... petal_length: float
... petal_width: float
>>>
>>>
>>> def factory(row):
... *features, species = row
... clsname = species.capitalize()
... cls = type(clsname, (Iris,), {})
... return cls(*features)
>>>
>>>
>>> result = map(factory, DATA[1:])
>>>
>>> list(result)
[Virginica(sepal_length=5.8, sepal_width=2.7, petal_length=5.1, petal_width=1.9),
Setosa(sepal_length=5.1, sepal_width=3.5, petal_length=1.4, petal_width=0.2),
Versicolor(sepal_length=5.7, sepal_width=2.8, petal_length=4.1, petal_width=1.3),
Virginica(sepal_length=6.3, sepal_width=2.9, petal_length=5.6, petal_width=1.8),
Versicolor(sepal_length=6.4, sepal_width=3.2, petal_length=4.5, petal_width=1.5),
Setosa(sepal_length=4.7, sepal_width=3.2, petal_length=1.3, petal_width=0.2)]
7.4.17. Use Case - 5
>>> from pprint import pprint
>>>
>>>
>>> DATA = [
... ('sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species'),
... (5.8, 2.7, 5.1, 1.9, 'virginica'),
... (5.1, 3.5, 1.4, 0.2, 'setosa'),
... (5.7, 2.8, 4.1, 1.3, 'versicolor'),
... (6.3, 2.9, 5.6, 1.8, 'virginica'),
... (6.4, 3.2, 4.5, 1.5, 'versicolor'),
... (4.7, 3.2, 1.3, 0.2, 'setosa'),
... (7.0, 3.2, 4.7, 1.4, 'versicolor'),
... (7.6, 3.0, 6.6, 2.1, 'virginica'),
... ]
>>>
>>>
>>> def myinit(self, sl, sw, pl, pw):
... self.sl = sl
... self.sw = sw
... self.pl = pl
... self.pw = pw
>>>
>>> def myrepr(self):
... clsname = self.__class__.__name__
... values = tuple(vars(self).values())
... return f'{clsname}{values}'
>>>
>>> iris = type('Iris', (), {'__init__': myinit, '__repr__': myrepr})
>>>
>>> result = [cls(*values)
... for *values, species in DATA[1:]
... if (clsname := species.capitalize())
... and (cls := type(clsname, (iris,), {}))]
>>>
>>>
>>> pprint(result)
[Virginica(5.8, 2.7, 5.1, 1.9),
Setosa(5.1, 3.5, 1.4, 0.2),
Versicolor(5.7, 2.8, 4.1, 1.3),
Virginica(6.3, 2.9, 5.6, 1.8),
Versicolor(6.4, 3.2, 4.5, 1.5),
Setosa(4.7, 3.2, 1.3, 0.2),
Versicolor(7.0, 3.2, 4.7, 1.4),
Virginica(7.6, 3.0, 6.6, 2.1)]
7.4.18. Use Case - 6
>>> from itertools import zip_longest
>>>
>>>
>>> DATA = [
... ('sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species'),
... (5.8, 2.7, 'virginica'),
... (5.1, 3.5, 1.4, 0.2, 'setosa'),
... (5.7, 'versicolor'),
... (6.3, 2.9, 5.6, 1.8, 'virginica'),
... (6.4, 3.2, 4.5, 1.5, 'versicolor'),
... (4.7, 3.2, 1.3, 'setosa'),
... (7.0, 3.2, 4.7, 1.4, 'versicolor'),
... (7.6, 3.0, 'virginica'),
... ]
>>>
>>>
>>> Iris = type('Iris', (), {
... '__init__': lambda self, **kwargs: self.__dict__.update(kwargs),
... '__repr__': lambda self: f'{self.__class__.__name__}{tuple(vars(self).values())}',
... })
>>>
>>> header, *rows = DATA
>>>
>>> result = [cls(**values)
... for *features,species in rows
... if (values := dict(zip_longest(header, features)))
... and (clsname := species.capitalize())
... and (cls := type(clsname, (Iris,), {}))]
>>> result
[Virginica(5.8, 2.7, None, None, None),
Setosa(5.1, 3.5, 1.4, 0.2, None),
Versicolor(5.7, None, None, None, None),
Virginica(6.3, 2.9, 5.6, 1.8, None),
Versicolor(6.4, 3.2, 4.5, 1.5, None),
Setosa(4.7, 3.2, 1.3, None, None),
Versicolor(7.0, 3.2, 4.7, 1.4, None),
Virginica(7.6, 3.0, None, None, None)]
>>> result[0]
Virginica(5.8, 2.7, None, None, None)
>>> vars(result[0])
{'sepal_length': 5.8,
'sepal_width': 2.7,
'petal_length': None,
'petal_width': None,
'species': None}
7.4.19. Use Case - 7
SetUp:
>>> del Iris
>>> del Setosa
>>> del Virginica
>>> del Versicolor
>>> del cls
>>> del values
>>> del species
>>> del iris
>>> del result
Code:
>>> DATA = [
... ('sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species'),
... (5.8, 2.7, 5.1, 1.9, 'virginica'),
... (5.1, 3.5, 1.4, 0.2, 'setosa'),
... (5.7, 2.8, 4.1, 1.3, 'versicolor'),
... (6.3, 2.9, 5.6, 1.8, 'virginica'),
... (6.4, 3.2, 4.5, 1.5, 'versicolor'),
... (4.7, 3.2, 1.3, 0.2, 'arctica'),
... (7.0, 3.2, 4.7, 1.4, 'versicolor'),
... (7.6, 3.0, 6.6, 2.1, 'virginica'),
... ]
>>>
>>>
>>> globals()['Iris'] = type('Iris', (), {
... '__init__': lambda self, *args: setattr(self, 'features', tuple(args)),
... '__repr__': lambda self: f'{self.__class__.__name__}{self.features}',
... })
>>>
>>>
>>> def iris(row):
... *values, species = row
... clsname = species.capitalize()
... if clsname not in globals():
... cls = type(clsname, (globals()['Iris'],), {})
... return cls(*values)
>>>
>>>
>>> result = map(iris, DATA[1:])
>>>
>>> list(result)
[Virginica(5.8, 2.7, 5.1, 1.9),
Setosa(5.1, 3.5, 1.4, 0.2),
Versicolor(5.7, 2.8, 4.1, 1.3),
Virginica(6.3, 2.9, 5.6, 1.8),
Versicolor(6.4, 3.2, 4.5, 1.5),
Arctica(4.7, 3.2, 1.3, 0.2),
Versicolor(7.0, 3.2, 4.7, 1.4),
Virginica(7.6, 3.0, 6.6, 2.1)]
7.4.20. Use Case - 8
>>> DATA = [
... ('sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species'),
... (5.8, 2.7, 5.1, 1.9, 'virginica'),
... (5.1, 3.5, 1.4, 0.2, 'setosa'),
... (5.7, 2.8, 4.1, 1.3, 'versicolor'),
... (6.3, 2.9, 5.6, 1.8, 'virginica'),
... (6.4, 3.2, 4.5, 1.5, 'versicolor'),
... (4.7, 3.2, 1.3, 0.2, 'arctica'),
... (7.0, 3.2, 4.7, 1.4, 'versicolor'),
... (7.6, 3.0, 6.6, 2.1, 'virginica'),
... ]
...
>>> header, *rows = DATA
...
>>> globals()['Iris'] = type('Iris', (), {
... '__init__': lambda self, **kwargs: self.__dict__.update(kwargs),
... '__str__': lambda self: f'{self.__class__.__name__}{tuple(vars(self).values())}',
... })
...
>>> def factory(row):
... *values, species = row
... clsname = species.capitalize()
... if clsname not in globals():
... globals()[clsname] = type(clsname, (globals()['Iris'],), {})
... cls = globals()[clsname]
... kwargs = dict(zip(header, values))
... return cls(**kwargs)
...
>>> for iris in map(factory, rows):
... print(iris)
...
Virginica(5.8, 2.7, 5.1, 1.9)
Setosa(5.1, 3.5, 1.4, 0.2)
Versicolor(5.7, 2.8, 4.1, 1.3)
Virginica(6.3, 2.9, 5.6, 1.8)
Versicolor(6.4, 3.2, 4.5, 1.5)
Arctica(4.7, 3.2, 1.3, 0.2)
Versicolor(7.0, 3.2, 4.7, 1.4)
Virginica(7.6, 3.0, 6.6, 2.1)
7.4.21. Use Case - 9
>>> def user(*args, **kwargs):
... clsname = 'User'
... if clsname not in globals():
... globals()[clsname] = type(clsname, (), {
... '__init__': lambda self, *args, **kwargs: setattr(self, '__dict__', kwargs),
... '__str__': lambda self: f'{self.firstname} {self.lastname}',
... '__repr__': lambda self: f'{self.__class__.__name__}({self.firstname}, {self.lastname}, {self.age})',
... })
... cls = globals()[clsname]
... return cls(*args, **kwargs)
>>>
>>>
>>> DATA = [
... {'firstname': 'Mark', 'lastname': 'Watney', 'age': 41},
... {'firstname': 'Melissa', 'lastname': 'Lewis', 'age': 40},
... {'firstname': 'Rick', 'lastname': 'Martinez', 'age': 39},
... {'firstname': 'Alex', 'lastname': 'Vogel', 'age': 40},
... {'firstname': 'Chris', 'lastname': 'Beck', 'age': 36},
... {'firstname': 'Beth', 'lastname': 'Johanssen', 'age': 29},
... ]
>>>
>>> data = DATA[0]
>>> user(**data)
User(Mark, Watney, 41)
7.4.22. Assignments
# %% License
# - Copyright 2025, Matt Harasymczuk <matt@python3.info>
# - This code can be used only for learning by humans
# - This code cannot be used for teaching others
# - This code cannot be used for teaching LLMs and AI algorithms
# - This code cannot be used in commercial or proprietary products
# - This code cannot be distributed in any form
# - This code cannot be changed in any form outside of training course
# - This code cannot have its license changed
# - If you use this code in your product, you must open-source it under GPLv2
# - Exception can be granted only by the author
# %% Run
# - PyCharm: right-click in the editor and `Run Doctest in ...`
# - PyCharm: keyboard shortcut `Control + Shift + F10`
# - Terminal: `python -m doctest -v myfile.py`
# %% About
# - Name: OOP ClassFactory Iris
# - Difficulty: medium
# - Lines: 8
# - Minutes: 8
# %% English
# 1. Define `result: list[Iris]`
# 2. Iterate over `DATA` rows (skip header - first row)
# 3. Separate `values` from `species` in each row
# 4. Append to `result`:
# - if `species` is "setosa" append an instance of a class `Setosa`
# - if `species` is "versicolor" append an instance of a class `Versicolor`
# - if `species` is "virginica" append an instance of a class `Virginica`
# 5. Initialize instances with `values` using `*args` notation
# 6. Run doctests - all must succeed
# %% Polish
# 1. Zdefiniuj `result: list[Iris]`
# 2. Iteruj po wierszach w `DATA` (pomiń nagłówek - pierwszy wiersz)
# 3. Odseparuj `values` od `species` w każdym wierszu
# 4. Dodaj do `result`:
# - jeżeli `species` jest "setosa" to dodaj instancję klasy `Setosa`
# - jeżeli `species` jest "versicolor" to dodaj instancję klasy `Versicolor`
# - jeżeli `species` jest "virginica" to dodaj instancję klasy `Virginica`
# 5. Instancje inicjalizuj danymi z `values` używając notacji `*args`
# 6. Uruchom doctesty - wszystkie muszą się powieść
# %% Hints
# - `globals()[classname]`
# %% Tests
"""
>>> import sys; sys.tracebacklimit = 0
>>> assert sys.version_info >= (3, 9), \
'Python 3.9+ required'
>>> list(result) # doctest: +NORMALIZE_WHITESPACE
[Virginica(5.8, 2.7, 5.1, 1.9),
Setosa(5.1, 3.5, 1.4, 0.2),
Versicolor(5.7, 2.8, 4.1, 1.3),
Virginica(6.3, 2.9, 5.6, 1.8),
Versicolor(6.4, 3.2, 4.5, 1.5),
Setosa(4.7, 3.2, 1.3, 0.2)]
"""
from dataclasses import dataclass
DATA = [
('sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species'),
(5.8, 2.7, 5.1, 1.9, 'virginica'),
(5.1, 3.5, 1.4, 0.2, 'setosa'),
(5.7, 2.8, 4.1, 1.3, 'versicolor'),
(6.3, 2.9, 5.6, 1.8, 'virginica'),
(6.4, 3.2, 4.5, 1.5, 'versicolor'),
(4.7, 3.2, 1.3, 0.2, 'setosa'),
]
header, *rows = DATA
class Iris:
def __init__(self, sepal_length, sepal_width,
petal_length, petal_width):
self.sepal_length = sepal_length
self.sepal_width = sepal_width
self.petal_length = petal_length
self.petal_width = petal_width
def __repr__(self):
name = self.__class__.__name__
args = tuple(self.__dict__.values())
return f'{name}{args}'
# Append to `result`:
# Use type() to create classes dynamically
# - if `species` is "setosa" append an instance of a class `Setosa`
# - if `species` is "versicolor" append an instance of a class `Versicolor`
# - if `species` is "virginica" append an instance of a class `Virginica`
# type: list[Iris]
result = ...