-
Notifications
You must be signed in to change notification settings - Fork 7
/
consts.py
97 lines (82 loc) · 2.42 KB
/
consts.py
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
import dis
import opcode
class InvalidConstantError(Exception): pass
def const(**names):
"""Decorator to rewrite lookups to be constants
@const(x=1)
def func():
print(x) # prints 1
"""
def decorate(func):
# Constants are not allowed to be assigned to within the function.
# Thus they should never appear in the function's local names.
# TODO: Alter the message if the colliding name is an argument.
c = func.__code__
if set(names) & set(c.co_varnames):
raise InvalidConstantError("Cannot assign to local constant")
# Constants should not be declared nonlocal and then assigned to.
# (Unfortunately this doesn't actually check if they were assigned to.)
if set(names) & set(c.co_freevars):
raise InvalidConstantError("Cannot create local and nonlocal constant")
# Constants should therefore be global names.
if set(names) - set(c.co_names):
raise InvalidConstantError("Constant not referenced as global")
# Okay. So now we replace every LOAD_GLOBAL for one of these names
# with a LOAD_CONST.
newcode = []
newconsts = c.co_consts
for name, val in names.items():
try:
names[name] = newconsts.index(val)
except ValueError:
names[name] = len(newconsts)
newconsts += (val,)
LOAD_CONST = opcode.opmap["LOAD_CONST"]
for instr in dis.get_instructions(c):
if instr.opname == "LOAD_GLOBAL" and instr.argval in names:
newcode.append(LOAD_CONST)
newcode.append(names[instr.argval])
else:
newcode.append(instr.opcode)
newcode.append(instr.arg or 0)
codeobj = type(c)(c.co_argcount, c.co_kwonlyargcount, c.co_nlocals, c.co_stacksize,
c.co_flags, bytes(newcode), newconsts, c.co_names, c.co_varnames, c.co_filename,
c.co_name, c.co_firstlineno, c.co_lnotab, c.co_freevars, c.co_cellvars)
func = type(func)(codeobj, func.__globals__, func.__name__, func.__defaults__, func.__closure__)
return func
return decorate
x = "global"
@const(x=1)
def func():
print("This should be one:", x)
func()
try:
@const(x=1)
def func(x):
print("Shouldn't happen")
func(2)
except InvalidConstantError as e:
print(e)
try:
@const(x=1)
def func():
x = 2
print("Shouldn't happen")
func()
except InvalidConstantError as e:
print(e)
try:
def f():
x = 2
@const(x=1)
def func():
nonlocal x
print("Shouldn't happen")
return func
f()
except InvalidConstantError as e:
print(e)
@const(len=len, str=str, int=int)
def digit_count(n):
return len(str(int(n)))
dis.dis(digit_count)