Dynaaminen luokan määritelmä Pythonissa

Tässä on siisti Python-temppu, josta saatat vain olla hyödyllinen jonain päivänä. Katsotaanpa, kuinka voit määritellä luokkia dynaamisesti ja luoda niistä instansseja tarpeen mukaan.

Tämä temppu hyödyntää Pythonin olio-ohjelmoinnin (OOP) ominaisuuksia, joten tarkistamme ne ensin.

Luokat ja esineet

Python on olio-orientoitu kieli, eli sen avulla voit kirjoittaa koodia olio-orientoituun paradigmaan.

Tämän ohjelmointiparadigman keskeinen käsite on luokat. Pythonissa näitä käytetään luomaan objekteja, joilla voi olla määritteitä.

Objektit ovat luokan erityisiä esiintymiä. Luokka on pohjimmiltaan suunnitelma siitä, mikä esine on ja miten sen pitäisi käyttäytyä.

Luokat määritellään kahdella tyypillä:

  • Tietomääritteet - muuttujat, jotka ovat käytettävissä kyseisen luokan tietylle esiintymälle
  • Menetelmät - toiminnot, jotka ovat käytettävissä kyseisen luokan ilmentymälle

Klassiseen OOP-esimerkkiin liittyy yleensä erityyppisiä eläimiä tai ruokia. Tässä olen käynyt käytännöllisemmäksi yksinkertaisella tietojen visualisointiteemalla.

Määritä ensin luokka 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__Menetelmän avulla voit asettaa attribuutteja kun instanssien. Toisin sanoen, kun luot uuden esiintymän BarChart, voit välittää argumentteja, jotka antavat kaavion otsikon ja tiedot.

Tässä luokassa on myös plot()menetelmä. Tämä tulostaa hyvin yksinkertaisen pylväskaavion konsolille, kun sitä kutsutaan. Se voisi tehdä mielenkiintoisempia asioita todellisessa sovelluksessa.

Seuraavaksi saatetaan esimerkki BarChart:

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

Nyt voit käyttää barobjektia muussa koodissasi:

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

Tämä on hienoa, koska sen avulla voit määrittää luokan ja luoda instansseja dynaamisesti. Voit pyörittää muiden pylväskaavioiden esiintymiä yhdellä koodirivillä.

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

Oletetaan, että haluat määrittää useita kaavioluokkia. Perinnön avulla voit määrittää luokat, jotka "perivät" ominaisuudet perusluokista.

Voit esimerkiksi määrittää Chartperusluokan. Sitten voit määrittää johdetut luokat, jotka perivät tukikohdasta.

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)

Täällä Chartluokka on perusluokka. BarChartJa Scatterluokat perivät __init__()menetelmän Chart.Mutta niillä on oma plot()menetelmiä, jotka ohittavat yksi määritelty Chart.

Nyt voit luoda myös sirontakaavioobjekteja.

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|

Tämän lähestymistavan avulla voit kirjoittaa abstraktimman koodin, mikä antaa sovelluksellesi enemmän joustavuutta. Jos sinulla on piirustuksia lukemattomien muunnelmien luomiseksi samasta yleisestä objektista, säästät tarpeettomasti toistuvia koodirivejä. Se voi myös helpottaa sovelluskoodisi ymmärtämistä.

Voit myös tuoda luokkia tuleviin projekteihin, jos haluat käyttää niitä myöhemmin myöhemmin.

Tehdasmenetelmät

Joskus et tiedä tiettyä luokkaa, jonka haluat ottaa käyttöön ennen ajon. Esimerkiksi luodut objektit riippuvat käyttäjän syötteistä tai toisen prosessin tuloksista, joilla on vaihteleva tulos.

Tehdasmenetelmät tarjoavat ratkaisun. Nämä ovat menetelmiä, jotka ottavat dynaamisen argumenttiluettelon ja palauttavat objektin. Annetut argumentit määrittävät palautettavan objektin luokan.

Yksinkertainen esimerkki on kuvattu alla. Tämä tehdas voi palauttaa joko pylväskaavion tai sirontakohdeobjektin stylevastaanotetusta argumentista riippuen . Älykkäämpi tehdasmenetelmä voisi jopa arvata parhaan käytettävän luokan tarkastelemalla dataargumentin rakennetta .

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()

Tehdasmenetelmät ovat hyviä, kun tiedät etukäteen palatut luokat ja olosuhteet, joissa ne palataan.

Mutta entä jos et edes tiedä tätä etukäteen?

Dynaamiset määritelmät

Python antaa sinun määrittää luokat dynaamisesti ja instantisoida objekteja niiden kanssa tarpeen mukaan.

Miksi haluat tehdä tämän? Lyhyt vastaus on vielä enemmän abstraktiota.

On totta, että koodin kirjoittamisen tarve tällä abstraktiotasolla on yleensä harvinaista. Kuten aina ohjelmoinnin yhteydessä, sinun on harkittava, onko olemassa helpompaa ratkaisua.

Voi kuitenkin olla aikoja, jolloin se todella osoittautuu hyödylliseksi määritellä luokkia dynaamisesti. Seuraavassa kerrotaan mahdollisesta käyttötapauksesta.

Saatat olla perehtynyt Pythonin type()toimintoon. Yhdellä argumentilla se yksinkertaisesti palauttaa argumentin kohteen "tyypin".

type(1) #  type('hello') #  type(True) # 

Mutta kolmella argumentilla type()palauttaa kokonaan uuden tyyppisen objektin. Tämä vastaa uuden luokan määrittelyä.

NewClass = type('NewClass', (object,), {})
  • Ensimmäinen argumentti on merkkijono, joka antaa uudelle luokalle nimen
  • Seuraava on sekoitus, joka sisältää kaikki perusluokat, joista uuden luokan tulisi periä
  • Viimeinen argumentti on tälle luokalle ominaisten määritteiden sanakirja

Milloin saatat joutua käyttämään jotain niin abstraktia kuin tämä? Harkitse seuraavaa esimerkkiä.

Flask Table on Python-kirjasto, joka tuottaa syntaksin HTML-taulukoille. Se voidaan asentaa pip-paketinhallinnan kautta.

You can use Flask Table to define classes for each table you want to generate. You define a class that inherits from a base Table class. Its attributes are column objects, which are instances of the Col class.

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__())

You then create an instance of the class, passing in the data you want to display. The __html__() method generates the required HTML.

Now, say you’re developing a tool that uses Flask Table to generate HTML tables based on a user-provided config file. You don’t know in advance how many columns the user wants to define — it could be one, it could be a hundred! How can your code define the right class for the job?

Dynamic class definition is useful here. For each class you wish to define, you can dynamically build the attributes dictionary.

Say your user config is a CSV file, with the following structure:

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

You could read the CSV file line-by-line, using the first element of each row as the name of each table class. The remaining elements in that row would be used to define Col objects for that table class. These are added to an attributes dictionary, which is built up iteratively.

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

The code above defines classes for each of the tables in the CSV config file. Each class is added to the globals dictionary.

Of course, this is a relatively trivial example. FlaskTable is capable of generating much more sophisticated tables. A real life use-case would make better use of this! But, hopefully, you’ve seen how dynamic class definition might prove useful in some contexts.

So now you know…

Jos olet uusi Pythonissa, kannattaa päästä vauhtiin luokkien ja objektien kanssa jo varhaisessa vaiheessa. Yritä toteuttaa ne seuraavassa oppimisprojektissasi. Tai selaa avoimen lähdekoodin projekteja Githubissa nähdäksesi, miten muut kehittäjät käyttävät niitä.

Niille, joilla on hieman enemmän kokemusta, voi olla erittäin palkitsevaa oppia, miten asiat toimivat "kulissien takana". Virallisten asiakirjojen selaaminen voi olla valaisevaa!

Oletko koskaan löytänyt käyttötapausta dynaamiseen luokan määrittelyyn Pythonissa? Jos on, olisi hienoa jakaa se alla oleviin vastauksiin.