Подтвердить что ты не робот

Как я могу открыть поля только для чтения из классов 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).

Это выглядит довольно аккуратно для меня - может ли кто-нибудь увидеть недостаток с ним?

4b9b3361

Ответ 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)