Објектна апстракција¶
Објектни систем омогућава програмерима да ефикасно граде и користе апстрактне представе података. Такође је пројектован и да омогући да вишеструке представе апстрактних података заједно постоје у истом програму.
Главни концепт у објектној апстракцији је такозвана генеричка или општа функција, функција која може прихватити вредности више различитих типова. Биће размотрене три различите технике за примену генеричких, то јест општих функција: дељена сучеља, отпремање типова и превођење или претварање типова. У поступку изградње ових концепата, такође ће бити откривене одлике и особине Пајтоновог објектног система који подржава стварање генеричких, односно општих функција.
Претварање ниски¶
Да би представљао податке на ефикасан начин, вредност објекта би се требала понашати попут оне врсте података коју је намењена да представља, укључујући ту и представу самог себе преко одговарајуће ниске. Текстуалне представе вредности података посебно су важне у интерактивном језику као што је Пајтон који аутоматски у виду ниске приказује вредности израза у интерактивној сеанси.
Текстуалне вредности, односно ниске обезбеђују основни медијум за преношење информација међу људима. Секвенце знакова могу се приказивати на екрану, штампати на папиру, читати наглас, претварати у Брајеву азбуку или слати као Морзеов код. Ниске су такође фундаменталне за програмирање, јер могу представљати Пајтонове изразе.
Пајтон предвиђа да сви објекти треба да производе две различите текстуалне представље у виду ниске: једну која је разумљива људима и другу која се може од стране Пајтона интерпретирати као израз. Конструкторска функција за ниске, str
, враћа ниску која је читљива човеку. Где је то могуће, функција repr
враћа Пајтонов израз који се вреднује у идентичан објекат. Докниска за уграђену функцију repr
објашњава ово својство:
repr(obj, /) -> ниска
Враћа каноничку представу објекта у виду ниске.
За многе типове објеката, укључујући и већину уграђених, eval(repr(obj)) == obj.
Резултат позива repr
над вредношћу неког израза је оно што Пајтон исписује, односно штампа у интерактивној сеанси.
>>> 12e12
12000000000000.0
>>> print(repr(12e12))
12000000000000.0
У случајевима када не постоји представа која се вреднује у изворну вредност, Пајтон обично даје опис окружен угластим заградама.
>>> repr(min)
'<built-in function min>'
Резултат конструктора str
се често подудара са repr
резултатом, али у неким случајевима пружа приказ текста који је лакши за тумачење. На пример, разлика између str
и repr
се види у раду са датумима.
>>> from datetime import date
>>> среда = date(1991, 5, 29)
>>> repr(среда)
'datetime.date(1991, 5, 29)'
>>> str(среда)
'1991-05-29'
Дефинисање repr
функције представља нови изазов: волели бисмо да се исправно примењује на све типове података, чак и на оне који нису постојали када је repr
имплементирана. Желели бисмо да она буде генеричка или полиморфна функција, функција која се може применити на многе (поли) различите облике (морф) података.
Објектни систем нуди елегантно решење у овом случају: функција repr
увек позива методу која се зове __repr__
над својим аргументом.
>>> среда.__repr__()
'datetime.date(1991, 5, 29)'
Имплементацијом исте ове методе у кориснички дефинисане класе, може се проширити применљивост repr
функције на било коју класу која се буде правила у будућности. Овај пример истиче још једну предност тачкастих израза уопште, а то је да пружају механизам за проширивање домена постојећих функција на нове типове објеката.
Конструктор str
је имплементиран на сличан начин: он позива методу под називом __str__
над својим аргументом.
>>> среда.__str__()
'1991-05-29'
Ове полиморфне функције су примери једног општијег принципа: одређене функције треба да буду примењиве на више различитих типова података. Штавише, један од начина за прављење такве једне функције јесте употреба заједничког имена атрибута са различитом дефиницијом у свакој класи.
Посебне методе¶
У Пајтону, у неким посебним околностима Пајтонов тумач позива одређене посебне методе. На пример, метода __init__
неке класе се аутоматски позива кад год се ствара нови објекат. Метода __str__
се позива аутоматски приликом исписивања, односно штампања, а __repr__
се позива у интерактивној сесији за приказ вредности.
Постоје посебна имена и за многа друга понашања у Пајтону. Нека од најчешће коришћених описана су у наставку.
Логичке вредности¶
Раније је приказано да бројеви у Пајтону имају своје логичке вредности; тачније 0 је нетачна вредност, а сви остали бројеви су тачне вредности. У ствари, сви објекти у Пајтону поседују логичку вредност. Подразумевано је да се објекти кориснички дефинисаних класа сматрају као тачни, али посебна метода __bool__
се може икористити за промену овог понашања. Уколико објекат дефинише __bool__
методу, тада Пајтон позива ту методу да одреди његову логичку вредност.
Као пример, претпоставимо да желимо да банковни рачун са стањем 0 буде логички нетачан. Може се додати метода __bool__
у класу Рачун
да би се имплементирао овакав режим рада.
>>> class Рачун:
... def __init__(self, власникРачуна):
... self.стање = 0
... self.власник = власникРачуна
>>> Рачун.__bool__ = lambda self: self.стање != 0
Може се позвати bool
конструктор како би се утврдила логичка вредност објекта, а било који објекат може се користити у логичком контексту.
>>> bool(Рачун('Влада'))
False
>>> if not Рачун('Влада'):
... print('Влада је шворц!')
Влада је шворц!
Операције над секвенцама¶
Већ је виђено да је могуће позвати уграђену функцију len
како би се одредила дужина неке секвенце.
>>> len('Напред Звездо!')
14
Функција len
позива методу __len__
свог аргумента како би одредила његову дужину. Сви уграђени типови секвенци имплементирају ову методу.
>>> 'Напред Звездо!'.__len__()
14
Пајтон користи користи дужину секвенце како би одредио њену логичку вредност, ако се већ деси да __bool__
метода не постоји. Празне секвенце су нетачне, док су непразне секвенце логички тачне.
>>> bool('')
False
>>> bool([])
False
>>> bool('Напред Звездо!')
True
Метода __getitem__
се позива од стране оператора избора члана, али се такође може позвати и директно.
>>> 'Напред Звездо!'[7]
'З'
>>> 'Напред Звездо!'.__getitem__(7)
'З'
Позивни објекти¶
У Пајтону, функције су првокласни објекти, тако да се могу прослеђивати као подаци и имати атрибуте као и било који други објекат. Пајтон такође омогућава да се дефинишу објекти који се могу „позвати” попут функција укључивањем __call__
методе. Овом методом може се дефинисати класа која се понаша као функција вишег реда.
Као пример, биће размотрена следећа функција вишег реда која враћа функцију која свом аргументу додаје константну вредност.
>>> def направиСабирач(n):
... def сабирач(k):
... return n + k
... return сабирач
>>> додајТри = направиСабирач(3)
>>> додајТри(4)
7
Може се направити класа Сабирач
која дефинише методу __call__
како би пружила исту функционалност.
>>> class Сабирач(object):
... def __init__(self, n):
... self.n = n
... def __call__(self, k):
... return self.n + k
>>> додајТројку = Сабирач(3)
>>> додајТројку(4)
7
Овде се класа Сабирач
понаша као функција вишег реда направиСабирач
, а објект додајТројку
понаша се као функција додајТри
. Овим је још више замагљена граница између података и функција.
Аритметика¶
Посебне методе такође могу дефинисати режим понашања уграђених оператора примењених на кориснички дефинисане објекте. Да би обезбедио ову општост, Пајтон прати одређене протоколе за примену сваког оператора. На пример, да би вредновао изразе који садрже оператор +
, Пајтон проверава посебне методе и на левом и на десном операнду израза. Најпре, Пајтон проверава методу __add__
над вредношћу левог операнда, а затим проверава методу __radd__
над вредношћу десног операнда. За случај да се пронађе било која од њих, та метода се позива са вредношћу другог операнда као аргументом. Неки примери су дати у следећим одељцима. За читаоце који су заинтересовани за појединости, Пајтон документација описује исцрпан скуп назива метода за операторе. Већ више пута референцирана интернет књига Dive Into Python 3 садржи поглавље о именима посебних метода које описује колико се ових специјалних имена метода заправо користи.
Вишеструке представе¶
Апстракцијске баријере омогућавају раздвајање употребе података од њихове представе. Међутим, у великим и сложеним програмима можда неће увек имати смисла говорити о „основној представи” за тип података у програму. Као прво, можда постоји више корисних представа за неки објекат податка, а могуће је и да се жели пројектовање система који могу баратати вишеструким представама.
Узмимо једноставан пример, комплексни бројеви могу се представити на два готово еквивалентна начина: у такозваном правоугаоном облику (реални и имагинарни део) и у поларном облику (апсолутна вреднос, односно модул и угао). Понекад је правоугаони облик прикладнији, а понекад поларни. Заиста, савршено је вероватно замислити систем у којем су комплексни бројеви представљени на оба начина и у којем функције за манипулисање комплексним бројевима раде са било којом представом. Такав систем биће имплементиран у наставку. Као пропратна напомена, биће развијен систем који изводи аритметичке операције над комплексним бројевима као једноставан, али нереалан пример програма који користи генеричке, односно опште операције. Комплексни тип броја је заправо уграђен у Пајтон, али за овај пример биће имплементиран посебан тип податка.
Идеја о омогућавању вишеструке представе података јавља се редовно. Велике софтверске системе често пројектује више људи који раде на томе неки дужи временски период, при чему се одређени захтеви у међувремену могу и променити. У таквом окружењу једноставно није могуће да се сви унапред договоре око избора представе података. Поред апстракцијских баријера за податке које изолују начин представљања података од употребе истих, потребне су и апстракцијске баријере које међусобно изолују различите пројектне изборе и омогућавају коегзистенцију различитих избора у једном програму.
Имплементацију започињемо на највишем нивоу апстракције и постепено ће се ићи ка конкретнијим представама. Комплексан
број је Број
, а бројеви се могу сабирати или множити. Како се бројеви могу сабирати или множити, апстраковано је методама под именима add
и mul
.
>>> class Број:
... def __add__(self, други):
... return self.add(други)
... def __mul__(self, други):
... return self.mul(други)
Ова класа захтева да објекти Број
-а садрже методе add
и mul
, али их не дефинише. Штавише, чак нема ни __init__
методу. Сврха Број
-а није да се директно инстанцира, већ да служи као суперкласа различитих специфичних класа бројева. Следећи задатак је да се дефинишу add
и mul
на одговарајући начин за комплексне бројеве.
Комплексни број се може сматрати тачком у дводимензионалном простору са две ортогоналне осе (Декартов правоугли координатни систем), такозваном реалном и имагинарном осом (комплексна раван). Из ове перспективе, комплексни број z = реал + имаг * i
(где је i * i = -1
) може се сматрати тачком у равни чија је хоризонтална координата реал
, а чија је вертикална координата имаг
. Додавање комплексних бројева заправо укључује додавање њима одговарајућих реал
и имаг
координата.
Приликом множења комплексних бројева, природније је размишљати у смислу представљања комплексног броја у поларном облику, односно као уређени пар модул
и угао
. Производ два комплексна броја је вектор добијен растезањем једног комплексног броја фактором дужине другог, а затим ротирањем за угао другог.
Класа Комплексан
наслеђује класу Број
и описује аритметику за комплексне бројеве.
>>> class Комплексан(Број):
... def add(self, други):
... return КомплексанРИ(self.реал + други.реал, self.имаг + други.имаг)
... def mul(self, други):
... модуо = self.модуо * други.модуо
... return КомплексанМУ(модуо, self.угао + други.угао)
Ова имплементација претпоставља да постоје две класе за сложене бројеве, што одговара њиховим природним представама:
КомплексанРИ
конструише комплексан број из реалног и имагинарног дела.КомплексанМУ
конструише комплексан број из модула и угла.
Сучеља¶
Атрибути објекта, који су облик преношења порука, омогућавају различитим типовима података да одговоре на исту поруку на различите начине. Заједнички скуп порука које производе слично понашање различитих класа је моћан метод апстракције. Сучеље је скуп дељених имена атрибута заједно са спецификацијом њиховог понашања. У случају комплексних бројева, сучеље потребно за имплементацију аритметике састоји се од четири атрибута: реал
, имаг
, модуо
и угао
.
Да би комплексна аритметика била тачна, ови атрибути морају бити међусобно доследни. Односно, правоугаоне координате (реал
, имаг
) и поларне координате (модуо
, угао
) морају описивати исту тачку на комплексној равни. Класа Комплексан
имплицитно дефинише ово сучеље одређујући како се ови атрибути користе за сабирање (add
) и множење (mul
) комплексних бројева.
Особине¶
Нови изазов представља захтев да две или више вредности атрибута одржавају фиксну међусобну везу. Једно од могућих решења је чување вредности атрибута само за једну представу и израчунавање друге представе кад год је то потребно.
Пајтон има једноставну функцију за израчунавање атрибута у ходу из функција без аргумената. Декоратер @property
омогућава да функције буду позване без синтаксе позивног израза (заграде након израза). Класа КомплексанРИ
чува реал
и имаг
атрибуте и израчунава модуо
и угао
на захтев.
>>> from math import atan2
>>> class КомплексанРИ(Комплексан):
... def __init__(self, реал, имаг):
... self.реал = реал
... self.имаг = имаг
... @property
... def модуо(self):
... return (self.реал ** 2 + self.имаг ** 2) ** 0.5
... @property
... def угао(self):
... return atan2(self.имаг, self.реал)
... def __repr__(self):
... return 'КомплексанРИ({0:g}, {1:g})'.format(self.реал, self.имаг)
Као резултат ове имплементације, може се приступити сва четири атрибута потребна за комплексну аритметику и то без икаквих позивних израза, а било какве промене у реал
или имаг
осликавају се и на модуо
и угао
.
>>> ри = КомплексанРИ(5, 12)
>>> ри.реал
5
>>> ри.модуо
13.0
>>> ри.реал = 9
>>> ри.реал
9
>>> ри.модуо
15.0
Слично томе, класа КомплексанМУ
чува модуо
и угао
, али израчунава реал
и имаг
кад год се ти атрибути потраже, односно позову.
>>> from math import sin, cos, pi
>>> class КомплексанМУ(Комплексан):
... def __init__(self, модуо, угао):
... self.модуо = модуо
... self.угао = угао
... @property
... def реал(self):
... return self.модуо * cos(self.угао)
... @property
... def имаг(self):
... return self.модуо * sin(self.угао)
... def __repr__(self):
... return 'КомплексанМУ({0:g}, {1:g} * pi)'.format(self.модуо, self.угао/pi)
Промене модула или угла осликавају се одмах на реал
и имаг
атрибутима.
>>> му = КомплексанМУ(2, pi/2)
>>> му.имаг
2.0
>>> му.угао = pi
>>> му.реал
-2.0
Имплементација комплексних бројева је сада завршена. Обе класе које имплементирају комплексне бројеве могу се користити за било који од аргумената у било којој аритметичкој функцији у класи Комплексан
.
>>> from math import pi
>>> КомплексанРИ(1, 2) + КомплексанМУ(2, pi/2)
КомплексанРИ(1, 4)
>>> КомплексанРИ(0, 1) * КомплексанРИ(0, 1)
КомплексанМУ(1, 1 * pi)
Приступ у виду сучеља кодирању вишеструких представа има нека привлачна својства. Класа за сваку представу може се развијати одвојено; морају се сложити само око имена дељених атрибута, као и свих услова понашања за те атрибуте. Сучеље је такође адитивно. Уколико би други програмер желео да дода трећу представу комплексних бројева истом програму, морала би само да се направи нова класа са истим атрибутима.
Вишеструке представе података уско су повезане са идејом апстракције података којом је започето ово поглавље. Коришћењем апстракције података промењена је имплементација типа податка без промене значења самог програма. Са сучељима и прослеђивањем порука, могу постојати више различитих представа у оквиру истог програма. У оба случаја, скуп имена и одговарајући услови понашања дефинишу апстракцију која омогућава ову флексибилност.
Генеричке функције¶
Генеричке функције су методе или функције које се примењују на аргументе различитих типова. Већ је виђено много примера. Метода Комплексан.add
је генеричка, јер може узимати или КомплексанРИ
или КомплексанМУ
као вредност за други
. Ова флексибилност стечена је обезбеђивањем да и КомплексанРИ
и КомплексанМУ
имају заједничко сучеље. Коришћење сучеља и прослеђивање порука су само једна од неколико метода које се користе за имплементацију генеричких или општих функција. У овом одељку биће размотрене још две: отпремање типова и превођење или претварање типова.
Претпоставимо да је, поред класа комплексних бројева, имплементирана и класа Рационалан
како би се њоме представљали разломци. Методе add
и mul
изражавају и описују иста израчунавања као и функције збирРазломака
и производРазломака
из једног од ранијих одељака овог поглавља.
>>> from math import gcd
>>> class Рационалан(Број):
... def __init__(self, бројилац, именилац):
... нзд = gcd(бројилац, именилац)
... self.бројилац = бројилац // нзд
... self.именилац = именилац // нзд
... def __repr__(self):
... return 'Рационалан({0}, {1})'.format(self.бројилац, self.именилац)
... def add(self, други):
... бx, иx = self.бројилац, self.именилац
... бy, иy = други.бројилац, други.именилац
... return Рационалан(бx * иy + бy * иx, иx * иy)
... def mul(self, други):
... бројилац = self.бројилац * други.бројилац
... именилац = self.именилац * други.именилац
... return Рационалан(бројилац, именилац)
Пошто је сучеље суперкласе Број
имплементирано укључивањем метода __add__
и __mul__
, рационални бројеви као, резултат тога, могу се сабирати и множити помоћу уобичајених оператора.
>>> Рационалан(2, 5) + Рационалан(1, 10)
Рационалан(1, 2)
>>> Рационалан(1, 4) * Рационалан(2, 3)
Рационалан(1, 6)
Међутим, комплексном броју се још увек не може додати рационалан број, иако је у математици и таква комбинација јасно дефинисана. Желели бисмо да представимо ову операцију унакрсног типа на неки пажљиво контролисан начин, како бисмо је могли подржати без озбиљног кршења већ успостављених апстракцијских баријера. Постоји натегнутост између исхода који се желе постићи: желели бисмо да рационалном броју додамо комплексан број и желели бисмо то да то буде учињено користећи генеричку __add__
методу која ради исправно са свим нумеричким типовима. Истовремено, желели бисмо да раздвојимо бриге и проблеме који се тичу комплексних бројева и рационалних бројева кад год је то могуће, како би се задржала модуларност програма.
Отпремање типова¶
Један од начина имплементације унакрсних операција је да се изабере одговарајуће понашање на основу типова аргумената функције или методе. Главна идеја такозваног отпремања типова јесте писање функција које испитују типове аргумената које добијају, а затим извршавају део кода који је прикладан за те конкретне типове.
Уграђена функција isinstance
прима као аргументе објекат и класу, а враћа логичку вредност True
ако је прослеђени објекат или те класе или неке од класа које је наслеђују, а иначе враћа False
.
>>> к = КомплексанРИ(1, 1)
>>> isinstance(к, КомплексанРИ)
True
>>> isinstance(к, Комплексан)
True
>>> isinstance(к, КомплексанМУ)
False
Једноставан пример такозваног отпремања типова је функција даЛиЈеРеалан
која користи различите имплементације за сваки тип комплексног броја.
>>> def даЛиЈеРеалан(к):
... """Враћа да ли је к реалан број без имагинарног дела."""
... if isinstance(к, КомплексанРИ):
... return к.имаг == 0
... elif isinstance(к, КомплексанМУ):
... return к.угао % pi == 0
>>> даЛиЈеРеалан(КомплексанРИ(1, 1))
False
>>> даЛиЈеРеалан(КомплексанМУ(2, pi))
True
Отпремање типова не изводи се увек помоћу isinstance
. За аритметику, у инстанце класа Рационалан
и Комплексан
биће уведен додатан атрибут ознакаТипа
који ће имати текстуалне вредности у виду ниске. Када је ознакаТипа
код две вредности x
и y
иста, тада се оне могу директно комбиновати са x.add(y)
, на пример, а ако није, биће неопходна операција унакрсног типа.
>>> Рационалан.ознакаТипа = 'рац'
>>> Комплексан.ознакаТипа = 'ком'
>>> Рационалан(2, 5).ознакаТипа == Рационалан(1, 2).ознакаТипа
True
>>> КомплексанРИ(1, 1).ознакаТипа == КомплексанМУ(2, pi/2).ознакаТипа
True
>>> Рационалан(2, 5).ознакаТипа == КомплексанРИ(1, 1).ознакаТипа
False
Да би се комбиновали комплексни и рационални бројеви, биће написане функције које се истовремено ослањају на обе представе. У наставку, ослонићемо се на чињеницу да се Рационалан
број може приближно претворити у float
вредност која је заправо рачунарска апроксимација реалног броја. Резултат се може комбиновати са комплексним бројевима.
>>> def сабериКомплексанИРационалан(к, р):
... return КомплексанРИ(к.реал + р.бројилац/р.именилац, к.имаг)
Множење укључује слично претварање. У поларном облику, реалан број у комплексној равни увек има позитивну апсолутну вредност, то јест модуо. Угао 0 означава позитиван број. Угао pi
означава негативан број.
>>> def помножиКомплексанИРационалан(к, р):
... рМодуо, рУгао = р.бројилац/р.именилац, 0
... if рМодуо < 0:
... рМодуо, рУгао = -рМодуо, pi
... return КомплексанМУ(к.модуо * рМодуо, к.угао + рУгао)
И код сабирања и код множења важи комутативност, тако да замена редоследа аргументима може користити исте имплементације ових операција унакрсног типа.
>>> def сабериРационаланИКомплексан(р, к):
... return сабериКомплексанИРационалан(к, р)
>>> def помножиРационаланИКомплексан(р, к):
... return помножиКомплексанИРационалан(к, р)
Улога отпремања типова је да обезбеди да се ове операције унакрсног типа користе у одговарајуће време. У наставку, суперкласа Број
биће преписана тако да користи отпремање типова за своје __add__
и __mul__
методе.
Атрибут ознакаТипа
биће искоришћен да се направи разлика између типова аргумената. Такође се може директно користити и уграђена isinstance
метода, али ознаке поједностављују имплементацију. Коришћење типовних ознака такође илуструје да отпремање типова није нужно везано за Пајтонов објектни систем, већ је напротив, општа техника за стварање генеричких функција на хетерогеним доменима.
Метода __add__
разматра два случаја. Најпре, ако два аргумента имају исту ознаку типа, тада се претпоставља да add
метода првог може узети други као свој аргумент. Иначе, проверава да ли речник имплементација унакрсног типа, под називом сабирачи
, садржи функцију која може да сабере аргументе тих типовних ознака. Уколико таква функција постоји, метода унакрсноПримени
је проналази и примењује. Метода __mul__
има сличну структуру.
>>> class Број:
... def __add__(self, други):
... if self.ознакаТипа == други.ознакаТипа:
... return self.add(други)
... elif (self.ознакаТипа, други.ознакаТипа) in self.сабирачи:
... return self.унакрсноПримени(други, self.сабирачи)
... def __mul__(self, други):
... if self.ознакаТипа == други.ознакаТипа:
... return self.mul(други)
... elif (self.ознакаТипа, други.ознакаТипа) in self.множачи:
... return self.унакрсноПримени(други, self.множачи)
... def унакрсноПримени(self, други, унакрснеФје):
... унакрснаФја = унакрснеФје[(self.ознакаТипа, други.ознакаТипа)]
... return унакрснаФја(self, други)
... сабирачи = {("ком", "рац"): сабериКомплексанИРационалан,
... ("рац", "ком"): сабериРационаланИКомплексан}
... множачи = {("ком", "рац"): помножиКомплексанИРационалан,
... ("рац", "ком"): помножиРационаланИКомплексан}
У овој новој дефиницији класе Број
, све имплементације унакрсног типа индексиране су паровима типовних ознака унутар речника сабирачи
и множачи
.
Овај приступ отпремања типова који је заснован на речницима је лако проширив. Нове поткласе класе Број
могле би се инсталирати у читав систем једноставном декларацијом ознаке типа и додавањем унакрсних операција у речнике Број.сабирачи
и Број.множачи
. Могу се такође дефинисати и сопствени сабирачи
и множачи
у самим поткласама.
Иако је у читав систем уведена одређена доза сложености, сада се могу комбиновати и мешати типови података у изразима сабирања и множења.
>>> КомплексанРИ(1.5, 0) + Рационалан(3, 2)
КомплексанРИ(3, 0)
>>> Рационалан(-1, 2) * КомплексанМУ(4, pi/2)
КомплексанМУ(2, 1.5 * pi)
Претварање¶
У општој ситуацији потпуно неповезаних операција које делују над потпуно неповезаним типовима, имплементација експлицитних унакрсних операција, колико год гломазна била, најбоље је до чега се може доћи. Срећом, понекад се може учинити још нешто боље користећи предност додатне структуре која може бити латентна и прикривена у изграђеном систему типова. Често различити типови података нису у потпуности независни и могу постојати начини на које се објекти једног типа могу посматрати као објекти другог типа. Овај процес се назива претварање. На пример, ако се затражи да се аритметички искомбинују рационалан број са комплексним бројем, рационалан број се може посматрати посматрати као комплексан број чији је имагинарни део нула. Након тога могу се користити Комплексан.add
и Комплексан.mul
за комбиноване математичке операције сабирања и множења.
Уопштено, ова идеја може се спровести пројектовањем функција претварања које трансформишу објекат једног типа у еквивалентни објекат другог типа. Ево типичне функције претварања која трансформише рационални у комплексни број са нултим имагинарним делом:
>>> def рационаланУкомплексан(р):
... return КомплексанРИ(р.бројилац/р.именилац, 0)
Алтернативна дефиниција класе Број
изводи операције над унакрсним типовима покушајем претварања оба аргумента у исти тип. Речник претварања
индексира сва могућа претварања са по једним паром типовних ознака, указујући да одговарајућа вредност претвара вредност првог типа у вредност другог типа.
Генерално није могуће претворити произвољан објекат података неког типа у све остале типове. На пример, не постоји начин да се произвољан комплексан број претвори у рационалан број, тако да неће бити имплементирана таква конверзија у речнику претварања
.
Метода претвори
враћа две вредности са истом ознаком типа. Она испитује ознаке типова својих аргумената, упоређује их са уносима у речнику претварања
и конвертује један аргумент у тип другог користећи претвориУ
методу. Само један унос у речнику претварања
је неопходан да би се довршио аритметички систем над унакрсним типовима, тиме замењујући све четири функције унакрсног типа у верзији класе Број
која користи отпремање типова.
>>> class Број:
... def __add__(self, други):
... x, y = self.претвори(други)
... return x.add(y)
... def __mul__(self, други):
... x, y = self.претвори(други)
... return x.mul(y)
... def претвори(self, други):
... if self.ознакаТипа == други.ознакаТипа:
... return self, други
... elif (self.ознакаТипа, други.ознакаТипа) in self.претварања:
... return (self.претвориУ(други.ознакаТипа), други)
... elif (други.ознакаТипа, self.ознакаТипа) in self.претварања:
... return (self, други.претвориУ(self.ознакаТипа))
... def претвориУ(self, other_tag):
... фјаПретварања = self.претварања[(self.ознакаТипа, other_tag)]
... return фјаПретварања(self)
... претварања = {('рац', 'ком'): рационаланУкомплексан}
Ова шема претварања има неке предности у односу на метод дефинисања експлицитних операција унакрсног типа. Иако се још увек морају писати функције претварања како би се повезали типови, ипак мора да се напише само једна функција за сваки пар типова, а не различиту функцију за сваки скуп типова и сваку генеричку операцију. Оно на шта се овде рачуна јесте чињеница да одговарајућа трансформација између типова зависи само од самих типова, а не од конкретне операције која ће се над њима применити.
Даље предности долазе од проширивања претварања. Неке софистицираније шеме претварања не само да покушавају да претворе један тип у други, већ уместо тога могу да покушају да претворе два различита типа у трећи уобичајени тип. Узмимо у обзир ромб и правоугаоник: ниједан није посебан случај другог, али оба се могу посматрати као четвороугли. Још једно проширење претварања је такозвано итеративно претварање, у коме се један тип података претвара у други путем посредних типова. Узмимо у обзир да се цео број може претворити у реалан број тако што ће се најпре претворити у рационалан број, а потом тај рационални број претворити у реалан број. Уланчавање претварања на овај начин може смањити укупан број функција претварања које су потребне програму.
Упркос својим предностима, претварање има потенцијалне недостатке. Као прво, функције претварања могу услед примене довести до губитка информације. У малопређашњем примеру, рационални бројеви су имају тачну и егзактну представу, али нажалост постају апроксимације када се претворе у комплексне бројеве.
Неки програмски језици поседују уграђене системе аутоматског претварања. У ствари, ране верзије Пајтона имале су посебну методу __coerce__
на објектима. На крају, сложеност уграђеног система претварања није оправдала његову употребу и зато је уклоњен. Уместо тога, одређени оператори по потреби примењују претварање над својим аргументима.