python, __slots__ и атрибут только для чтения

Я хочу создать объект в python, который имеет несколько атрибутов, и я хочу защитить себя от случайного использования неправильного имени атрибута. Код выглядит следующим образом:

class MyClass( object ) :
    m = None # my attribute
    __slots__ = ( "m" ) # ensure that object has no _m etc

a = MyClass() # create one
a.m = "?"  # here is a PROBLEM

Но после запуска этого простого кода я получаю очень странную ошибку:

Traceback (most recent call last):
  File "test.py", line 8, in <module>
    a.m = "?"
AttributeError: 'test' object attribute 'm' is read-only

Есть ли какой-нибудь мудрый программист, который может уделить немного своего времени и просветить меня об ошибках «только для чтения»?


person grigoryvp    schedule 04.05.2009    source источник
comment
Это кажется ненужным вариантом использования. Зачем ты это делаешь? Почему вы не используете свойства?   -  person S.Lott    schedule 04.05.2009
comment
Я хочу, чтобы я написал имя свойства с опечаткой, например, если я напишу object.my_prperty = 1 вместо object.my_property = 1 (пропущено «o»), python не будет показывать никаких ошибок, но у меня будет логика ошибка в моей программе. Поэтому я хочу ограничить свойства моим предопределенным набором, чтобы только они могли быть доступны. Как я могу решить это со свойствами?   -  person grigoryvp    schedule 04.05.2009
comment
как правило, в python мы допускаем возможность орфографических ошибок в заданиях и пишем хорошие тесты, которые также выявляют поверхностные и более семантические ошибки. Я скромно предлагаю, чтобы, если вы хотите писать на питоне, вам следовало подумать о том, чтобы делать это по-питоновски, а не бороться с языком зубами и когтями. Добавлю, что опечатки в моем коде на Python стоили мне может быть 20 минут времени за последние 9 лет.   -  person llimllib    schedule 04.05.2009
comment
Вы также можете заглянуть в Python IDE. Я обычно использую IDLE, где (в оболочке, а не в файле .py) вы можете сделать: a=MyClass(), затем введите a. и он покажет вам все атрибуты. Я также использовал PyDev, плагин Python для Eclipse, который делает это даже в файле .py. Там много других. Погуглите или найдите Python IDE на этом сайте, и я уверен, что вы найдете то, что вам нравится.   -  person MatrixFrog    schedule 05.05.2009
comment
Ошибка должна выглядеть так: AttributeError: 'MyClass' object attribute 'm' is read-only   -  person Mr_and_Mrs_D    schedule 03.03.2016


Ответы (5)


Когда вы объявляете переменные экземпляра с помощью __slots__, Python создает объект-дескриптор как класс переменная с тем же именем. В вашем случае этот дескриптор перезаписывается переменной класса m, которую вы определяете в следующей строке:

  m = None # my attribute

Вот что вам нужно сделать: Не определяйте переменную класса с именем m и инициализируйте переменную экземпляра m в методе __init__.

class MyClass(object):
  __slots__ = ("m",)
  def __init__(self):
    self.m = None

a = MyClass()
a.m = "?"

В качестве примечания: кортежи с отдельными элементами требуют запятой после элемента. Оба работают в вашем коде, потому что __slots__ принимает одну строку или итерируемую/последовательность строк. В общем, чтобы определить кортеж, содержащий элемент 1, используйте (1,) или 1,, а не (1).

person Ayman Hourieh    schedule 04.05.2009
comment
Знаете ли вы, почему, если бы я написал: slots = [ m ] m = 5 , Python перезапишет объект дескриптора переменной и не будет вместо этого использовать метод set() объекта дескриптора для установки значения? - person grigoryvp; 04.05.2009
comment
Потому что m в этом контексте является переменной класса, а не переменной экземпляра. Чтобы использовать метод set() дескриптора, вы должны присвоить значение m объекта, а не класса. Если вы хотите инициализировать переменную экземпляра объекта, сделайте это из метода init, как это сделал я в своем фрагменте кода. - person Ayman Hourieh; 04.05.2009
comment
Ключ из документов (который, кстати, перемещен сюда) — __slots__ are implemented at the class level by creating descriptors for each variable name. As a result, class attributes cannot be used to set default values for instance variables defined by __slots__; otherwise, the class attribute would overwrite the descriptor assignment. Итак, что происходит, так это то, что (независимо от порядка определения m и __slots__ ?) при попытке присваивания метод set больше ни на что не сопоставляется - и не получает, но возвращается к MyClass.m - person Mr_and_Mrs_D; 03.03.2016

Вы совершенно неправильно используете __slots__. Это предотвращает создание __dict__ для экземпляров. Это имеет смысл только в том случае, если вы столкнетесь с проблемами памяти со многими небольшими объектами, потому что избавление от __dict__ может уменьшить занимаемую площадь. Это хардкорная оптимизация, которая не нужна в 99,9% всех случаев.

Если вам нужна та безопасность, которую вы описали, то Python действительно не тот язык. Лучше используйте что-то строгое, например Java (вместо того, чтобы пытаться писать Java на Python).

Если вы не можете сами понять, почему атрибуты класса вызвали эти проблемы в вашем коде, возможно, вам стоит дважды подумать, прежде чем внедрять подобные языковые хаки. Вероятно, было бы разумнее сначала лучше познакомиться с языком.

Для полноты картины вот ссылка на документацию по слотам.

person nikow    schedule 04.05.2009
comment
+1 за полноту. Как упоминалось повсюду, слоты — неплохая идея, но требует осторожного использования. Это больше, чем просто проверка безопасности, она значительно усложняет реализацию, требующую внимания к тому, как вы используете объект. - person David Berger; 05.05.2009

__slots__ работает с переменными экземпляра, тогда как у вас есть переменная класса. Вот как вы должны это делать:

class MyClass( object ) :
  __slots__ = ( "m", )
  def __init__(self):
    self.m = None

a = MyClass()
a.m = "?"       # No error
person RichieHindle    schedule 04.05.2009

Учти это.

class SuperSafe( object ):
    allowed= ( "this", "that" )
    def __init__( self ):
        self.this= None
        self.that= None
    def __setattr__( self, attr, value ):
        if attr not in self.allowed:
            raise Exception( "No such attribute: %s" % (attr,) )
        super( SuperSafe, self ).__setattr__( attr, value )

Лучшим подходом является использование модульных тестов для такого рода проверки. Это изрядное количество накладных расходов во время выполнения.

person S.Lott    schedule 04.05.2009
comment
Но это больше строк кода по сравнению с слотами? Зачем вручную проверять то, что может быть автоматически выполнено языком. - person grigoryvp; 04.05.2009
comment
Чтобы избежать очень странных сообщений об ошибках и не тратить много времени на отладку внутренних тайн слотов. Я предпочитаю очевидные и простые решения, где для отладки не требуются тайные знания. - person S.Lott; 04.05.2009
comment
Невозможность добавления новых экземпляров переменных является побочным эффектом slots, а не целью. (что является оптимизацией производительности). - person Ravi; 05.05.2009
comment
@Ravi — И в этом, я думаю, суть проблемы. - person Ben Blank; 19.05.2009

class MyClass( object ) :
    m = None # my attribute

Здесь m — это атрибуты класса, а не атрибут экземпляра. Вам нужно подключить его к вашему экземпляру самостоятельно в __init__.

person pinseng    schedule 20.10.2014