我可以在Python3中限制对象,以便只允许我创建setter的属性吗?

时间:2015-08-09 17:26:55

标签: oop python-3.x inheritance

我有一个叫做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"

运行得很好,没有错误。但我希望它引发错误。正如您所看到的,我尝试在显示的代码底部手动检查复数,但这只是一个补丁。我想阻止没有明确定义的任何属性的设置。这种事情的最佳做法是什么?

我正在寻找满足" chicken" requirement的答案:

  

我认为这不能解决我的问题。在你的两个解决方案中,我都可以   附上代码t.chicken =' hi&#39 ;;打印(t.chicken),打印喜   没有错误。我不希望用户能够弥补新的   像鸡一样的属性。

3 个答案:

答案 0 :(得分:4)

简短的回答是“是的,你可以。”

后续问题是“为什么?” 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添加到DefinitionTheorem。< / p>

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

修改

考虑到这一点之后,我认为对于你想要的东西是一个很好的折衷方案,并且实际上回答你关于限制只允许更改为setter的问题的一部分,那就是:

  • 使用元类在创建时检查类并动态构建_allowed_attributes元组
  • 修改__setattr__的{​​{1}}以始终允许修改/创建至少有一个前导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) 子类NodeTheoremDefinition属性移入类名称空间,因此设置它们没有问题 - 并且作为一个方面请注意,type是一个糟糕的名称,因为它也是一个内置函数 - 可能是type而已?

node_type

最后要注意的是:即使这种方法也不能免除实际添加或更改属性的人,因为仍然可以使用class Definition(Node): type = "definition" ... class Theorem(Node): type = "theorem" ... - 或者(更简单)可以修改object.__setattr__(theorum_instance, 'an_attr', 99);然而,如果有人要完成所有这些工作,他们希望知道他们在做什么......如果没有,他们拥有所有的部分。 ;)

答案 1 :(得分:0)

每次访问时都可以检查属性。

@implementation

您可以动态构建允许的方法列表作为装饰器的副作用:

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

您还需要手动添加非方法属性。

答案 2 :(得分:-1)

是的,您可以创建无法从类外部修改的私有成员。变量名称应以two underscores开头:

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()接受一个参数并使用它来动态地为属性设置值。

<强>演示:

# -*- 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')) 

<强>输出


◊◊◊◊◊◊◊◊
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