Pull to refresh

Метаморфозы атрибутов класса

Reading time 2 min
Views 4.2K
Короткая заметка из серии «Вас предупреждали».

Переход с классических языков программирования на Питон доставляет немало сюрпризов.
Читаем документацию:
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)
Tags:
Hubs:
+5
Comments 17
Comments Comments 17

Articles