原文地址: Closure漫谈


通过一个实际场景,练习Python Closure的使用方法。


The Assignment

The Programming Task

The first part of your submission has to demonstrate the use of closures on a specific programming task using Python 3.5.

The aim of the programming task is to model the basic functions of a project allocation system dealing with a number of supervisors and students. Each supervisor maintains a number of projects. Students can choose a project from the ones on offer. They can select a project and later change their mind and drop it or select another one, but each student can have at most one project allocated at a time. Your overall task will be to design, write and test procedures that model 'projects', 'students' and 'supervisors' as specified below.

The program must be written in 'message-passing', or 'object-oriented' style as described in the POPL lectures, and Structure and Interpretation of Computer Programs (SICP). Your code should not make use of Python classes, and the keyword class must not appear in your code or its comments. Any line of code containing the keyword class will be removed from your solution before it is tested. It is acceptable though to make references to it in the essay, if needed.

The programming part of the assignment consists of a series of tasks, which you must complete in order. The transcript of a run of a valid solution for this part of the assignment can be found on the VLE under Assessment. The submitted code will be tested and marked in a fully automated way against a number of similar tests.


Appendix A gives a skeleton procedure for constructing project objects, and three 'interface' procedures, project_supervisor, set_supervisor and projectID. Projects are constructed using the a_project(projectID) procedure, where projectID is a Python string (e.g., as constructed by "DLK1") representing the project's unique ID.1 You are to complete the implementation of this part of the programming task by filling in the missing code in the places indicated by ??? in the procedure a_project(...), and completing the definitions of the three interface procedures.

Any project object created with a_project(...) must respond to 'actions' as defined by the following interface procedures:

  • project_supervisor(project) returns the ID of the project's supervisor (a Python string,e.g.,"DLK").
  • set_supervisor(project,supervisor) marks project as being owned by supervisor. When first created (using a_project), the project has no supervisor, and project_supervisor(project) should return the value False.
  • projectID(project) returns the project's ID.


A student is an object that models students for the purpose of selecting a project. Design, implement and test a procedure a_student(studentID) that constructs a student object, where studentID is a Python string (e.g., as constructed by "abc234") representing the student's unique ID.

An object constructed using a_student must maintain a record of the project selected (as constructed by a_project). Initially, a_student returns a student object with no project selected.

Objects constructed with a_student must respond to actions as defined by the following interface routines:

  • allocation(student) returns the student's current choice of project, or the value False if no project is selected.
  • studentID(student) returns the student's ID.
  • choose_project(student,project) student records project (as constructed by a_project) as its current selection of project. If successful, returns True. This procedure should only succeed if the student's current selection is empty, i.e., (allocation student) returns False.
  • drop_project(student) the student's project selection record is reset to False and the value True returned.


Design, implement and test a procedure a_supervisor(supervisorID) that models supervisors, where supevisorID is a Python string representing the supervisor's name. An object created by a_supervisor has the following properties:

  1. Maintains a record of all projects (as constructed by a_project) that it owns. Each such project's supervisor would therefore be represented in the project object by the corresponding supervisorID.
  2. Maintains information as to whether a project is allocated to a student (as constructed by a_student) or not. A project can only be allocated to at most one student.
  3. For allocated projects, maintains the student (as constructed by a_student) to whom the project is allocated.
  4. Maintains a record of the supervisor's quota, a non-negative integer that represents the number of students that the supervisor can still take on supervising. A supervisor object is created with an initial quota of 6; that value is decremented each time a supervisor's project is allocated to a student, and incremented if a student drops a project. A quota of 0 means no further projects of this supervisor can be allocated.
  5. Responds to actions as defined by the following interface procedures, in which supervisor is an object created by a_supervisor, student is an object created by a_student, and project is an object created by a_project:

    • supervisorID(supervisor) returns the ID of that supervisor.
    • quota(supervisor) returns the quota of that supervisor.
    • is_underloaded(supervisor) returns False if the supervisor's quota is 0, and True otherwise.
    • increment_quota(supervisor) increments the supervisor's quota by one and returns True.
    • decrement_quota(supervisor) returns False if the supervisor's quota is 0, otherwise decrements the quota by one and returns True.
    • add_project(supervisor,project) returns False if the project (as constructed by a_project) already has a supervisor, otherwise sets the project's supervisor to supervisor and adds a_project to its record of projects owned.
    • allocate_project(supervisor,project,student) If project is one of the supervisor's, the project is still not allocated, and the supervisor is underloaded, make the student choose the project, decrement the supervisor's quota, and update the supervisor's record to show that the status of project has changed to "allocated", and that it was allocated to student.
    • deallocate_project(supervisor,project,student) If project is one of the supervisor's, and it is currently allocated, update the supervisor's and student's records to reflect the fact that project is no longer allocated to student.
  • list_allocated_projects(supervisor) returns a list of pairs of (projectID,studentID) containing the IDs of all currently allocated projects of supervisor and the students to whom the projects are allocated, e.g.: [('dlk01', 'abc101'), ('dlk02', 'xyz737')].
  • list_available_projects(supervisor) returns a list containing the IDs of all currently available (unallocated) projects of supervisor.

The Essay

This part of your submission needs to define what closures are, discuss some of the pros and cons of their use, compare the provisions for the use of closures made in Racket Scheme and Python 3.5, and, lastly, discuss the limitations of Python 2.7 for the implementation of the same programming task through closures as shown in Appendix A, and how these could be overcome.

The following aspects will be taken into account when marking the essay: clarity of explanation, appropriate program examples, strength and structure of argument, and use of references.

Appendix A

The text of the following code can be copied from the VLE under Assessment.

def a_project(projectID):
  supervisor = ???  # To do
  def the_project(request):
    nonlocal supervisor
    if request == "supervisor": return lambda: supervisor
    elif request == "set_supervisor": return set_sv
    elif request == "projectID": return ??? # To do
    else: return "the_project: unknown request "+request
  def set_sv(sv):
    nonlocal supervisor
    supervisor = sv
    return True
  return the_project

def project_supervisor(project):
  return project("supervisor")()

def set_supervisor(project, supervisor):
  ??? #Todo

def projectID(project):
  ??? #Todo