Welcome, guest | Sign In | My Account | Store | Cart

Closured-based alternative to normal classes. Allows a faster, cleaner coding style at the expense of some functionality.

Python, 156 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# Simple tool for making class-like objects using nested scopes and closures in Python 3
#
# Advantages:
#  * cleaner coding style
#  * faster internal references
#  * faster external calls - no bound methods or rewriting arg tuples
#  * inlined initialization code
#  * painlessly delegate work to other objects
#
# Disadvantages:
#  * need setter methods to update attributes
#  * inheritance is not supported
#  * slower calls to special methods
#  * there is no "self"
#  * limited support for help()

import sys

class Instance:
    'Instances where methods are implemented as closures'

# The Python interpreter looks for special methods in the class dictionary
# Add support for them by dispatching them back to the instance methods
smethods =    '''__bool__ __int__ __float__ __complex__ __index__
                 __len__ __getitem__ __setitem__ __delitem__ __contains__
                 __iter__ __next__ __reversed__
                 __call__ __enter__ __exit__
                 __str__ __repr__  __bytes__ __format__
                 __eq__ __ne__ __lt__ __le__ __gt__ __ge__ __hash__
                 __add__ __mul__ __sub__ __truediv__ __floordiv__ __mod__
                 __and__ __or__ __xor__ __invert__ __lshift__ __rshift__
                 __pos__ __neg__ __abs__ __pow__ __divmod__
                 __round__ __ceil__ __floor__ __trunc__
                 __radd__ __rmul__ __rsub__ __rtruediv__ __rfloordiv__ __rmod__
                 __rand__ __ror__ __rxor__ __rlshift__ __rrshift__
                 __rpow__ __rdivmod__
                 __get__ __set__ __delete__
                 __copy__ __deepcopy__ __reduce__ __reduce_ex__
                 __getstate__ __setstate__ __getnewargs__ __getinitargs__
                 __subclasshook__ __subclasscheck__ __instancecheck__
                 __dir__ __sizeof__'''.split()
for sm in smethods:
    setattr(Instance, sm, lambda self, *args, sm=sm: self.__dict__[sm](*args))

def classify(local_dict=None):
    'Move local definitions to an instance dictionary'
    o = Instance()
    if local_dict is None:
        local_dict = sys._getframe(1).f_locals
    vars(o).update(local_dict)
    return o


############################################################################
###  Simple example  #######################################################

def Animal(name):
    'Animal-like class'
    def speak():
        print('I am', name)
    def set_name(newname):
        nonlocal name
        name = newname
    def __getitem__(key):
        return '%s is %sing' % (name, key.title())
    return classify()

d = Animal('Fido')
print(d.name)
d.speak()
d.set_name('Max')
d.speak()
print(d['fetch'])


############################################################################
###  Practical example  ####################################################

def PriorityQueue(initial_tasklist):
    'Retrieve tasks by priority.  Tasks can be removed or reprioritized.'
    from heapq import heappush, heappop

    pq = []                         # list of entries arranged in a heap
    entry_finder = {}               # mapping of tasks to entries
    REMOVED = object()              # placeholder for a removed task
    count = 0                       # unique sequence count

    def add(task, priority=0):
        'Add a new task or update the priority of an existing task'
        nonlocal count
        if task in entry_finder:
            remove(task)
        entry = [priority, count, task]
        entry_finder[task] = entry
        heappush(pq, entry)
        count += 1

    def remove(task):
        'Mark an existing task as REMOVED.  Raise KeyError if not found.'
        entry = entry_pop(task)
        entry[-1] = REMOVED

    def pop():
        'Remove and return the lowest priority task. Raise KeyError if empty.'
        while pq:
            priority, count, task = heappop(pq)
            if task is not REMOVED:
                del entry_finder[task]
                return task
        raise KeyError('pop from an empty priority queue')

    def __iter__():
        'Show task in order of priority'
        pq.sort()
        for priority, count, task in pq:
            if task is not REMOVED:
                yield task

    def __repr__():
        return 'PriorityQueue()'

    def __str__():
        return 'PriorityQueue with %d pending tasks' % __len__()

    # Delegate some of the work to the entry_finder dictionary
    __contains__ = entry_finder.__contains__
    __len__ = entry_finder.__len__
    entry_pop = entry_finder.pop

    # No separate method needed for initialization
    for task, priority in initial_tasklist:
        add(task, priority)

    return classify()


if __name__ == '__main__':

    # Note: Client code is written normally.  It makes no difference
    # whether a PriorityQueue was implemented as a regular class or
    # implemented using closures.

    todo = PriorityQueue([('fish', 5), ('play', 3)])
    todo.add('code', 1)
    todo.add('sleep', 6)
    print(list(todo))
    print('Pop the topmost task:  %r' % todo.pop())
    print("Deprioritize 'play'")
    todo.add('play', 10)
    todo.remove('sleep')
    print(len(todo))
    print(list(todo))
    print(todo)
    print('fish' in todo)
    help(PriorityQueue)
    help(todo.add)

The nested-scope approach provides some advantages for clean coding, so you don't have to write "self.attribute" or "self.method()" for internal references.

The implementation also provides some speed advantages:

  • internal references use dereferences which are much faster than traditional attribute and method access.
  • external calls, such as todo.add('code', 1), are executed as plain function calls, so there is no need to create a bound method and to rewrite the arguments to prepend self.

1 comment

Daniel Miller 9 years, 7 months ago  # | flag

Would be cool if classify() could be a decorator:

@classify
def TheClass():
    ...

With inheritance:

@classify(extends=OtherClass)
def SubClass():
    ...