Как я могу открыть поля только для чтения из классов Python?
У меня много разных небольших классов, которые имеют по несколько полей, например. это:
class Article:
def __init__(self, name, available):
self.name = name
self.available = available
Какой самый простой и/или самый идиоматический способ сделать поле name
только для чтения, так что
a = Article("Pineapple", True)
a.name = "Banana" # <-- should not be possible
больше невозможно?
Вот что я рассмотрел до сих пор:
-
Используйте getter (ugh!).
class Article:
def __init__(self, name, available):
self._name = name
self.available = available
def name(self):
return self._name
Уродливый, непитонический - и много кода шаблона для написания (особенно, если у меня есть несколько полей для чтения только для чтения). Тем не менее, это делает работу, и легко понять, почему это так.
-
Используйте __setattr__
:
class Article:
def __init__(self, name, available):
self.name = name
self.available = available
def __setattr__(self, name, value):
if name == "name":
raise Exception("%s property is read-only" % name)
self.__dict__[name] = value
Выглядит хорошо на стороне вызывающего абонента, кажется, является идиоматическим способом выполнения этой работы, но, к сожалению, у меня есть много классов с несколькими полями, чтобы читать только каждый. Поэтому мне нужно добавить реализацию __setattr__
ко всем из них. Или, может быть, использовать какой-то миксин? В любом случае, я должен решить, как вести себя, если клиент попытается присвоить значение полю только для чтения. Допустим какое-то исключение, я думаю - но что?
-
Используйте функцию утилиты для автоматического определения свойств (и необязательно геттеров). Это в основном та же идея, что и (1), за исключением того, что я не пишу геттеры явно, а скорее делаю что-то вроде
class Article:
def __init__(self, name, available):
# This function would somehow give a '_name' field to self
# and a 'name()' getter to the 'Article' class object (if
# necessary); the getter simply returns self._name
defineField(self, "name")
self.available = available
Недостатком этого является то, что я даже не знаю, возможно ли это (или как его реализовать), поскольку я не знаком с созданием кода времени выполнения в Python.: -)
До сих пор (2) представляется мне наиболее перспективным, за исключением того, что мне нужны определения __setattr__
для всех моих классов. Хотелось бы, чтобы был способ "аннотировать" поля, чтобы это происходило автоматически. Кто-нибудь имеет лучшую идею?
Для чего это стоит, я пью Python 2.6.
UPDATE:
Спасибо за все интересные ответы! К настоящему времени у меня есть следующее:
def ro_property(o, name, value):
setattr(o.__class__, name, property(lambda o: o.__dict__["_" + name]))
setattr(o, "_" + name, value)
class Article(object):
def __init__(self, name, available):
ro_property(self, "name", name)
self.available = available
Кажется, это работает очень хорошо. Единственные изменения, необходимые для исходного класса, - это
- Мне нужно наследовать
object
(что, во всяком случае, это не такая глупая вещь).
- Мне нужно изменить
self._name = name
на ro_property(self, "name", name)
.
Это выглядит довольно аккуратно для меня - может ли кто-нибудь увидеть недостаток с ним?
Ответы
Ответ 1
Я использовал бы property
в качестве декоратора для управления вашим получателем для name
(см. пример для класса Parrot
в документации). Используйте, например, что-то вроде:
class Article(object):
def __init__(self, name, available):
self._name = name
self.available = available
@property
def name(self):
return self._name
Если вы не определяете setter для свойства name
(используя декоратор x.setter
вокруг функции), это вызывает AttributeError
при попытке и reset name
.
Примечание. Вы должны использовать классы нового стиля Python (т.е. в Python 2.6, которые вы должны наследовать от object
), чтобы свойства работали правильно. Это не в соответствии с @SvenMarnach.
Ответ 2
Как указано в других ответах, использование свойства - это путь для атрибутов только для чтения. Решение в Ответ Криса является самым чистым: он использует встроенный property()
простой, простой способ. Все, кто знаком с Python, узнают этот шаблон, и там не будет никакого вуду, связанного с доменом.
Если вам не нравится, что для каждого свойства необходимо определить три строки, вот еще один прямой способ:
from operator import attrgetter
class Article(object):
def __init__(self, name, available):
self._name = name
self.available = available
name = property(attrgetter("_name"))
В общем, мне не нравится определять функции, специфичные для домена, для выполнения чего-то, что можно сделать достаточно легко со стандартными инструментами. Чтение кода намного проще, если вам не нужно сначала использовать все элементы для проекта.
Ответ 3
Основываясь на ответе Криса, но, возможно, больше pythonic:
def ro_property(field):
return property(lambda self : self.__dict__[field])
class Article(object):
name = ro_property('_name')
def __init__(self):
self._name = "banana"
При попытке изменить свойство он поднимет AttributeError
.
a = Article()
print a.name # -> 'banana'
a.name = 'apple' # -> AttributeError: can't set attribute
ОБНОВЛЕНИЕ. О вашем обновленном ответе (небольшая) проблема, которую я вижу, заключается в том, что вы изменяете определение свойства в классе каждый раз, когда вы создаете экземпляр. И я не думаю, что это такая хорошая идея. Поэтому я поместил вызов ro_property
вне функции __init__
Что??
def ro_property(name):
def ro_property_decorator(c):
setattr(c, name, property(lambda o: o.__dict__["_" + name]))
return c
return ro_property_decorator
@ro_property('name')
@ro_property('other')
class Article(object):
def __init__(self, name):
self._name = name
self._other = "foo"
a = Article("banana")
print a.name # -> 'banana'
a.name = 'apple' # -> AttributeError: can't set attribute
Декораторы класса причудливы!
Ответ 4
Следует отметить, что всегда можно изменять атрибуты объекта в Python - в Python нет действительно частных переменных. Это просто, что некоторые подходы делают это немного сложнее. Но определенный кодер всегда может искать и изменять значение атрибута. Например, я всегда могу изменить ваш __setattr__
, если я хочу...
Для получения дополнительной информации см. Раздел 9.6 Учебника по Python. Python использует манипуляцию имени, когда атрибуты имеют префикс __
, поэтому фактическое имя во время выполнения отличается, но вы все равно можете получить то, что это имя во время выполнения (и, таким образом, изменить атрибут).
Ответ 5
Я бы придерживался вашего варианта 1, но уточнил его для использования свойства Python:
class Article
def get_name(self):
return self.__name
name = property(get_name)