Контрол на достъпа до атрибути

Понякога е удобно да има атрибути в клас, чиито стойности не се съхраняват в паметта, а се изчисляват в момента на достъп до тях. Следното е пълна реализация на такъв клас:

def __getattr__(self, char): връща ord(char)

Имайки предвид класа Ord, можете да създадете екземпляр на този клас ord = Ord() и да имате алтернатива на вградената функция ord(), която работи с всеки знак, който може да се използва в идентификатор. Например достъпът до ord.a ще върне 97, ord.Z ще върне 90, а ord.e ще върне 229. (Но достъпът до ord.! и други подобни ще доведе до синтактична грешка.)

Имайте предвид, че ако въведете дефиницията на класа Ord в >

Подобрени техники за обектно/ориентирано програмиране

поради факта че

екземпляр на класа има същото име,

и вградена функция

ord(), който се използва от метода на класа

ред. В този случай извикването на ord() ще се интерпретира като опит за извикване на екземпляр на ord, което ще хвърли изключение TypeError. Този проблем не възниква при импортиране на модул с дефиниция на клас Ord, тъй като в този случай екземплярът ord, създаден в интерактивната обвивка, и функцията ord(), използвана от класа Ord, ще бъдат в различни модули и следователно единият няма да бъде заменен от другия. Ако наистина е необходимо да се създаде този клас в интерактивна обвивка и да се използва вградена функция в него, това може да стане, като се принуди класът да извика самата вградена функция — в този случай чрез импортиране на модула builtins, който осигурява недвусмислен достъп до всички вградени функции, и извикване на вградената функция като builtins.ord(), а не просто ord().

По-долу е друг пример за малък, но пълен клас. Тойви позволява да създавате "константи". Дори когато използвате този клас, не е трудно да промените стойността на такава "константа", но поне предотвратява най-простите грешки:

def __setattr__(self, име, стойност): ако име в self.__dict__:

raise ValueError("не може да промени атрибут const") self.__dict__[име] = стойност

def __delattr__(self, име): ако име в self.__dict__:

raise ValueError("не може да изтрие атрибут const") raise AttributeError("'' обектът няма атрибут ''"

С този клас можете да създавате постоянни обекти, да кажем така: const=Const(), и да зададете всеки от техните атрибути, които искате, например const.limit = 591. Но след като стойността на атрибута е зададена, той ще бъде само за четене - всеки опит за промяна или премахване на атрибута ще доведе до изключение ValueError. Не отменихме метода __getattr__(), тъй като методът object.__getattr__() на основния клас прави каквото искаме, независимо дали връща стойността на необходимия атрибут или предизвиква AttributeError, ако посоченият атрибут не присъства. Методът __delattr__() имитира съобщението за грешка, което методът __getattr__() хвърля, когато се опитаме да получим достъп до атрибут, който не съществува, което изисква да получим името на класа и името на атрибута, който не съществува. Класът се основава на атрибута __dict__ на обекта, който също се използва от следните методи на базов клас: __getattr__(), __setattr__() и __delattr__(); в този случай използваме само метода __get

Глава 8 Разширени техники за програмиране

attr__() на базовия клас. Всички специални методи, използвани за достъп до атрибути, са изброени в таблица. 8.2.

Таблица 8.2. Специаленатрибути за достъп

Премахва атрибут n от обект x

Връща списък с имена на атрибути

обект х

Връща стойността на атрибут n

обект x, ако съществува

Връща стойността на атрибут n

обект x; подробности в текста

__setattr__(себе си, име, стойност)

Присвоява стойност v на атрибут

Има и друг начин за имплементиране на константи - използване на именувани кортежи. По-долу са дадени няколко примера:

Const = collections.namedtuple("_", "min max")(191, 591) Const.min, Const.max # ще върне: (191, 591)

Offset = collections.namedtuple("_", "id name description")(*range(3)) Offset.id, Offset.name, Offset.description # ще върне: (0, 1, 2)

И в двата случая използвахме безсмислено име за namedtuple, защото се нуждаем само от един екземпляр на tuple всеки път, а не от подклас, който може да се използва за създаване на множество namedtuple екземпляри. Езикът Python не поддържа типове данни enum, но можем да използваме именувани кортежи, за да постигнем същия ефект.

Image.ru, стр. 306

За да завършим нашата дискусия за потребителските инструменти за достъп до атрибути, нека се върнем към примера, който за първи път демонстрирахме в глава 6. В тази глава създадохме клас Image, който имаше фиксирана ширина, височина и цвят на фона, които бяха зададени при създаването на екземпляра Image (и това можеше да се промени, когато изображението беше заредено от файл). Осигурили сме достъп до тези атрибути, като използваме свойства само за четене. Например:

def width(self): връща себе си.__width

Този метод е прост, но може да бъде досаден, ако трябва да внедрите твърде много свойства,Само за четене. По-долу е друго решение, което

Подобрени техники за обектно/ориентирано програмиране

включва обслужване на всички свойства само за четене на класа Image:

def __getattr__(self, name): if name == "colors":

if name в frozenset(): return self.__dict__["___".format(classname, name)]

raise AttributeError("'' обектът няма атрибут ''"

Ако се опитате да осъществите достъп до атрибут на обект и атрибутът не бъде намерен, интерпретаторът ще извика метода __getattr__() (ако приемем, че класът предоставя негова реализация и методът __getattribute__() не е заменен) с името на атрибута като параметър. Реализация на метода __getattr__() трябва да изведе AttributeError, ако не обслужва посочения атрибут.

Например, ако програмата има достъп до атрибута image.colors и интерпретаторът не го намери, ще бъде извикан методът Image.__getattr__(image, "colors"). В този случай методът __get attr__() ще обслужва атрибута с име "colors" и ще върне копие на набора от цветове, използвани в изображението.

Други атрибути са неизменни обекти, така че връщането на директни препратки към тези атрибути е безвредно. Всеки атрибут може да има отделен оператор elif, както е показано по-долу:

elif name == "фон": връща себе си.__фон

Но вместо това използвахме по-компактно решение. Знаейки, че всички неспециални атрибути на обект се съхраняват в самия речник. __dict__, предпочетохме да имаме директен достъп до тях. За частни атрибути (чиито имена започват с две долни черти), имената им се предават във формата _className__attributeName и трябва да вземем това предвидобстоятелство при извличане на стойности на атрибути от личния речник на обект.

За да извършим принуда на име, когато търсим частен атрибут и да пресъздадем правилния текст, когато бъде хвърлено изключение AttributeError, трябва да знаем името на класа, който притежава метода. (Може да не е клас Image, защото обектът може да е екземпляр на подклас, който наследява класа Image.) Всеки обект има специален атрибут __class__, така че винаги можете да се обърнете към атрибута self.__class__ вътре в методите, без да рискувате безкрайна рекурсия.

Обърнете внимание на фината разлика: когато използвате метода __getattr__() и израза self.__class__, достъпът до