8. POO II : Construire des classes#
8.1. Vue d’ensemble#
Dans un cours précédent, nous avons appris quelques fondements de la programmation orientée objet.
Les objectifs de ce cours sont les suivants
aborder la POO plus en profondeur
apprendre à construire nos propres objets, spécialisés selon nos besoins
Par exemple, vous savez déjà comment
créer des listes, des chaînes de caractères et d’autres objets Python
utiliser leurs méthodes pour modifier leur contenu
Imaginez maintenant que vous vouliez écrire un programme avec des consommateurs, qui peuvent
détenir et dépenser de l’argent
consommer des biens
travailler et gagner de l’argent
Une solution naturelle en Python consisterait à créer des consommateurs sous forme d’objets ayant
des données, telles que l’argent disponible
des méthodes, telles que
buyouworkqui agissent sur ces données
Python rend cela facile, en vous fournissant des définitions de classes.
Les classes sont des plans qui vous aident à construire des objets selon vos propres spécifications.
Il faut un peu de temps pour s’habituer à la syntaxe, c’est pourquoi nous fournirons de nombreux exemples.
Nous utiliserons les importations suivantes :
import numpy as np
import matplotlib.pyplot as plt
8.2. Révision de la POO#
La POO est prise en charge dans de nombreux langages :
JAVA et Ruby sont des langages relativement purement orientés objet.
Python prend en charge à la fois la programmation procédurale et orientée objet.
Fortran et MATLAB sont principalement procéduraux, avec quelques ajouts récents de POO.
C est un langage procédural, tandis que C++ est du C avec la POO ajoutée par-dessus.
Abordons les concepts généraux de la POO avant de nous spécialiser sur Python.
8.2.1. Concepts clés#
Comme nous l’avons vu dans un cours précédent, dans le paradigme de la POO, les données et les fonctions sont regroupées en « objets ».
Un exemple est une liste Python, qui non seulement stocke des données mais sait aussi comment se trier elle-même, etc.
x = [1, 5, 4]
x.sort()
x
[1, 4, 5]
Comme nous le savons maintenant, sort est une fonction qui « fait partie » de l’objet liste — et donc appelée une méthode.
Si nous voulons créer nos propres types d’objets, nous devons utiliser des définitions de classes.
Une définition de classe est un plan pour une classe particulière d’objets (par exemple, des listes, des chaînes de caractères ou des nombres complexes).
Elle décrit
Quel type de données la classe stocke
Quelles méthodes elle possède pour agir sur ces données
Un objet ou instance est une réalisation de la classe, créée à partir du plan
Chaque instance possède ses propres données uniques.
Les méthodes définies dans la définition de classe agissent sur ces données (et d’autres).
En Python, les données et les méthodes d’un objet sont collectivement appelées attributs.
Les attributs sont accessibles via la « notation d’attribut par point »
object_name.dataobject_name.method_name()
Dans l’exemple
x = [1, 5, 4]
x.sort()
x.__class__
list
xest un objet ou une instance, créé à partir de la définition des listes Python, mais avec ses propres données particulières.x.sort()etx.__class__sont deux attributs dex.dir(x)peut être utilisé pour afficher tous les attributs dex.
8.2.2. Pourquoi la POO est-elle utile ?#
La POO est utile pour la même raison que l’abstraction est utile : pour reconnaître et exploiter la structure commune.
Par exemple,
une chaîne de Markov consiste en un ensemble d’états, une distribution de probabilité initiale sur les états, et une collection de probabilités de passage d’un état à un autre
une théorie de l’équilibre général consiste en un espace de biens, des préférences, des technologies, et une définition d’équilibre
un jeu consiste en une liste de joueurs, des listes d’actions disponibles pour chaque joueur, les gains de chaque joueur en fonction des actions de tous les autres joueurs, et un protocole de timing
Ce sont toutes des abstractions qui regroupent des « objets » du même « type ».
Reconnaître une structure commune nous permet d’employer des outils communs.
En théorie économique, il peut s’agir d’une proposition qui s’applique à tous les jeux d’un certain type.
En Python, il peut s’agir d’une méthode utile pour toutes les chaînes de Markov (par exemple, simulate).
Lorsque nous utilisons la POO, la méthode simulate est commodément regroupée avec l’objet chaîne de Markov.
8.3. Définir vos propres classes#
Construisons quelques classes simples pour commencer.
Avant de le faire, afin d’illustrer une partie de la puissance des classes, nous allons définir deux fonctions que nous appellerons earn et spend.
def earn(w,y):
"Consumer with inital wealth w earns y"
return w+y
def spend(w,x):
"consumer with initial wealth w spends x"
new_wealth = w -x
if new_wealth < 0:
print("Insufficient funds")
else:
return new_wealth
La fonction earn prend la richesse initiale d’un consommateur \(w\) et y ajoute ses gains actuels \(y\).
La fonction spend prend la richesse initiale d’un consommateur \(w\) et en déduit ses dépenses actuelles \(x\).
Nous pouvons utiliser ces deux fonctions pour suivre la richesse d’un consommateur au fur et à mesure qu’il gagne et dépense.
Par exemple
w0=100
w1=earn(w0,10)
w2=spend(w1,20)
w3=earn(w2,10)
w4=spend(w3,20)
print("w0,w1,w2,w3,w4 = ", w0,w1,w2,w3,w4)
w0,w1,w2,w3,w4 = 100 110 90 100 80
Une classe regroupe un ensemble de données liées à une instance particulière avec une collection de fonctions qui opèrent sur ces données.
Dans notre exemple, une instance sera le nom d’une personne particulière dont les données d’instance consistent uniquement en sa richesse.
(Dans d’autres exemples, les données d’instance consisteront en un vecteur de données.)
Dans notre exemple, deux fonctions earn et spend peuvent être appliquées aux données d’instance actuelles.
Prises ensemble, les données d’instance et les fonctions sont appelées attributs.
Ceux-ci sont facilement accessibles de la manière que nous allons décrire maintenant.
8.3.1. Exemple : une classe Consommateur#
Nous allons construire une classe Consumer avec
un attribut
wealthqui stocke la richesse du consommateur (données)une méthode
earn, oùearn(y)augmente la richesse du consommateur deyune méthode
spend, oùspend(x)soit diminue la richesse dex, soit renvoie une erreur si les fonds sont insuffisants
Certes un peu artificiel, cet exemple de classe nous aide à intérioriser une syntaxe particulière.
Voici comment nous mettons en place notre classe Consommateur.
class Consumer:
def __init__(self, w):
"Initialize consumer with w dollars of wealth"
self.wealth = w
def earn(self, y):
"The consumer earns y dollars"
self.wealth += y
def spend(self, x):
"The consumer spends x dollars if feasible"
new_wealth = self.wealth - x
if new_wealth < 0:
print("Insufficent funds")
else:
self.wealth = new_wealth
Il y a une syntaxe spéciale ici, alors examinons-la attentivement
Le mot-clé
classindique que nous construisons une classe.
La classe Consumer définit les données d’instance wealth et trois méthodes : __init__, earn et spend
wealthest une donnée d’instance car chaque consommateur que nous créons (chaque instance de la classeConsumer) aura ses propres données de richesse.
Les méthodes earn et spend déploient les fonctions que nous avons décrites précédemment et qui peuvent potentiellement être appliquées aux données d’instance wealth.
La méthode __init__ est une méthode constructeur.
Chaque fois que nous créons une instance de la classe, la méthode __init_ sera appelée automatiquement.
L’appel de __init__ met en place un « espace de noms » pour contenir les données d’instance — nous y reviendrons bientôt.
Nous discuterons également en détail ci-dessous du rôle du particulier dispositif de gestion self.
8.3.1.1. Utilisation#
Voici un exemple dans lequel nous utilisons la classe Consumer pour créer une instance d’un consommateur que nous nommons affectueusement \(c1\).
Après avoir créé le consommateur \(c1\) et l’avoir doté d’une richesse initiale de \(10\), nous appliquerons la méthode spend.
c1 = Consumer(10) # Create instance with initial wealth 10
c1.spend(5)
c1.wealth
5
c1.earn(15)
c1.spend(100)
Insufficent funds
Nous pouvons bien sûr créer plusieurs instances, c’est-à-dire plusieurs consommateurs, chacun avec son propre nom et ses propres données
c1 = Consumer(10)
c2 = Consumer(12)
c2.spend(4)
c2.wealth
8
c1.wealth
10
Chaque instance, c’est-à-dire chaque consommateur, stocke ses données dans un dictionnaire d’espace de noms distinct
c1.__dict__
{'wealth': 10}
c2.__dict__
{'wealth': 8}
Lorsque nous accédons aux attributs ou les définissons, nous modifions en réalité simplement le dictionnaire maintenu par l’instance.
8.3.1.2. Self#
Si vous regardez à nouveau la définition de la classe Consumer, vous verrez le mot self tout au long du code.
Les règles d’utilisation de self lors de la création d’une classe sont les suivantes
Toute donnée d’instance doit être préfixée par
selfpar exemple, la méthode
earnutiliseself.wealthplutôt que simplementwealth
Une méthode définie dans le code qui définit la classe doit avoir
selfcomme premier argumentpar exemple,
def earn(self, y)plutôt que simplementdef earn(y)
Toute méthode référencée dans la classe doit être appelée comme
self.method_name
Il n’y a pas d’exemples de la dernière règle dans le code précédent, mais nous en verrons quelques-uns sous peu.
8.3.1.3. Détails#
Dans cette section, nous examinons quelques détails plus formels liés aux classes et à self
Vous pourriez souhaiter passer directement à la section suivante lors de votre première lecture de ce cours.
Vous pourrez revenir à ces détails après vous être familiarisé avec davantage d’exemples.
Les méthodes vivent en réalité à l’intérieur d’un objet de classe formé lorsque l’interpréteur lit la définition de la classe
print(Consumer.__dict__) # Show __dict__ attribute of class object
{'__module__': '__main__', '__firstlineno__': 1, '__init__': <function Consumer.__init__ at 0x7f4f84910540>, 'earn': <function Consumer.earn at 0x7f4f84910680>, 'spend': <function Consumer.spend at 0x7f4f84910720>, '__static_attributes__': ('wealth',), '__dict__': <attribute '__dict__' of 'Consumer' objects>, '__weakref__': <attribute '__weakref__' of 'Consumer' objects>, '__doc__': None}
Notez comment les trois méthodes __init__, earn et spend sont stockées dans l’objet de classe.
Considérons le code suivant
c1 = Consumer(10)
c1.earn(10)
c1.wealth
20
Lorsque vous appelez earn via c1.earn(10), l’interpréteur passe l’instance c1 et l’argument 10 à Consumer.earn.
En fait, les deux suivants sont équivalents
c1.earn(10)Consumer.earn(c1, 10)
Dans l’appel de fonction Consumer.earn(c1, 10), notez que c1 est le premier argument.
Rappelez-vous que dans la définition de la méthode earn, self est le premier paramètre
def earn(self, y):
"The consumer earns y dollars"
self.wealth += y
Le résultat final est que self est lié à l’instance c1 à l’intérieur de l’appel de fonction.
C’est pourquoi l’instruction self.wealth += y à l’intérieur de earn finit par modifier c1.wealth.
8.3.2. Exemple : le modèle de croissance de Solow#
Pour notre prochain exemple, écrivons une classe simple pour implémenter le modèle de croissance de Solow.
Le modèle de croissance de Solow est un modèle de croissance néoclassique dans lequel le stock de capital par habitant \(k_t\) évolue selon la règle
Ici
\(s\) est un taux d’épargne donné de manière exogène
\(z\) est un paramètre de productivité
\(\alpha\) est la part du capital dans le revenu
\(n\) est le taux de croissance de la population
\(\delta\) est le taux de dépréciation
Un état stationnaire du modèle est un \(k\) qui résout (8.1) lorsque \(k_{t+1} = k_t = k\).
Voici une classe qui implémente ce modèle.
Quelques points intéressants dans le code sont
Une instance conserve un enregistrement de son stock de capital actuel dans la variable
self.k.La méthode
himplémente le membre de droite de (8.1).La méthode
updateutilisehpour mettre à jour le capital conformément à (8.1).Remarquez comment, à l’intérieur de
update, la référence à la méthode localehestself.h.
Les méthodes steady_state et generate_sequence sont assez explicites
class Solow:
r"""
Implements the Solow growth model with the update rule
k_{t+1} = [(s z k^α_t) + (1 - δ)k_t] /(1 + n)
"""
def __init__(self, n=0.05, # population growth rate
s=0.25, # savings rate
δ=0.1, # depreciation rate
α=0.3, # share of labor
z=2.0, # productivity
k=1.0): # current capital stock
self.n, self.s, self.δ, self.α, self.z = n, s, δ, α, z
self.k = k
def h(self):
"Evaluate the h function"
# Unpack parameters (get rid of self to simplify notation)
n, s, δ, α, z = self.n, self.s, self.δ, self.α, self.z
# Apply the update rule
return (s * z * self.k**α + (1 - δ) * self.k) / (1 + n)
def update(self):
"Update the current state (i.e., the capital stock)."
self.k = self.h()
def steady_state(self):
"Compute the steady state value of capital."
# Unpack parameters (get rid of self to simplify notation)
n, s, δ, α, z = self.n, self.s, self.δ, self.α, self.z
# Compute and return steady state
return ((s * z) / (n + δ))**(1 / (1 - α))
def generate_sequence(self, t):
"Generate and return a time series of length t"
path = []
for i in range(t):
path.append(self.k)
self.update()
return path
Voici un petit programme qui utilise la classe pour calculer des séries temporelles à partir de deux conditions initiales différentes.
L’état stationnaire commun est également tracé à des fins de comparaison
s1 = Solow()
s2 = Solow(k=8.0)
T = 60
fig, ax = plt.subplots(figsize=(9, 6))
# Trace la valeur commune de l'état stationnaire du capital
ax.plot([s1.steady_state()]*T, 'k-', label='état stationnaire')
# Trace les séries temporelles pour chaque économie
for s in s1, s2:
lb = f'série du capital à partir de l\'état initial {s.k}'
ax.plot(s.generate_sequence(T), 'o-', lw=2, alpha=0.6, label=lb)
ax.set_xlabel('$t$', fontsize=14)
ax.set_ylabel('$k_t$', fontsize=14)
ax.legend()
plt.show()
8.3.3. Exemple : un marché#
Ensuite, écrivons une classe pour un marché concurrentiel dans lequel les acheteurs et les vendeurs sont tous deux preneurs de prix.
Le marché consiste en les objets suivants :
Une courbe de demande linéaire \(Q = a_d - b_d p\)
Une courbe d’offre linéaire \(Q = a_z + b_z (p - t)\)
Ici
\(p\) est le prix payé par l’acheteur, \(Q\) est la quantité et \(t\) est une taxe unitaire.
Les autres symboles sont des paramètres de demande et d’offre.
La classe fournit des méthodes pour calculer diverses valeurs d’intérêt, y compris le prix et la quantité d’équilibre concurrentiel, les recettes fiscales collectées, le surplus du consommateur et le surplus du producteur.
Voici notre implémentation.
(Elle utilise une fonction de SciPy appelée quad pour l’intégration numérique — un sujet dont nous parlerons davantage plus tard.)
from scipy.integrate import quad
class Market:
def __init__(self, ad, bd, az, bz, tax):
"""
Set up market parameters. All parameters are scalars. See
https://lectures.quantecon.org/py/python_oop.html for interpretation.
"""
self.ad, self.bd, self.az, self.bz, self.tax = ad, bd, az, bz, tax
if ad < az:
raise ValueError('Insufficient demand.')
def price(self):
"Compute equilibrium price"
return (self.ad - self.az + self.bz * self.tax) / (self.bd + self.bz)
def quantity(self):
"Compute equilibrium quantity"
return self.ad - self.bd * self.price()
def consumer_surp(self):
"Compute consumer surplus"
# == Compute area under inverse demand function == #
integrand = lambda x: (self.ad / self.bd) - (1 / self.bd) * x
area, error = quad(integrand, 0, self.quantity())
return area - self.price() * self.quantity()
def producer_surp(self):
"Compute producer surplus"
# == Compute area above inverse supply curve, excluding tax == #
integrand = lambda x: -(self.az / self.bz) + (1 / self.bz) * x
area, error = quad(integrand, 0, self.quantity())
return (self.price() - self.tax) * self.quantity() - area
def taxrev(self):
"Compute tax revenue"
return self.tax * self.quantity()
def inverse_demand(self, x):
"Compute inverse demand"
return self.ad / self.bd - (1 / self.bd)* x
def inverse_supply(self, x):
"Compute inverse supply curve"
return -(self.az / self.bz) + (1 / self.bz) * x + self.tax
def inverse_supply_no_tax(self, x):
"Compute inverse supply curve without tax"
return -(self.az / self.bz) + (1 / self.bz) * x
Voici un exemple d’utilisation
baseline_params = 15, .5, -2, .5, 3
m = Market(*baseline_params)
print("equilibrium price = ", m.price())
equilibrium price = 18.5
print("consumer surplus = ", m.consumer_surp())
consumer surplus = 33.0625
Voici un court programme qui utilise cette classe pour tracer une courbe de demande inverse avec des courbes d’offre inverse avec et sans taxes
# Baseline ad, bd, az, bz, tax
baseline_params = 15, .5, -2, .5, 3
m = Market(*baseline_params)
q_max = m.quantity() * 2
q_grid = np.linspace(0.0, q_max, 100)
pd = m.inverse_demand(q_grid)
ps = m.inverse_supply(q_grid)
psno = m.inverse_supply_no_tax(q_grid)
fig, ax = plt.subplots()
ax.plot(q_grid, pd, lw=2, alpha=0.6, label='demande')
ax.plot(q_grid, ps, lw=2, alpha=0.6, label='offre')
ax.plot(q_grid, psno, '--k', lw=2, alpha=0.6, label='offre sans taxe')
ax.set_xlabel('quantité', fontsize=14)
ax.set_xlim(0, q_max)
ax.set_ylabel('prix', fontsize=14)
ax.legend(loc='lower right', frameon=False, fontsize=14)
plt.show()
Le programme suivant fournit une fonction qui
prend une instance de
Marketcomme paramètrecalcule la perte sèche résultant de l’imposition de la taxe
def deadw(m):
"Computes deadweight loss for market m."
# == Create analogous market with no tax == #
m_no_tax = Market(m.ad, m.bd, m.az, m.bz, 0)
# == Compare surplus, return difference == #
surp1 = m_no_tax.consumer_surp() + m_no_tax.producer_surp()
surp2 = m.consumer_surp() + m.producer_surp() + m.taxrev()
return surp1 - surp2
Voici un exemple d’utilisation
baseline_params = 15, .5, -2, .5, 3
m = Market(*baseline_params)
deadw(m) # Show deadweight loss
1.125
8.3.4. Exemple : le chaos#
Examinons un exemple de plus, lié à la dynamique chaotique dans les systèmes non linéaires.
Une règle de transition simple qui peut générer des trajectoires temporelles erratiques est la carte logistique
Écrivons une classe pour générer des séries temporelles à partir de ce modèle.
Voici une implémentation
class Chaos:
"""
Models the dynamical system :math:`x_{t+1} = r x_t (1 - x_t)`
"""
def __init__(self, x0, r):
"""
Initialize with state x0 and parameter r
"""
self.x, self.r = x0, r
def update(self):
"Apply the map to update state."
self.x = self.r * self.x *(1 - self.x)
def generate_sequence(self, n):
"Generate and return a sequence of length n."
path = []
for i in range(n):
path.append(self.x)
self.update()
return path
Voici un exemple d’utilisation
ch = Chaos(0.1, 4.0) # x0 = 0.1 and r = 0.4
ch.generate_sequence(5) # First 5 iterates
[0.1, 0.36000000000000004, 0.9216, 0.28901376000000006, 0.8219392261226498]
Ce morceau de code trace une trajectoire plus longue
ch = Chaos(0.1, 4.0)
ts_length = 250
fig, ax = plt.subplots()
ax.set_xlabel('$t$', fontsize=14)
ax.set_ylabel('$x_t$', fontsize=14)
x = ch.generate_sequence(ts_length)
ax.plot(range(ts_length), x, 'bo-', alpha=0.5, lw=2, label='$x_t$')
plt.show()
Le morceau de code suivant fournit un diagramme de bifurcation
fig, ax = plt.subplots()
ch = Chaos(0.1, 4)
r = 2.5
while r < 4:
ch.r = r
t = ch.generate_sequence(1000)[950:]
ax.plot([r] * len(t), t, 'b.', ms=0.6)
r = r + 0.005
ax.set_xlabel('$r$', fontsize=16)
ax.set_ylabel('$x_t$', fontsize=16)
plt.show()
Sur l’axe horizontal se trouve le paramètre \(r\) dans (8.2).
L’axe vertical est l’espace d’états \([0, 1]\).
Pour chaque \(r\), nous calculons une longue série temporelle puis traçons la queue (les 50 derniers points).
La queue de la séquence nous montre où la trajectoire se concentre après s’être stabilisée dans une sorte d’état stationnaire, si un état stationnaire existe.
Le fait qu’elle se stabilise, et le caractère de l’état stationnaire vers lequel elle se stabilise, dépendent de la valeur de \(r\).
Pour \(r\) entre environ 2,5 et 3, la série temporelle se stabilise en un point fixe unique tracé sur l’axe vertical.
Pour \(r\) entre environ 3 et 3,45, la série temporelle se stabilise en oscillant entre les deux valeurs tracées sur l’axe vertical.
Pour \(r\) un peu plus élevé que 3,45, la série temporelle se stabilise en oscillant entre les quatre valeurs tracées sur l’axe vertical.
Remarquez qu’il n’y a aucune valeur de \(r\) qui conduit à un état stationnaire oscillant entre trois valeurs.
8.4. Méthodes spéciales#
Python fournit des méthodes spéciales qui s’avèrent pratiques.
Par exemple, rappelez-vous que les listes et les tuples ont une notion de longueur et que cette longueur peut être interrogée via la fonction len
x = (10, 20)
len(x)
2
Si vous souhaitez fournir une valeur de retour pour la fonction len lorsqu’elle est appliquée à votre objet défini par l’utilisateur, utilisez la méthode spéciale __len__
class Foo:
def __len__(self):
return 42
Maintenant nous obtenons
f = Foo()
len(f)
42
Une méthode spéciale que nous utiliserons régulièrement est la méthode __call__.
Cette méthode peut être utilisée pour rendre vos instances appelables, tout comme des fonctions
class Foo:
def __call__(self, x):
return x + 42
Après l’exécution nous obtenons
f = Foo()
f(8) # Exactly equivalent to f.__call__(8)
50
L’exercice 1 fournit un exemple plus utile.
8.5. Exercices#
Exercice 8.1
La fonction de répartition empirique (ecdf) correspondant à un échantillon \(\{X_i\}_{i=1}^n\) est définie comme
Ici \(\mathbf{1}\{X_i \leq x\}\) est une fonction indicatrice (un si \(X_i \leq x\) et zéro sinon) et donc \(F_n(x)\) est la fraction de l’échantillon qui tombe en dessous de \(x\).
Le théorème de Glivenko-Cantelli stipule que, à condition que l’échantillon soit i.i.d., l’ecdf \(F_n\) converge vers la véritable fonction de répartition \(F\).
Implémentez \(F_n\) sous forme d’une classe appelée ECDF, où
Un échantillon donné \(\{X_i\}_{i=1}^n\) sont les données d’instance, stockées sous
self.observations.La classe implémente une méthode
__call__qui renvoie \(F_n(x)\) pour tout \(x\).
Votre code devrait fonctionner comme suit (au hasard près)
from random import uniform
samples = [uniform(0, 1) for i in range(10)]
F = ECDF(samples)
F(0.5) # Evaluate ecdf at x = 0.5
F.observations = [uniform(0, 1) for i in range(1000)]
F(0.5)
Visez la clarté, pas l’efficacité.
Solution
class ECDF:
def __init__(self, observations):
self.observations = observations
def __call__(self, x):
counter = 0.0
for obs in self.observations:
if obs <= x:
counter += 1
return counter / len(self.observations)
# == test == #
from random import uniform
samples = [uniform(0, 1) for i in range(10)]
F = ECDF(samples)
print(F(0.5)) # Evaluate ecdf at x = 0.5
F.observations = [uniform(0, 1) for i in range(1000)]
print(F(0.5))
0.7
0.484
Exercice 8.2
Dans un exercice précédent, vous avez écrit une fonction pour évaluer des polynômes.
Cet exercice est une extension, où la tâche consiste à construire une classe simple appelée Polynomial pour représenter et manipuler des fonctions polynomiales telles que
Les données d’instance de la classe Polynomial seront les coefficients (dans le cas de (8.4), les nombres \(a_0, \ldots, a_N\)).
Fournissez des méthodes qui
Évaluent le polynôme (8.4), renvoyant \(p(x)\) pour tout \(x\).
Différencient le polynôme, en remplaçant les coefficients d’origine par ceux de sa dérivée \(p'\).
Évitez d’utiliser toute instruction import.
Solution
class Polynomial:
def __init__(self, coefficients):
"""
Creates an instance of the Polynomial class representing
p(x) = a_0 x^0 + ... + a_N x^N,
where a_i = coefficients[i].
"""
self.coefficients = coefficients
def __call__(self, x):
"Evaluate the polynomial at x."
y = 0
for i, a in enumerate(self.coefficients):
y += a * x**i
return y
def differentiate(self):
"Reset self.coefficients to those of p' instead of p."
new_coefficients = []
for i, a in enumerate(self.coefficients):
new_coefficients.append(i * a)
# Remove the first element, which is zero
del new_coefficients[0]
# And reset coefficients data to new values
self.coefficients = new_coefficients
return new_coefficients