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

Могу ли я ограничить объекты в 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), и он печатает привет без ошибок. Я не хочу, чтобы пользователи могли создавать новые такие как курица.

4b9b3361

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