Метаморфозы атрибутов класса
Короткая заметка из серии «Вас предупреждали».
Переход с классических языков программирования на Питон доставляет немало сюрпризов.
Читаем документацию:
Generally speaking, instance variables are for data unique to each instance and class variables are for attributes and methods shared by all instances of the class
Попробуем поиграться
class Vessel:
#class attribute
vtype = "boat"
#instance attribute
def __init__(self, name):
self.name = name
# ислючительно для печати
def __str__(self):
res= '>>'
for a in inspect.getmembers( self):
if not a[0].startswith("__"): res += f"{a[0]}={a[1]:<14}"
for a in inspect.getmembers( self.__class__):
if not a[0].startswith("__"): res += f"__class__.{a[0]}={a[1]:<14}"
return res
Создаем два объекта проверим значения всех атрибутов:
Iowa = Vessel("Iowa")
Drum=Vessel("Drum")
printAttr(Iowa, Drum)
>>name=Iowa vtype=boat __class__.vtype=boat
>>name=Drum vtype=boat __class__.vtype=boat
Пока все как и ожидалось.
Попытаемся изменить vtype: это можно сделать двумя способами, которые по сути просто разный синтаксис одного и того же
Vessel.vtype = "USS boat"
printAttr(Iowa, Drum)
>>name=Iowa vtype=USS boat __class__.vtype=USS boat
>>name=Drum vtype=USS boat __class__.vtype=USS boat
Iowa.__class__.vtype = 'USS WW2 Boat'
printAttr(Iowa, Drum)
>>name=Iowa vtype=USS WW2 Boat __class__.vtype=USS WW2 Boat
>>name=Drum vtype=USS WW2 Boat __class__.vtype=USS WW2 Boat
И снова все в порядке.
Теперь попытаемся сделать тоже самое через атрибут объекта.
Drum.vtype = 'submarine'
printAttr(Iowa, Drum)
>>name=Iowa vtype=USS WW2 Boat __class__.vtype=USS WW2 Boat
>>name=Drum vtype=submarine __class__.vtype=USS WW2 Boat
И вот первая неожиданность: несмотря на то, что vtype это атрибут класса, неожиданно он становится атрибутом объекта.
Проверим:
Vessel.vtype = "NAVY Museum"
>>name=Iowa vtype=NAVY Museum __class__.vtype=NAVY Museum
>>name=Drum vtype=submarine __class__.vtype=NAVY Museum
а что если…
del Drum.vtype
>>name=Iowa vtype=NAVY Museum __class__.vtype=NAVY Museum
>>name=Drum vtype=NAVY Museum __class__.vtype=NAVY Museum
И снова атрибут класса.
Следующее выражение уже не проходит
del Drum.vtype
printAttr(Iowa, Drum)
del Drum.vtype
AttributeError: vtype
И последний пример, эмулирующий переопределения класса и удаление атрибута vtype.
Drum.vtype = 'submarine'
del Vessel.vtype
printAttr(Iowa, Drum)
>>name=Iowa
>>name=Drum vtype=submarine
Если начать разбираться с namespace-ами, то подобное поведение становится понятным.
Однако для программистов, кто раньше работал с нормальными языками, это по меньшей мере кажется странным. А если говорить о больших проектах, которые поддерживаются несколькими поколениями разработчиков, это может оказаться провалом сроков и пр.
Принимая во внимание концепцию Питона, что все открыто для всех, почему бы не сделать доступ к «классным» атрибутам только через __class__ или его аналог. На мой взгляд, это бы хоть как-то оградило от сюрпризов и заставило 10 раз подумать прежде чем присваивать что-то классным атрибутам на уровне объектов.
Update: текст PrintAttr
def printAttr(*o):
for a in o:
print(a)