这是以下帖子的后续问题(检查链接不需要理解问题)
我们将idCounter
设置为类Student的类变量,并计算创建的实例数。
这是班级:
class Student:
# A student ID counter
idCounter = 0
def __init__(self):
self.gpa = 0
self.record = {}
# Each time I create a new student, the idCounter increment
Student.idCounter += 1
self.name = 'Student {0}'.format(Student.idCounter)
现在,我们实例化几个实例,然后检查idCounter
:
student1 = Student()
student2 = Student()
student3 = Student()
student4 = Student()
Student.idCounter
4
但是,如果你能做到这一点,维持一个计数器就没有用了:
Student.idCounter = 2000
现在创建新实例:
student5 = Student()
并检查idCounter
:
Student.idCounter
2001
idCounter
可以在没有运行__init__
的情况下搞砸柜台。
如何创建一个仅在__init__
运行时递增的计数器(或任何类变量)?并且不能通过从类中调用类变量来独立修改,如上所示。
是否有一种通用的方法来限制使用语法修改类变量?
ClassName.ClassVariable = new_value
谢谢。
答案 0 :(得分:3)
使用property
改进版本但原则相同:
class Meta(type):
def __init__(cls, *args, **kwargs):
cls.__value = 0
super().__init__(*args, **kwargs)
@property
def idCounter(cls):
return cls.__value
class Student(metaclass=Meta):
def __init__(self):
self.__class__._Meta__value += 1
现在:
>>> s1 = Student()
>>> Student.idCounter
1
>>> s2 = Student()
>>> Student.idCounter
2
>>> Student.idCounter = 100
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-64-a525899df18d> in <module>()
----> 1 Student.idCounter = 100
AttributeError: can't set attribute
使用描述符和元类:
class Counter:
def __init__(self):
self.value = 0
def __get__(self, instance, cls):
return getattr(instance, '_{}__hidden_counter'.format(instance.__name__ ))
def __set__(self, instance, value):
raise NotImplementedError
class Meta(type):
idCounter = Counter()
class Student(metaclass=Meta):
__hidden_counter = 0
def __init__(self):
Student.__hidden_counter += 1
似乎实现了这个目标:
>>> s1 = Student()
>>> Student.idCounter
1
>>> s2 = Student()
>>> Student.idCounter
2
>>> Student.idCounter = 200
---------------------------------------------------------------------------
NotImplementedError Traceback (most recent call last)
<ipython-input-51-dc2483b583f6> in <module>()
----> 1 Student.idCounter = 200
<ipython-input-46-b21e03bf3cb3> in __set__(self, instance, value)
5 return getattr(instance, '_{}__hidden_counter'.format(instance.__name__ ))
6 def __set__(self, instance, value):
----> 7 raise NotImplementedError
8
9 class Meta(type):
NotImplementedError:
>>> Student.idCounter
2
这仍然可以故意破坏:
>>> Student._Student__hidden_counter = 100
>>> Student.idCounter
100
但不是偶然的。
答案 1 :(得分:3)
我的第一个版本看起来像这样(使用sphinx / rST标记):
class Student:
"""
Student class description...
.. py:attribute:: idCounter
A student ID counter. Do not modify this class attribute
manually!
"""
idCounter = 0
...
如果由于某种原因严厉的警告是不够的,我会接受我在评论中提出的建议,即在元类上使用property
。我会使用property
代替@MichaelMüller建议的自定义描述符,原因有两个:1)使用属性的实际工作较少,这是自动只读的:不需要重新发明轮子; 2)该属性将引发AttributeError
,我觉得这比NotImplementedError
更合适。
解决方案看起来像这样:
class StudentMeta(type):
def __new__(meta, name, bases, attrs):
attrs['idCounter'] = [0]
return type.__new__(meta, name, bases, attrs)
@property
def idCounter(cls):
return cls.__dict__['idCounter'][0]
class Student(metaclass=StudentMeta):
def __init__(self):
self.gpa = 0
self.record = {}
# Each time I create a new student, the idCounter increment
__class__.__dict__['idCounter'][0] += 1
self.name = 'Student {0}'.format(__class__.idCounter)
请注意,idCounter
中实际存在名为Student.__dict__
的属性。我发现这是隐藏属性的底层存储的最优雅方式,因为由于属性,您永远不会通过Student.idCounter
意外地修改它。但是,由于Python将类__dict__
优化为只读,因此我添加了一个间接级别,通过使实际计数器值为list
而不是int
来实现增量。
答案 2 :(得分:2)
tl; dr:不要传递活物;传递一个愚蠢的表示,并传递它仅供参考(不要接受它)如果你想要任何安全措施防止篡改。
您可以使用class-private属性和属性保护属性不被修改:
class Student(object):
__counter = 0
def __init__(self):
self.__class__.__counter += 1 # Only works within the class.
self.__ordinal = self.__counter
@property
def ordinal(self):
return self.__ordinal
它按预期工作,不允许轻易篡改自己。 对于那些不了解私有属性如何工作的人来说,篡改企图看起来令人费解和误导。
工作原理:
>>> s1 = Student()
>>> s1.ordinal
1
>>> s2 = Student()
>>> s2.ordinal
2
>>> s2.ordinal = 88
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>> s2.__ordinal = 88 # A cunning student.
>>> s2.__ordinal # Gasp!
88
>>> s2.ordinal # Nope. The private attribute is not touched.
2
>>> Student.__counter # Trying to override the true source.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'Student' has no attribute '__counter'
>>> Student.__counter = 123 # This appears to succeed!
>>> s3 = Student()
>>> s3.ordinal # Again, the private attribute is not touched.
3
>>> _
尽管如此,这不是防弹的。如果有足够的决心,可以访问私有类属性:
>>> Student._Student__counter = 999
>>> s1000 = Student()
>>> s1000.ordinal
1000
同样适用于任何隐藏属性的答案(给出一个数字);只要__dict__
可见,隐藏属性就不会完全隐藏。
可以围绕属性访问构建更复杂的防御,包括检查堆栈以确定调用者。但只要您传递一个具有任何访问主状态的Python对象,就会出现安全漏洞,并且确定的攻击者将能够更改该主状态。
对于真正的防篡改访问,当您将数据传递给不信任方时:
Student
个对象作为属性的哑存储,并且函数从这些属性计算某些东西(不改变它们)。 Student
个对象中传递对该数据库的任何引用。 在您的特定情况下,这可能是或不是过度杀伤;你没有描述它。