在Python中重新创建“私有”类变量

4
我已经阅读了许多有关Python为什么没有真正私有变量的SO线程,对于大多数应用程序,我都能理解。
这是我的问题:我正在创建一个班级项目。在这个班级项目中,学生设计一个代理来参加选择题测试。我们希望能够立即评分以便代理可以从之前的错误答案中学习。因此,我们需要将每个问题的正确答案存储在程序中。学生在本地机器上运行这些项目,因此他们可以看到所有测试者的代码。他们无法修改它--当他们提交时,我们会覆盖他们对测试者代码所做的任何更改。他们只需提交代表其代理的新文件。
在Java中,这很简单。在Problem类中,有一个private的correctAnswer变量。correctAnswer始终存储问题的正确答案。代理只能通过checkAnswer方法读取correctAnswer,并且为了调用checkAnswer,代理必须实际给出不能在之后更改的问题答案。
我需要在Python中重新创建这种行为,到目前为止我束手无策。似乎无论在程序的哪个位置存储correctAnswer,代理都可以访问它--我熟悉下划线约定,但在这个问题中,我需要代理无法访问正确答案。我唯一能想到的是在测试学生代码时将correctAnswer命名为不同的名称,以便他们的代理无法预测它将被称为什么,但这是一个不优雅的解决方案。
有什么建议吗?只要我们可以检测到代理何时读取它(以便我们可以在那之后将'代理答案'变量设置为只读...尽管然后我也需要一种方法来做到这一点,oof),代理就可以阅读correctAnswer。

4
这里的目标是通过防止学生查看correctAnswer变量来防止作弊吗?那么只看他们提交的代码不就可以确定吗? - dano
1
在Java中,如果你依赖于private来防止访问,那么你将容易受到反射字段访问的攻击,例如Problem.class.getDeclaredField("correctAnswer").get(problem) - user2357112
@dano 有数百名学生和一名评分员。检查每个学生的代码并不是一个真正的选择。理论上,我们可以自动搜索学生代码中的变量名称,但这并不完美。 - David
5个回答

5

改变规则。不要将正确答案存储在程序中,而是将其存储在安全的服务器上:

def checkAnswer(studentAnswer):
    response = requests.get('http://teacher-server.example.edu/checkAnswer?%s'%studentAnswer)
    return response.text == 'Yes'

我们的许多学生在海外,网络不稳定。这是不能接受的。 - David

3

您可以在重写测试程序时更改correctAnswer属性的名称。这将立即破坏所有依赖于正确答案名称的解决方案。

此外,您可以运行两个版本的测试程序并比较输出结果。如果有差异,则他们的代码以某种方式依赖于属性名称。


这基本上就是我们要使用的。虽然不是理想的,但已经足够好了。谢谢! - David

1

查看覆盖类的

__getattr__

方法。当引用类属性时,就会调用这个方法。您可以控制类在像下面这样的语句中返回什么和不返回什么:

x = myClass.myAttribute

被用于编程。


我认为这是最好的回答,符合提问者对隐私的需求。但在我看来,私有方法不是隐藏信息的好方法,除非你只是想隐含地隐藏信息。 - Quentin Engles
学生总是可以绕过__getattr__并查找__getattr__将要查找数据的任何地方,因此这并没有真正解决问题。 - user2357112

1

无论你做什么,都不能保证学生的Python代码无法访问或修改由同一解释器中运行的你的Python代码创建的任何对象。像覆盖__getattr__这样的东西可以通过使用object.__getattribute__来绕过。你的学生可以“猴子补丁”测试代码以实现他们想要的效果,而不必修改源代码。与Java不同,Python并没有假装成为一种安全语言。你要么需要检查代码,要么将测试代码移动到另一个进程中。

multiprocessing模块可能会帮助你做到这一点。例如:

import multiprocessing as mp
import multiprocessing.managers

class Problem(object):
    def __init__(self, correctAnswer):
        self.answer = None
        self.correctAnswer = correctAnswer

    def checkAnswer(self, answer):
        if self.answer == None:
            self.answer = answer
        return self.answer == self.correctAnswer

    def __getstate__(self):
        raise ValueError("don't pickle me");

class Problem_proxy(mp.managers.BaseProxy):
    def checkAnswer(self, answer):
        return self._callmethod("checkAnswer", (answer,))

class mymanager(mp.managers.BaseManager):
    pass

mymanager.register("Problem", Problem, Problem_proxy)

def other_process(problems):
    # uncommenting either of these should cause an exception
    # print problems[0].correctAnswer
    # print problems[0]._getvalue().correctAnswer

    import agent       # module provide by the student
    agent.answer_problems(problems)

def main():
    manager = mymanager()
    manager.start()

    meaningoflife = manager.Problem(42)
    problems = [meaningoflife]

    proc = mp.Process(target = other_process,
              args = ([meaningoflife],))
    proc.start()
    proc.join()


if __name__ == "__main__":
    main()

即使这样,仍然存在许多漏洞。例如,学生的代码可以读取您的测试器代码模块并检查答案。要真正安全,您需要建立某种客户端/服务器模型,其中测试器代码在不同用户帐户上运行。

0
在测试器上,您可以将correctAnswer定义为属性,并在除已知代码以外的任何调用它时引发异常。
这可以通过内省实现,但并不美观:
import inspect

class Problem(object):
    @property
    def correctAnswer(self):
        frm = inspect.currentframe()
        caller = inspect.getouterframes(frm)[0]

        _,_,_,name,_,_ = caller

        if name != YOUR_TESTING_FUNCTION:
            raise BadStudentError()

        return self._correctAnswer

请注意,这意味着您需要定义_correctAnswer,但它只能在您的代码版本中存在(您可以在他们的版本中使用.correctAnswer;使用@property的美妙之处在于这种替换对调用者是透明的)。

当然,虽然上述解决方案可以让您知道学生是否尝试访问正确答案,但它有点不太正规。

然而,我个人认为,在服务器上检查学生的答案会更加优雅。 通过这样做,您可以控制整个过程,并轻松确保代理提交后答案不会被更改。

如果编写简单的http查询超出了学生的能力范围,您可以包含一个帮助方法(checkAnswer或类似方法),为他们执行此过程。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接