Dinamikus osztálydefiníció Pythonban

Itt van egy ügyes Python-trükk, amelyet egyszer csak hasznosnak találhat. Nézzük meg, hogyan lehet dinamikusan meghatározni az osztályokat, és szükség szerint létrehozhatunk belőlük példányokat.

Ez a trükk a Python objektum-orientált programozási (OOP) képességeit használja fel, ezért ezeket először átnézzük.

Osztályok és tárgyak

A Python egy objektumorientált nyelv, vagyis lehetővé teszi a kód beírását az objektumorientált paradigmába.

Ebben a programozási paradigmában a kulcsfogalom az osztályok. A Pythonban ezeket olyan objektumok létrehozására használják, amelyek attribútumokkal rendelkeznek.

Az objektumok egy adott osztály egyedi példányai. Az osztály lényegében tervrajz arról, hogy mi az objektum és hogyan kell viselkednie.

Az osztályok kétféle attribútummal vannak meghatározva:

  • Adattribútumok - az adott osztály adott példányához elérhető változók
  • Metódusok - az adott osztály egy példányának elérhető funkciói

A klasszikus OOP példa általában különböző típusú állatokat vagy ételeket tartalmaz. Itt praktikusabb lett egy egyszerű adatmegjelenítési téma.

Először határozza meg az osztályt 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)

A __init__módszer lehetővé teszi az attribútumok beállítását a példányosításkor. Vagyis amikor létrehoz egy új példányt BarChart, akkor argumentumokat adhat át, amelyek megadják a diagram címét és adatait.

Ennek az osztálynak is van plot()módszere. Ez egy nagyon egyszerű sávdiagramot nyomtat ki a konzolra, amikor hívják. Valójában érdekes módon kivitelezhető érdekesebb dolgokat is megtehet.

Ezután példányosítsa a következőket BarChart:

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

Most használhatja az barobjektumot a kód többi részében:

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

Ez nagyszerű, mert lehetővé teszi egy osztály definiálását és a dinamikus példányok létrehozását. Felforgathatja más oszlopdiagramok példányait egy kódsorban.

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

Tegyük fel, hogy a diagram több osztályát meg akarja határozni. Az öröklés lehetővé teszi olyan osztályok meghatározását, amelyek „öröklik” a tulajdonságokat az alaposztályoktól.

Például meghatározhat egy alaposztályt Chart. Ezután meghatározhat olyan származtatott osztályokat, amelyek örökölnek az alapból.

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)

Itt az Chartosztály egy alaposztály. Az BarChartés Scatterosztályok örökölik a __init__()módszert a következőtől: Chart.De vannak saját plot()módszereik, amelyek felülírják a metódust Chart.

Most létrehozhat scatter diagram objektumokat is.

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|

Ez a megközelítés lehetővé teszi absztraktabb kódok írását, nagyobb rugalmasságot biztosítva az alkalmazás számára. Ha tervrajzokkal számtalan variációt hoz létre ugyanarról az általános objektumról, akkor feleslegesen megismétli a kódsorokat. Ez megkönnyíti az alkalmazás kódjának megértését is.

Az osztályokat későbbi projektekbe is importálhatja, ha később szeretné újra felhasználni őket.

Gyári módszerek

Időnként futásidő előtt nem fogja tudni, hogy melyik osztályt kívánja végrehajtani. Például az Ön által létrehozott objektumok függhetnek a felhasználó bevitelétől, vagy egy másik, változó kimenetelű folyamat eredményeitől.

A gyári módszerek megoldást kínálnak. Ezek olyan módszerek, amelyek dinamikus argumentumlistát vesznek fel, és objektumot adnak vissza. A megadott argumentumok meghatározzák a visszaküldött objektum osztályát.

Az alábbiakban egy egyszerű példát szemléltetünk. Ez a gyár oszlopdiagramot vagy scatter plot objektumot adhat vissza, a kapott styleargumentumtól függően . Egy intelligensebb gyári módszer akár kitalálhatná a legjobban alkalmazható osztályt is, megvizsgálva az dataérv felépítését .

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

A gyári módszerek nagyszerűek, ha előre tudod, melyik osztályba akarsz visszatérni, és milyen körülmények között térnek vissza.

De mi van, ha ezt nem is tudja előre?

Dinamikus meghatározások

A Python lehetővé teszi az osztályok dinamikus meghatározását, és az objektumok szükség szerinti példányosítását velük.

Miért akarja ezt megtenni? A rövid válasz még inkább absztrakció.

Kétségtelen, hogy az absztrakció ezen szintjén szükséges kódot írni, ritkán fordul elő. Mint mindig a programozás során, érdemes megfontolnia, hogy van-e könnyebb megoldás.

Előfordulhat azonban, hogy valóban hasznosnak bizonyul az osztályok dinamikus meghatározása. Az alábbiakban bemutatunk egy lehetséges felhasználási esetet.

Lehet, hogy ismeri a Python type()funkcióját. Egy argumentummal egyszerűen visszaadja az argumentum tárgyának „típusát”.

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

Három argumentummal azonban type()egy teljesen új típusú objektumot ad vissza. Ez egyenértékű egy új osztály meghatározásával.

NewClass = type('NewClass', (object,), {})
  • Az első argumentum egy karaktersorozat, amely nevet ad az új osztálynak
  • A következő egy páros, amely tartalmazza azokat az alaposztályokat, amelyekből az új osztálynak örökölnie kell
  • Az utolsó érv az adott osztályra jellemző attribútumok szótára

Mikor kell esetleg valami ilyen elvont dolgot használni? Tekintsük a következő példát.

A Flask Table egy Python könyvtár, amely szintaxist generál a HTML táblákhoz. Telepíthető a pip csomagkezelőn keresztül.

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…

If you are new to Python, then it is worth getting up to speed with classes and objects early on. Try implementing them in your next learning project. Or, browse open source projects on Github to see how other developers make use of them.

For those with a little more experience, it can be very rewarding to learn how things work “behind-the-scenes”. Browsing the official docs can be illuminating!

Have you ever found a use-case for dynamic class definition in Python? If so, it’d be great to share it in the responses below.