В общем, это не очень хорошая идея по причинам, которые @yak упомянул в своем комментарии.По сути, вы запрещаете пользователю предоставлять действительные аргументы, которые имеют правильные атрибуты / поведение, но не находятся в дереве наследования, в котором вы жестко запрограммированы.
Отказ от ответственности, кроме того, есть несколько вариантов того, чем вы являетесьпытаюсь сделать.Основная проблема заключается в том, что в Python нет личных атрибутов.Поэтому, если у вас есть просто старая ссылка на объект, скажем, self._a
, вы не можете гарантировать, что пользователь не установит его напрямую, даже если вы предоставили установщик, который выполняет проверку типа для него.Приведенные ниже параметры демонстрируют, как действительно обеспечить проверку типов.
Переопределить __setattr __
Этот метод будет удобен только для (очень) небольшого числа атрибутов, для которых вы делаете это.Метод __setattr__
- это то, что вызывается, когда вы используете точечную запись для назначения обычного атрибута.Например,
class A:
def __init__(self, a0):
self.a = a0
Если мы сейчас сделаем A().a = 32
, это вызовет A().__setattr__('a', 32)
под капотом .Фактически, self.a = a0
в __init__
также использует self.__setattr__
.Вы можете использовать это для принудительной проверки типа:
class A:
def __init__(self, a0):
self.a = a0
def __setattr__(self, name, value):
if name == 'a' and not isinstance(value, int):
raise TypeError('A.a must be an int')
super().__setattr__(name, value)
Недостатком этого метода является то, что вам нужно иметь отдельную if name == ...
для каждого типа, который вы хотите проверить (или if name in ...
для проверки несколькихимена для данного типа).Преимущество состоит в том, что это самый простой способ сделать практически невозможным для пользователя обойти проверку типа.
Создать свойство
Свойства - это объекты, которые заменяют ваш обычный атрибут дескриптором.объект (обычно с помощью декоратора).Дескрипторы могут иметь методы __get__
и __set__
, которые настраивают доступ к базовому атрибуту.Это все равно что взять соответствующую ветку if
в __setattr__
и поместить ее в метод, который будет запускаться только для этого атрибута.Вот пример:
class A:
def __init__(self, a0):
self.a = a0
@property
def a(self):
return self._a
@a.setter
def a(self, value):
if not isinstance(value, int):
raise TypeError('A.a must be an int')
self._a = value
Несколько иной способ сделать то же самое можно найти в ответе @ jsbueno.
Хотя использование свойства таким способом изящно и в основном решает проблемуЭто действительно пара проблем.Во-первых, у вас есть «приватный» атрибут _a
, который пользователь может изменять напрямую, минуя проверку типа.Это почти та же проблема, что и при использовании простого метода получения и установки, за исключением того, что теперь a
доступен как «правильный» атрибут, который перенаправляет к установщику за кулисами, что снижает вероятность того, что пользователь будет связываться с _a
,Вторая проблема заключается в том, что у вас есть лишний метод получения, чтобы свойство работало как чтение-запись.Эти проблемы являются предметом этого вопроса.
Создание дескриптора только для истинного установщика
Это решение, вероятно, является наиболее надежным из всех.Это предлагается в принятом ответе на вопрос, упомянутый выше.По сути, вместо использования свойства, которое имеет кучу изысков и удобств, от которых вы не можете избавиться, создайте свой собственный дескриптор (и декоратор) и используйте его для любых атрибутов, требующих проверки типа:
class SetterProperty:
def __init__(self, func, doc=None):
self.func = func
self.__doc__ = doc if doc is not None else func.__doc__
def __set__(self, obj, value):
return self.func(obj, value)
class A:
def __init__(self, a0):
self.a = a0
@SetterProperty
def a(self, value):
if not isinstance(value, int):
raise TypeError('A.a must be an int')
self.__dict__['a'] = value
Установщик хранит фактическое значение непосредственно в __dict__
экземпляра, чтобы избежать его повторного использования в течение неопределенного времени.Это позволяет получить значение атрибута без указания явного метода получения.Поскольку дескриптор a
не имеет метода __get__
, поиск будет продолжаться до тех пор, пока он не найдет атрибут в __dict__
.Это гарантирует, что все наборы проходят через дескриптор / установщик, в то время как get разрешают прямой доступ к значению атрибута.
Если у вас есть большое количество атрибутов, которые требуют такой проверки, вы можете переместить строку self.__dict__['a'] = value
в метод дескриптора __set__
:
class ValidatedSetterProperty:
def __init__(self, func, name=None, doc=None):
self.func = func
self.__name__ = name if name is not None else func.__name__
self.__doc__ = doc if doc is not None else func.__doc__
def __set__(self, obj, value):
ret = self.func(obj, value)
obj.__dict__[self.__name__] = value
class A:
def __init__(self, a0):
self.a = a0
@ValidatedSetterProperty
def a(self, value):
if not isinstance(value, int):
raise TypeError('A.a must be an int')
Обновление
Python3.6 делает это для вас практически из коробки: https://docs.python.org/3.6/whatsnew/3.6.html#pep-487-descriptor-protocol-enhancements
TL; DR
Для очень небольшого числа атрибутов, требующих проверки типа, переопределите __setattr__
напрямую. Для большего количества атрибутов используйте дескриптор только для установки, как показано выше. Использование свойств непосредственно для такого рода приложений создает больше проблем, чем решает.