forked from scanny/python-pptx
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutil.py
More file actions
238 lines (183 loc) · 7.64 KB
/
util.py
File metadata and controls
238 lines (183 loc) · 7.64 KB
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# encoding: utf-8
"""Utility functions and classes."""
from __future__ import division
import functools
class Length(int):
"""
Base class for length classes Inches, Emu, Cm, Mm, Pt, and Px. Provides
properties for converting length values to convenient units.
"""
_EMUS_PER_INCH = 914400
_EMUS_PER_CENTIPOINT = 127
_EMUS_PER_CM = 360000
_EMUS_PER_MM = 36000
_EMUS_PER_PT = 12700
def __new__(cls, emu):
return int.__new__(cls, emu)
@property
def inches(self):
"""
Floating point length in inches
"""
return self / float(self._EMUS_PER_INCH)
@property
def centipoints(self):
"""
Integer length in hundredths of a point (1/7200 inch). Used
internally because PowerPoint stores font size in centipoints.
"""
return self // self._EMUS_PER_CENTIPOINT
@property
def cm(self):
"""
Floating point length in centimeters
"""
return self / float(self._EMUS_PER_CM)
@property
def emu(self):
"""
Integer length in English Metric Units
"""
return self
@property
def mm(self):
"""
Floating point length in millimeters
"""
return self / float(self._EMUS_PER_MM)
@property
def pt(self):
"""
Floating point length in points
"""
return self / float(self._EMUS_PER_PT)
class Inches(Length):
"""
Convenience constructor for length in inches
"""
def __new__(cls, inches):
emu = int(inches * Length._EMUS_PER_INCH)
return Length.__new__(cls, emu)
class Centipoints(Length):
"""
Convenience constructor for length in hundredths of a point
"""
def __new__(cls, centipoints):
emu = int(centipoints * Length._EMUS_PER_CENTIPOINT)
return Length.__new__(cls, emu)
class Cm(Length):
"""
Convenience constructor for length in centimeters
"""
def __new__(cls, cm):
emu = int(cm * Length._EMUS_PER_CM)
return Length.__new__(cls, emu)
class Emu(Length):
"""
Convenience constructor for length in english metric units
"""
def __new__(cls, emu):
return Length.__new__(cls, int(emu))
class Mm(Length):
"""
Convenience constructor for length in millimeters
"""
def __new__(cls, mm):
emu = int(mm * Length._EMUS_PER_MM)
return Length.__new__(cls, emu)
class Pt(Length):
"""
Convenience value class for specifying a length in points
"""
def __new__(cls, points):
emu = int(points * Length._EMUS_PER_PT)
return Length.__new__(cls, emu)
class lazyproperty(object):
"""Decorator like @property, but evaluated only on first access.
Like @property, this can only be used to decorate methods having only
a `self` parameter, and is accessed like an attribute on an instance,
i.e. trailing parentheses are not used. Unlike @property, the decorated
method is only evaluated on first access; the resulting value is cached
and that same value returned on second and later access without
re-evaluation of the method.
Like @property, this class produces a *data descriptor* object, which is
stored in the __dict__ of the *class* under the name of the decorated
method ('fget' nominally). The cached value is stored in the __dict__ of
the *instance* under that same name.
Because it is a data descriptor (as opposed to a *non-data descriptor*),
its `__get__()` method is executed on each access of the decorated
attribute; the __dict__ item of the same name is "shadowed" by the
descriptor.
While this may represent a performance improvement over a property, its
greater benefit may be its other characteristics. One common use is to
construct collaborator objects, removing that "real work" from the
constructor, while still only executing once. It also de-couples client
code from any sequencing considerations; if it's accessed from more than
one location, it's assured it will be ready whenever needed.
Loosely based on: https://stackoverflow.com/a/6849299/1902513.
A lazyproperty is read-only. There is no counterpart to the optional
"setter" (or deleter) behavior of an @property. This is critically
important to maintaining its immutability and idempotence guarantees.
Attempting to assign to a lazyproperty raises AttributeError
unconditionally.
The parameter names in the methods below correspond to this usage
example::
class Obj(object)
@lazyproperty
def fget(self):
return 'some result'
obj = Obj()
Not suitable for wrapping a function (as opposed to a method) because it
is not callable.
"""
def __init__(self, fget):
"""*fget* is the decorated method (a "getter" function).
A lazyproperty is read-only, so there is only an *fget* function (a
regular @property can also have an fset and fdel function). This name
was chosen for consistency with Python's `property` class which uses
this name for the corresponding parameter.
"""
# ---maintain a reference to the wrapped getter method
self._fget = fget
# ---adopt fget's __name__, __doc__, and other attributes
functools.update_wrapper(self, fget)
def __get__(self, obj, type=None):
"""Called on each access of 'fget' attribute on class or instance.
*self* is this instance of a lazyproperty descriptor "wrapping" the
property method it decorates (`fget`, nominally).
*obj* is the "host" object instance when the attribute is accessed
from an object instance, e.g. `obj = Obj(); obj.fget`. *obj* is None
when accessed on the class, e.g. `Obj.fget`.
*type* is the class hosting the decorated getter method (`fget`) on
both class and instance attribute access.
"""
# ---when accessed on class, e.g. Obj.fget, just return this
# ---descriptor instance (patched above to look like fget).
if obj is None:
return self
# ---when accessed on instance, start by checking instance __dict__
value = obj.__dict__.get(self.__name__)
if value is None:
# ---on first access, __dict__ item will absent. Evaluate fget()
# ---and store that value in the (otherwise unused) host-object
# ---__dict__ value of same name ('fget' nominally)
value = self._fget(obj)
obj.__dict__[self.__name__] = value
return value
def __set__(self, obj, value):
"""Raises unconditionally, to preserve read-only behavior.
This decorator is intended to implement immutable (and idempotent)
object attributes. For that reason, assignment to this property must
be explicitly prevented.
If this __set__ method was not present, this descriptor would become
a *non-data descriptor*. That would be nice because the cached value
would be accessed directly once set (__dict__ attrs have precedence
over non-data descriptors on instance attribute lookup). The problem
is, there would be nothing to stop assignment to the cached value,
which would overwrite the result of `fget()` and break both the
immutability and idempotence guarantees of this decorator.
The performance with this __set__() method in place was roughly 0.4
usec per access when measured on a 2.8GHz development machine; so
quite snappy and probably not a rich target for optimization efforts.
"""
raise AttributeError("can't set attribute") # pragma: no cover