Могу ли я ограничить объекты в Python3, чтобы разрешены только атрибуты, которые я создаю setter?
У меня есть что-то, называемое Node. Оба определения и теоремы являются типом node, но только определениям должно быть разрешено иметь атрибут plural
:
class Definition(Node):
def __init__(self,dic):
self.type = "definition"
super(Definition, self).__init__(dic)
self.plural = move_attribute(dic, {'plural', 'pl'}, strict=False)
@property
def plural(self):
return self._plural
@plural.setter
def plural(self, new_plural):
if new_plural is None:
self._plural = None
else:
clean_plural = check_type_and_clean(new_plural, str)
assert dunderscore_count(clean_plural)>=2
self._plural = clean_plural
class Theorem(Node):
def __init__(self, dic):
self.type = "theorem"
super().__init__(dic)
self.proofs = move_attribute(dic, {'proofs', 'proof'}, strict=False)
# theorems CANNOT have plurals:
# if 'plural' in self:
# raise KeyError('Theorems cannot have plurals.')
Как вы можете видеть, у определений есть plural.setter
, но теорем нет. Однако код
theorem = Theorem(some input)
theorem.plural = "some plural"
работает отлично и не вызывает ошибок. Но я хочу, чтобы это привело к ошибке. Как вы можете видеть, я попытался проверить множественные числа вручную в нижней части моего кода, но это будет только патч. Я хотел бы заблокировать установку атрибута ANY, который явно не определен. Какова наилучшая практика для такого рода вещей?
Я ищу ответ, который удовлетворяет требованиям цыпленка":
Я не думаю, что это решает мою проблему. В обоих ваших решениях я могу добавьте код t.chicken = 'hi'; print (t.chicken), и он печатает привет без ошибок. Я не хочу, чтобы пользователи могли создавать новые такие как курица.
Ответы
Ответ 1
Короткий ответ: "Да, вы можете".
Следующий вопрос: "Почему?" Одна из сильных сторон Python - замечательный динамизм, и, ограничивая эту способность, вы фактически делаете свой класс менее полезным (но см. Редактирование внизу).
Однако есть веские причины быть ограничительными, и если вы решите пойти по этому маршруту, вам нужно будет изменить свой метод __setattr__
:
def __setattr__(self, name, value):
if name not in ('my', 'attribute', 'names',):
raise AttributeError('attribute %s not allowed' % name)
else:
super().__setattr__(name, value)
Нет необходимости связываться с __getattr__
и __getattribute__
, так как они не вернут атрибут, который не существует.
Вот ваш код, слегка измененный - я добавил метод __setattr__
в Node
и добавил _allowed_attributes
в Definition
и Theorem
.
class Node:
def __setattr__(self, name, value):
if name not in self._allowed_attributes:
raise AttributeError('attribute %s does not and cannot exist' % name)
super().__setattr__(name, value)
class Definition(Node):
_allowed_attributes = '_plural', 'type'
def __init__(self,dic):
self.type = "definition"
super().__init__(dic)
self.plural = move_attribute(dic, {'plural', 'pl'}, strict=False)
@property
def plural(self):
return self._plural
@plural.setter
def plural(self, new_plural):
if new_plural is None:
self._plural = None
else:
clean_plural = check_type_and_clean(new_plural, str)
assert dunderscore_count(clean_plural)>=2
self._plural = clean_plural
class Theorem(Node):
_allowed_attributes = 'type', 'proofs'
def __init__(self, dic):
self.type = "theorem"
super().__init__(dic)
self.proofs = move_attribute(dic, {'proofs', 'proof'}, strict=False)
При использовании он выглядит так:
>>> theorem = Theorem(...)
>>> theorem.plural = 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in __setattr__
AttributeError: attribute plural does not and cannot exist
изменить
Подумав об этом еще немного, я считаю, что хороший компромисс за то, что вы хотите, и чтобы фактически ответить на часть вашего вопроса об ограничении разрешенных изменений в сеттерах, было бы:
- используйте метакласс для проверки класса во время создания и динамического создания кортежа
_allowed_attributes
- изменить
__setattr__
of Node
, чтобы всегда разрешать модификацию/создание атрибутов с хотя бы одним ведущим _
Это дает вам некоторую защиту как от орфографических ошибок, так и от создания атрибутов, которые вам не нужны, но при этом позволяет программистам работать или улучшать классы для своих нужд.
Хорошо, новый метаклас выглядит следующим образом:
class NodeMeta(type):
def __new__(metacls, cls, bases, classdict):
node_cls = super().__new__(metacls, cls, bases, classdict)
allowed_attributes = []
for base in (node_cls, ) + bases:
for name, obj in base.__dict__.items():
if isinstance(obj, property) and hasattr(obj, '__fset__'):
allowed_attributes.append(name)
node_cls._allowed_attributes = tuple(allowed_attributes)
return node_cls
В классе Node
есть две настройки: включите метакласс NodeMeta
и настройте __setattr__
только для блокировки ведущих атрибутов нижнего подчеркивания:
class Node(metaclass=NodeMeta):
def __init__(self, dic):
self._dic = dic
def __setattr__(self, name, value):
if not name[0] == '_' and name not in self._allowed_attributes:
raise AttributeError('attribute %s does not and cannot exist' % name)
super().__setattr__(name, value)
Наконец, подклассы Node
Theorem
и Definition
имеют атрибут type
, перемещенный в пространство имен классов, поэтому нет проблем с их установкой - а в качестве боковой заметки type
является плохим имя, так как это также встроенная функция - может быть node_type
вместо?
class Definition(Node):
type = "definition"
...
class Theorem(Node):
type = "theorem"
...
Как последнее замечание: даже этот метод не застрахован от того, что кто-то действительно добавляет или меняет атрибуты, поскольку object.__setattr__(theorum_instance, 'an_attr', 99)
все еще можно использовать - или (даже проще) _allowed_attributes
можно изменить; однако, если кто-то идет ко всей этой работе, они, надеюсь, знают, что они делают... а если нет, у них есть все части.;)
Ответ 2
Вы можете проверять атрибут каждый раз, когда вы его получаете.
class Theorem(Node):
...
def __getattribute__(self, name):
if name not in ["allowed", "attribute", "names"]:
raise MyException("attribute "+name+" not allowed")
else:
return self.__dict__[name]
def __setattr__(self, name, value):
if name not in ["allowed", "attribute", "names"]:
raise MyException("attribute "+name+" not allowed")
else:
self.__dict__[name] = value
Вы можете создать список разрешенных методов динамически как побочный эффект декоратора:
allowed_attrs = []
def allowed(f):
allowed_attrs.append(f.__name__)
return f
Вам также нужно будет вручную добавлять атрибуты метода.
Ответ 3
Если вы действительно хотите предотвратить все другие динамические атрибуты. Я предполагаю наличие четко определенного временного окна, которое вы хотите разрешить добавлять атрибуты.
Ниже я разрешаю его до завершения инициализации объекта. (вы можете управлять им с помощью переменной allow_dynamic_attribute
.
class A:
def __init__(self):
self.allow_dynamic_attribute = True
self.abc = "hello"
self._plural = None # need to give default value
# A.__setattr__ = types.MethodType(__setattr__, A)
self.allow_dynamic_attribute = False
def __setattr__(self, name, value):
if hasattr(self, 'allow_dynamic_attribute'):
if not self.allow_dynamic_attribute:
if not hasattr(self, name):
raise Exception
super().__setattr__(name, value)
@property
def plural(self):
return self._plural
@plural.setter
def plural(self, new_plural):
self._plural = new_plural
a = A()
print(a.abc) # fine
a.plural = "yes" # fine
print(a.plural) # fine
a.dkk = "bed" # raise exception
Или он может быть более компактным таким образом, я не мог понять, как MethodType + super может ладить вместе.
import types
def __setattr__(self, name, value):
if not hasattr(self, name):
raise Exception
else:
super().__setattr__(name,value) # this doesn't work for reason I don't know
class A:
def __init__(self):
self.foo = "hello"
# after this point, there no more setattr for you
A.__setattr__ = types.MethodType(__setattr__, A)
a = A()
print(a.foo) # fine
a.bar = "bed" # raise exception
Ответ 4
Да, вы можете создавать частные члены, которые не могут быть изменены извне класса. Имя переменной должно начинаться с двух символов подчеркивания:
class Test(object):
def __init__(self, t):
self.__t = t
def __str__(self):
return str(self.__t)
t = Test(2)
print(t) # prints 2
t.__t = 3
print(t) # prints 2
Тем не менее, пытаясь получить доступ к такой переменной, как в t.__t = 3
, будет не возбуждать исключение.
Другой подход, который вы можете предпринять для достижения желаемого поведения, - это использование функций. Этот подход потребует "доступа к атрибутам" с использованием функциональной нотации, но если это вас не беспокоит, вы можете получить именно то, что хотите. Следующие демонстрационные "жесткие коды" значений, но, очевидно, вы можете иметь Theorem()
принять аргумент и использовать его для динамического определения значений атрибутов.
Demo:
# -*- coding: utf-8 -*-
def Theorem():
def f(attrib):
def proofs():
return ''
def plural():
return '◊◊◊◊◊◊◊◊'
if attrib == 'proofs':
return proofs()
elif attrib == 'plural':
return plural()
else:
raise ValueError("Attribute [{}] doesn't exist".format(attrib))
return f
t = Theorem()
print(t('proofs'))
print(t('plural'))
print(t('wait_for_error'))
OUTPUT
◊◊◊◊◊◊◊◊
Traceback (most recent call last):
File "/Users/alfasi/Desktop/1.py", line 40, in <module>
print(t('wait_for_error'))
File "/Users/alfasi/Desktop/1.py", line 32, in f
raise ValueError("Attribute [{}] doesn't exist".format(attrib))
ValueError: Attribute [wait_for_error] doesn't exist