Уведомления

Группа в Telegram: @pythonsu

#1 Май 24, 2007 19:36:21

Kolyan
От:
Зарегистрирован: 2007-05-24
Сообщения: 5
Репутация: +  0  -
Профиль   Отправить e-mail  

Еще раз о статических аттрибутах классов

Позволю поднять эту довольно избитую тему, так как несмотря на поиски и чтение соответствующей документации в Интернете, мое понимание данного вопроса до конца не оформилось.

Итак, вопрос в следующем: есть ли в Python статические аттрибуты классов в том понимании этого термина, которое выработалось в мире C++ и Java?

Предположим, нам необходим статический аттрибут для подсчета количества созданных экземпляров некоторого класса. Что мы делаем в Python (для чистоты буду использовать new-style ООП, хотя в случае с классическим ООП, ситуация, думаю, не изменится):

class C(object):
counter = 0 # вот это “по идее” статический аттрибут

def __init__(self):
C.counter += 1 # во время инициализации нового экземпляра
# увеличиваем общий для всех экземпляров счетчик на 1-цу

Смотрим что получилось:

>>> inst1 = C()
>>> C.counter
1
>>> inst2 = C()
>>> C.counter
2

Все работает как надо, и, казалось бы, counter - то что нам нужно. Но тут наступает то, что не увязывается с концепцией статического аттрибута. Дело в том, что помимо аттрибута counter класса C, создаются аттрибуты counter экземпляров класса C, для каждого экземпляра в отдельности:

>>> inst1.counter
2
>>> inst2.counter
2

которые изначально ссылаются на тот же объект int, что и аттрибут C.counter:

>>> inst1.counter is C.counter
True
>>> inst2.counter is C.counter
True

Но стоит поработать с аттрибутом counter какого-то экземпляра в отдельности, его связь с аттрибутом counter класса C исчезает, что и понятно:

>>> inst1.counter = 5
>>> C.counter
2
>>> inst1.counter is C.counter
False

Это совсем не та семантика статических аттрибутов данных, которая принята в C++ и в Java. Мне не нужны аттрибуты экземпляра, которые создаются для каждого объявленного в теле класса “статического” аттрибута. Это лишь захламляет память (на каждый аттрибут counter экземлпяров класса C нужно выделять указатель), да и противоречит самому понятию статический аттрибут - он должен быть в единственном экземпляре.

То есть, описанный выше механизм лишь отчасти реализует концепцию статических аттрибутов в Python. Вопрос: есть ли альтернативный механизм, который в точности соответствует принятым понятиям в C++ или Java?

Буду благодарен любым ссылкам, где освещается этот вопрос.



Офлайн

#2 Май 24, 2007 19:57:31

Александр Кошелев
От: Москва
Зарегистрирован: 2007-02-03
Сообщения: 1724
Репутация: +  2  -
Профиль   Отправить e-mail  

Еще раз о статических аттрибутах классов

Kolyan
То есть, описанный выше механизм лишь отчасти реализует концепцию статических аттрибутов в Python. Вопрос: есть ли альтернативный механизм, который в точности соответствует принятым понятиям в C++ или Java?
Ну никто тебе и не обещал, что это будет статический атрибут.
По крайней мере можно сделать статическую функцию, см. staticmethod в документации. Ещё посмотри функцию classmethod



Офлайн

#3 Май 24, 2007 20:05:02

bialix
От:
Зарегистрирован: 2006-07-13
Сообщения: 774
Репутация: +  1  -
Профиль   Отправить e-mail  

Еще раз о статических аттрибутах классов

возможно property как-то поможет, но не уверен.
в общем случае in-place changing будет работать, если изменять не сам атрибут, а косвенно элементы списка или словаря.
но это на самом деле криво.

как всегда вопрос: а зачем?



Офлайн

#4 Май 25, 2007 00:25:03

Андрей Светлов
От:
Зарегистрирован: 2007-05-15
Сообщения: 3137
Репутация: +  14  -
Профиль   Адрес электронной почты  

Еще раз о статических аттрибутах классов

property поможет наверняка. Только в нем вместо self.attr следует использовать self.__class__.attr - и будет полная иллюзия статической переменной. Можно написать специализированный дескриптор вместо property (довольно слабо использующийся сейчас подход, но он интенсивно набирает обороты). Дел-то - переопределить __set__ и __get__. __delete__, кажется, не понадобится.

Теперь о главном. Я практически никогда не использую staticmethod. classmethod его отлично перекрывает и дает еще много дополнительных возможностей. staticmethod, по моему убеждению, полезен только в случае биндинга с C++ (мы используем boost.python), поскольку в С++ нет аналога аттрибута класса. А classmethod и аттрибут класса отлично вписываются в наследование (мне это кажется немаловажным).

Нужно четко представлять себе механизм поиска объекта по имени аттрибута. Стандартная документация (http://docs.python.org/ref/attribute-access.html)
__getattribute__, __getattr__, поиск в словаре инстанции, поиск в словаре класса (с специальными случаями, когда в словаре класса находим дескриптор).

Это была бы хорошая тема для отдельной статьи, а подобные лекции я сотрудникам читал много раз (и каждый раз открывался еще один любопытный аспект).
С удовольствием отвечу на более конкретный вопрос, а писать статью кажется, хотел бы, но, похоже, в ближайшем будущем не найду для этой работы времени.

PS. Возможно, я не до конца правильно понимаю функционирование интерпретатора - поправьте меня. Я же все таки не GvR :)



Офлайн

#5 Май 25, 2007 05:47:34

Striver
От:
Зарегистрирован: 2006-10-26
Сообщения: 247
Репутация: +  22  -
Профиль   Отправить e-mail  

Еще раз о статических аттрибутах классов

Ну если нужен атрибут класса (а не экземпляра), то и присваивать надо именно ему:

>>> class A(object):
… counter=0
>>> a1=A()
>>> a2=A()
>>> a1.counter
0
>>> a1.__class__.counter
0
>>> a1.__class__.counter=3
>>> a1.counter
3
>>> a2.counter
3



Офлайн

#6 Май 25, 2007 12:21:51

Kolyan
От:
Зарегистрирован: 2007-05-24
Сообщения: 5
Репутация: +  0  -
Профиль   Отправить e-mail  

Еще раз о статических аттрибутах классов

Андрей Светлов
property поможет наверняка. Только в нем вместо self.attr следует использовать self.__class__.attr - и будет полная иллюзия статической переменной. Можно написать специализированный дескриптор вместо property (довольно слабо использующийся сейчас подход, но он интенсивно набирает обороты). Дел-то - переопределить __set__ и __get__. __delete__, кажется, не понадобится.
Если я правильно понял вашу идею, вы предлагаете сделать что-то вроде этого:

class C(object):
_counter = 0
def get_counter(self): return self.__class__._counter
def set_counter(self, val): self.__class__._counter = val
counter = property(get_counter, set_counter)
def __init__(self):
self.counter += 1

Тогда будем иметь:

>>> inst1 = C()
>>> inst2 = C()
>>> inst1.counter
2
>>> inst2.counter
2
>>> inst1.counter = 4
>>> inst2.counter
4

То есть, мы решим проблему несвязанности аттрибутов экземпляров между собой - теперь все аттрибуты x.counter, где x - некоторый экземпляр класса C, - по крайней мере внешне выглядят как один и тот же аттрибут, единый для всех экземпляров, что хоть как-то согласуется с концепцией статических аттрибутов.
Однако останутся другие проблемы, которые сводят на нет все усилия:

1. Аттрибут для “внутреннего пользования” _counter все так же будет делегироваться всем экземплярам класса С, и хотя мы его использовать не должны и не будем, на него все так же будет тратиться память, да и захламляться внутренняя область имен экземпляров.

2. Появится другая проблема. Наш “статический аттрибут” теперь будет являться таковым только, если обращаться к нему через экземпляры класса, а не через сам класс:

>>> inst1.counter
4
>>> C.counter
<property object at 0x0163F940>

И это очень плохо. Потому как, что такое статический аттрибут (или член, в терминах C++)? Страуструп дает такое определение: "Переменная, которая является частью класса, но не является частью объекта этого класса, называется статическим членом". У нас же чисто внешне все выглядит так, будто counter - это аттрибут экземпляра, а не класса C. В C++ мы также можем обратиться к статическому члену через объект, либо через класс: inst.counter или C::counter, но при этом вторая запись всегда предпочтительнее, ибо подчеркивает саму суть (определение) статического члена. В нашем случае мы лишены возможности такой записи, и поэтому должны использовать лишь первый вариант записи, что может привести к разночтениям: попробуйте сразу разобраться, обращаетесь ли вы к обычному аттрибуту экземпляра, или к статическому - аттрибуту класса - с помощью такой записи: inst.counter. Если вы не писали этот код, вам придется лезть в описание класса.

Из всего вышесказанного можно сделать вывод, что для полноценной поддержки статических аттрибутов (членов, полей - в терминах C++ и Java) нужна специфическая языковая семантика и синтаксис, которые сейчас, судя по ответам, в Python отсутствуют. Это я и хотел узнать. Я думаю, что концепция статических аттрибутов (членов, полей) - это неотъемлемая часть парадигмы ООП, ибо являет собой простое средство моделирования общих (разделяемых) переменных для класса и его экземпляров. Можно попытаться смоделировать разделяемые переменные с помощью других механизмов (как в данном случае - с помощью свойств - properties), но 1. это стоит бОльших усилий и загромождает код ненужными деталями 2. обычно не вписывается полностью в концепцию общих (разделяемых) переменных, как в данном случае.



Офлайн

#7 Май 25, 2007 14:02:14

Kolyan
От:
Зарегистрирован: 2007-05-24
Сообщения: 5
Репутация: +  0  -
Профиль   Отправить e-mail  

Еще раз о статических аттрибутах классов

Kolyan
1. Аттрибут для “внутреннего пользования” _counter все так же будет делегироваться всем экземплярам класса С, и хотя мы его использовать не должны и не будем, на него все так же будет тратиться память, да и захламляться внутренняя область имен экземпляров.
Прошу извинить, это я неправду сказал: эта проблема отпадает, ничего никому не делегируется, я уже разобрался. _counter будет аттрибутом объекта класса C и все обращения к x._counter, где x - некоторый экземпляр класса C, будут приводить к обращению C._counter (по правилу поиска аттрибутов), т.к. в x.__dict__ никакого ключа _counter нет и быть не может, пока мы явно не создадим его, например, так: x._counter = 5.

Но вторая проблема остается актуальной.



Офлайн

#8 Май 25, 2007 14:22:01

Striver
От:
Зарегистрирован: 2006-10-26
Сообщения: 247
Репутация: +  22  -
Профиль   Отправить e-mail  

Еще раз о статических аттрибутах классов

Вопрос bialix'а “как всегда вопрос: а зачем?” на самом деле актуальнее, чем казался сначала.
Если нужно создать счетчик экземпляров, или подобную ему штуку, то предложенные способы вполне подходят.
Если же необходимо сделать “поведение в точности как у C++”… Насколько я понимаю, создатели Питона никогда не ставили перед собой таких целей.



Офлайн

#9 Май 25, 2007 14:25:07

bialix
От:
Зарегистрирован: 2006-07-13
Сообщения: 774
Репутация: +  1  -
Профиль   Отправить e-mail  

Еще раз о статических аттрибутах классов

Kolyan
Из всего вышесказанного можно сделать вывод, что для полноценной поддержки статических аттрибутов (членов, полей - в терминах C++ и Java) нужна специфическая языковая семантика и синтаксис, которые сейчас, судя по ответам, в Python отсутствуют.
Из этого следует вывод, что Питон – это не C++ и не Java.
И что каждый язык имеет свои особенности, и писать на Питоне надо как на Питоне, а не как на Яве.

http://dirtsimple.org/2004/12/python-is-not-java.html

А еще в Ява нету множественного наследования, насколько я помню. От этого факта его объектная модель не страдает?



Офлайн

#10 Май 25, 2007 14:26:40

bialix
От:
Зарегистрирован: 2006-07-13
Сообщения: 774
Репутация: +  1  -
Профиль   Отправить e-mail  

Еще раз о статических аттрибутах классов

просто до кучи еще о разнице между Питоном и Си++
http://www.onembedding.com/tools/python/articles/compare2cpp/



Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version