Iată un truc Python îngrijit pe care l-ați putea găsi util într-o zi. Să ne uităm la modul în care puteți defini în mod dinamic clase și să creăm instanțe ale acestora după cum este necesar.

Acest truc folosește capabilitățile de programare orientate pe obiecte (OOP) ale Python, așa că le vom analiza mai întâi.

Conţinut

Clase și obiecte

Python este un limbaj orientat obiect, ceea ce înseamnă că vă permite să scrieți cod în paradigmă orientată obiect.

Conceptul cheie în această paradigmă de programare este clasele. În Python, acestea sunt utilizate pentru a crea obiecte care pot avea atribute.

Obiectele sunt instanțe specifice unei clase. O clasă este în esență un plan al ceea ce este un obiect și cum ar trebui să se comporte.

ad-banner

Clasele sunt definite cu două tipuri de atribute:

  • Atribute de date – variabile disponibile pentru o instanță dată a clasei respective
  • Metode – funcții disponibile unei instanțe din acea clasă

Exemplul clasic OOP implică de obicei diferite tipuri de animale sau alimente. Aici, am devenit mai practic cu o temă simplă de vizualizare a datelor.

Mai întâi, definiți clasa BarChart.

class BarChart:
	def __init__(self, title, data):
    	self.title = title
    	self.data = data
   	def plot(self):
    	print("n"+self.title)
        for k in self.data.keys():
        	print("-"*self.data[k]+" "+k)

__init__ metoda vă permite să setați atribute la instanțierea. Adică atunci când creați o nouă instanță de BarChart, puteți transmite argumente care furnizează titlul și datele graficului.

Această clasă are și o plot() metodă. Aceasta imprimă o diagramă de bare foarte simplă pe consolă atunci când este apelată. Ar putea face lucruri mai interesante într-o aplicație reală.

Apoi, creați o instanță de BarChart:

data = {"a":4, "b":7, "c":8}bar = BarChart("A Simple Chart", data)

Acum puteți utiliza bar obiect în restul codului:

bar.data['d'] = bar.plot()
A Simple Chart
---- a
------- b
-------- c
----- d

Acest lucru este minunat, deoarece vă permite să definiți o clasă și să creați instanțe dinamic. Puteți roti instanțe ale altor diagrame cu bare într-o singură linie de cod.

new_data = {"x":1, "y":2, "z":3}
bar2 = BarChart("Another Chart", new_data)
bar2.plot()
Another Chart
- x
-- y
--- z

Spuneți că ați dorit să definiți mai multe clase de diagrame. Moştenire vă permite să definiți clase care „moștenesc” proprietăți din clasele de bază.

De exemplu, ați putea defini o bază Chart clasă. Apoi puteți defini clase derivate care moștenesc de la bază.

class Chart:
	def __init__(self, title, data):
    	self.title = title
        self.data = data
    def plot(self):
    	pass
class BarChart(Chart):
	def plot(self):
    	print("n"+self.title)
        for k in self.data.keys():
        	print("-"*self.data[k]+" "+k)
class Scatter(Chart):
	def plot(self):
    	points = zip(data['x'],data['y'])
        y = max(self.data['y'])+1
        x = max(self.data['x'])+1
        print("n"+self.title)
        for i in range(y,-1,-1):
        	line = str(i)+"|"
            for j in range(x):
            	if (j,i) in points:
                	line += "X"
                else:
                	line += " "
            print(line)

Aici Chart clasa este o clasă de bază. BarChart și Scatter clasele moștenesc __init__() metoda din Chart. Dar au propriile lor plot() metode care îl anulează pe cel definit în Chart.

Acum puteți crea și obiecte de diagramă scatter.

data = {'x':[1,2,4,5], 'y':[1,2,3,4]}
scatter = Scatter('Scatter Chart', data)
scatter.plot()
Scatter Chart
4|     X
3|	  X 
2|  X
1| X
0|

Această abordare vă permite să scrieți cod mai abstract, oferind aplicației dvs. o mai mare flexibilitate. Dacă aveți planuri pentru a crea nenumărate variații ale aceluiași obiect general, veți economisi repetarea inutilă a liniilor de cod. De asemenea, vă poate ușura înțelegerea codului aplicației.

De asemenea, puteți importa clase în proiecte viitoare, dacă doriți să le refolosiți ulterior.

Metode din fabrică

Uneori, nu veți cunoaște clasa specifică pe care doriți să o implementați înainte de runtime. De exemplu, poate că obiectele pe care le creați vor depinde de introducerea utilizatorului sau de rezultatele unui alt proces cu un rezultat variabil.

Metode din fabrică ofera o solutie. Acestea sunt metode care iau o listă dinamică de argumente și returnează un obiect. Argumentele furnizate determină clasa obiectului returnat.

Un exemplu simplu este ilustrat mai jos. Această fabrică poate returna fie o diagramă cu bare, fie un obiect de diagramă scatter, în funcție de style argument pe care îl primește. O metodă mai inteligentă din fabrică ar putea chiar ghici cea mai bună clasă de utilizat, analizând structura data argument.

def chart_factory(title, data, style):
	if style == "bar":
    	return BarChart(title, data)
    if style == "scatter":
    	return Scatter(title, data)
    else:
    	raise Exception("Unrecognized chart style.")
        
    
chart = chart_factory("New Chart", data, "bar")
chart.plot()

Metodele din fabrică sunt excelente atunci când știți din timp ce clase doriți să returnați și condițiile în care sunt returnate.

Dar dacă nici nu știi asta din timp?

Definiții dinamice

Python vă permite să definiți clasele dinamic și să creați obiecte cu ele după cum este necesar.

De ce ai vrea să faci asta? Răspunsul scurt este încă mai multă abstractizare.

Desigur, necesitatea de a scrie cod la acest nivel de abstractizare este, în general, un eveniment rar. Ca întotdeauna la programare, ar trebui să luați în considerare dacă există o soluție mai ușoară.

Cu toate acestea, pot exista momente în care se dovedește cu adevărat utilă definirea dinamică a claselor. Vom acoperi mai jos un posibil caz de utilizare.

Este posibil să fiți familiarizați cu Python’s type() funcţie. Cu un singur argument, acesta returnează pur și simplu „tipul” obiectului argumentului.

type(1) # <type 'int'>
type('hello') # <type 'str'>
type(True) # <type 'bool'>

Dar, cu trei argumente, type() revine cu totul nou tip de obiect. Aceasta este echivalent cu definirea unei clase noi.

NewClass = type('NewClass', (object,), {})
  • Primul argument este un șir care dă un nou nume clasei
  • Următorul este un tuplu, care conține orice clasă de bază din care ar trebui să moștenească noua clasă
  • Argumentul final este un dicționar de atribute specifice acestei clase

Când ar putea fi necesar să folosiți ceva la fel de abstract ca acesta? Luați în considerare următorul exemplu.

Tabel cu baloane este o bibliotecă Python care generează sintaxă pentru tabelele HTML. Poate fi instalat prin managerul de pachete pip.

Puteți utiliza Flask Table pentru a defini clase pentru fiecare tabel pe care doriți să îl generați. Definiți o clasă care moștenește de la o bază Table clasă. Atributele sale sunt obiecte coloană, care sunt instanțe ale Col clasă.

from flask_table import Table, Col
class MonthlyDownloads(Table):
	month = Col('Month')
    downloads = Col('Downloads')
    
data = [{'month':'Jun', 'downloads':700},
		{'month':'Jul', 'downloads':900},
        {'month':'Aug', 'downloads':1600},
        {'month':'Sep', 'downloads':1900},
        {'month':'Oct', 'downloads':2200}]
        
table = MonthlyDownloads(data)print(table.__html__())

Apoi creați o instanță a clasei, trecând datele pe care doriți să le afișați. __html__() metoda generează codul HTML necesar.

Acum, spuneți că dezvoltați un instrument care utilizează Flask Table pentru a genera tabele HTML bazate pe un fișier de configurare furnizat de utilizator. Nu știți dinainte câte coloane dorește să definească utilizatorul – ar putea fi una, ar putea fi o sută! Cum poate defini codul dvs. clasa potrivită pentru job?

Definiția claselor dinamice este utilă aici. Pentru fiecare clasă pe care doriți să o definiți, puteți construi dinamic attributes dicţionar.

Spuneți că configurația dvs. de utilizator este un fișier CSV, cu următoarea structură:

Table1, column1, column2, column3
Table2, column1
Table3, column1, column2

Puteți citi fișierul CSV rând cu rând, folosind primul element al fiecărui rând ca nume al fiecărei clase de tabel. Restul elementelor din acel rând ar fi utilizate pentru a defini Col obiecte pentru acea clasă de masă. Acestea sunt adăugate la un attributes dicționar, care este construit iterativ.

for row in csv_file:
	attributes = {}
    for column in row[1:]:
    	attributes[column] = Col(column)
        globals()[row[0]] = type(row[0], (Table,), attributes)

Codul de mai sus definește clasele pentru fiecare dintre tabelele din fișierul de configurare CSV. Fiecare clasă este adăugată la globals dicţionar.

Desigur, acesta este un exemplu relativ banal. FlaskTable este capabil să genereze tabele mult mai sofisticate. Un caz de utilizare din viața reală ar folosi mai bine acest lucru! Dar, sperăm, ați văzut cum definiția dinamică a clasei s-ar putea dovedi utilă în anumite contexte.

Deci acum știi …

Dacă sunteți nou în Python, atunci merită să vă grăbiți din timp cu cursurile și obiectele. Încercați să le implementați în următorul dvs. proiect de învățare. Sau, răsfoiți proiecte open source pe Github pentru a vedea cum utilizează alți dezvoltatori.

Pentru cei cu ceva mai multă experiență, poate fi foarte satisfăcător să afle cum funcționează lucrurile „în culise”. Navigare documentele oficiale poate fi iluminant!

Ați găsit vreodată un caz de utilizare pentru definirea clasei dinamice în Python? Dacă da, ar fi minunat să-l distribuiți în răspunsurile de mai jos.