Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/misc/classcall_metaclass.pyx
4057 views
1
r"""
2
Special Methods for Classes
3
4
AUTHORS:
5
6
- Nicolas M. Thiery (2009-2011) implementation of
7
``__classcall__``, ``__classget__``, ``__classcontains__``;
8
- Florent Hivert (2010-2012): implementation of ``__classcall_private__``,
9
documentation, Cythonization and optimization.
10
"""
11
#*****************************************************************************
12
# Copyright (C) 2009 Nicolas M. Thiery <nthiery at users.sf.net>
13
# Copyright (C) 2010-2012 Florent Hivert <Florent.Hivert at lri.fr>
14
#
15
# Distributed under the terms of the GNU General Public License (GPL)
16
# http://www.gnu.org/licenses/
17
#*****************************************************************************
18
19
include '../ext/python.pxi'
20
21
cdef extern from "Python.h":
22
ctypedef PyObject *(*callfunc)(type, object, object) except NULL
23
ctypedef struct PyTypeObject_call "PyTypeObject":
24
callfunc tp_call # needed to call type.__call__ at very high speed.
25
cdef PyTypeObject_call PyType_Type # Python's type
26
27
__all__ = ['ClasscallMetaclass', 'typecall', 'timeCall']
28
29
cdef class ClasscallMetaclass(NestedClassMetaclass):
30
"""
31
A metaclass providing support for special methods for classes.
32
33
From the Section :python:`Special method names
34
<reference/datamodel.html#special-method-names>` of the Python Reference
35
Manual:
36
37
\`a class ``cls`` can implement certain operations on its instances
38
that are invoked by special syntax (such as arithmetic operations or
39
subscripting and slicing) by defining methods with special
40
names\'.
41
42
The purpose of this metaclass is to allow for the class ``cls`` to
43
implement analogues of those special methods for the operations on the
44
class itself.
45
46
Currently, the following special methods are supported:
47
48
- ``.__classcall__`` (and ``.__classcall_private__``) for
49
customizing ``cls(...)`` (analogue of ``.__call__``).
50
51
- ``.__classcontains__`` for customizing membership testing
52
``x in cls`` (analogue of ``.__contains__``).
53
54
- ``.__classget__`` for customizing the binding behavior in
55
``foo.cls`` (analogue of ``.__get__``).
56
57
See the documentation of :meth:`.__call__` and of :meth:`.__get__`
58
and :meth:`.__contains__` for the description of the respective
59
protocols.
60
61
.. warning:: for technical reasons, ``__classcall__``,
62
``__classcall_private__``, ``__classcontains__``, and
63
``__classget__`` must be defined as :func:`staticmethod`'s, even
64
though they receive the class itself as their first argument.
65
66
``ClasscallMetaclass`` is an extension of the base :class:`type`.
67
68
TODO: find a good name for this metaclass.
69
70
TESTS::
71
72
sage: PerfectMatchings(2).list()
73
[PerfectMatching [(2, 1)]]
74
75
.. note::
76
77
If a class is put in this metaclass it automatically becomes a
78
new-style class::
79
80
sage: from sage.misc.classcall_metaclass import ClasscallMetaclass
81
sage: class Foo:
82
... __metaclass__ = ClasscallMetaclass
83
sage: x = Foo(); x
84
<__main__.Foo object at 0x...>
85
sage: issubclass(Foo, object)
86
True
87
sage: isinstance(Foo, type)
88
True
89
"""
90
_included_private_doc_ = ['__call__', '__contains__', '__get__']
91
92
def __cinit__(self, *args, **opts):
93
r"""
94
TESTS::
95
96
sage: from sage.misc.classcall_metaclass import ClasscallMetaclass
97
sage: class FOO(object):
98
... __metaclass__ = ClasscallMetaclass
99
sage: isinstance(FOO, ClasscallMetaclass) # indirect doctest
100
True
101
"""
102
if '__classcall_private__' in self.__dict__:
103
self.classcall = self.__classcall_private__
104
elif hasattr(self, "__classcall__"):
105
self.classcall = self.__classcall__
106
else:
107
self.classcall = None
108
109
self.classcontains = getattr(self, "__classcontains__", None)
110
self.classget = getattr(self, "__classget__", None)
111
112
def __call__(cls, *args, **opts):
113
r"""
114
This method implements ``cls(<some arguments>)``.
115
116
Let ``cls`` be a class in :class:`ClasscallMetaclass`, and
117
consider a call of the form::
118
119
cls(<some arguments>)
120
121
- If ``cls`` defines a method ``__classcall_private__``, then
122
this results in a call to::
123
124
cls.__classcall_private__(cls, <some arguments>)
125
126
- Otherwise, if ``cls`` has a method ``__classcall__``, then instead
127
the following is called::
128
129
cls.__classcall__(cls, <some arguments>)
130
131
- If neither of these two methods are implemented, then the standard
132
``type.__call__(cls, <some arguments>)`` is called, which in turn
133
uses :meth:`~object.__new__` and :meth:`~object.__init__` as usual
134
(see Section :python:`Basic Customization
135
<reference/datamodel.html#basic-customization>` in the Python
136
Reference Manual).
137
138
.. warning:: for technical reasons, ``__classcall__`` must be
139
defined as a :func:`staticmethod`, even though it receives
140
the class itself as its first argument.
141
142
EXAMPLES::
143
144
sage: from sage.misc.classcall_metaclass import ClasscallMetaclass
145
sage: class Foo(object):
146
... __metaclass__ = ClasscallMetaclass
147
... @staticmethod
148
... def __classcall__(cls):
149
... print "calling classcall"
150
... return type.__call__(cls)
151
... def __new__(cls):
152
... print "calling new"
153
... return super(Foo, cls).__new__(cls)
154
... def __init__(self):
155
... print "calling init"
156
sage: Foo()
157
calling classcall
158
calling new
159
calling init
160
<__main__.Foo object at ...>
161
162
This behavior is inherited::
163
164
sage: class Bar(Foo): pass
165
sage: Bar()
166
calling classcall
167
calling new
168
calling init
169
<__main__.Bar object at ...>
170
171
We now show the usage of ``__classcall_private__``::
172
173
sage: class FooNoInherits(object):
174
... __metaclass__ = ClasscallMetaclass
175
... @staticmethod
176
... def __classcall_private__(cls):
177
... print "calling private classcall"
178
... return type.__call__(cls)
179
...
180
sage: FooNoInherits()
181
calling private classcall
182
<__main__.FooNoInherits object at ...>
183
184
Here the behavior is not inherited::
185
186
sage: class BarNoInherits(FooNoInherits): pass
187
sage: BarNoInherits()
188
<__main__.BarNoInherits object at ...>
189
190
We now show the usage of both::
191
192
sage: class Foo2(object):
193
... __metaclass__ = ClasscallMetaclass
194
... @staticmethod
195
... def __classcall_private__(cls):
196
... print "calling private classcall"
197
... return type.__call__(cls)
198
... @staticmethod
199
... def __classcall__(cls):
200
... print "calling classcall with %s"%cls
201
... return type.__call__(cls)
202
...
203
sage: Foo2()
204
calling private classcall
205
<__main__.Foo2 object at ...>
206
207
sage: class Bar2(Foo2): pass
208
sage: Bar2()
209
calling classcall with <class '__main__.Bar2'>
210
<__main__.Bar2 object at ...>
211
212
213
.. rubric:: Discussion
214
215
Typical applications include the implementation of factories or of
216
unique representation (see :class:`UniqueRepresentation`). Such
217
features are traditionaly implemented by either using a wrapper
218
function, or fiddling with :meth:`~object.__new__`.
219
220
The benefit, compared with fiddling directly with
221
:meth:`~object.__new__` is a clear separation of the three distinct
222
roles:
223
224
- ``cls.__classcall__``: what ``cls(<...>)`` does
225
- ``cls.__new__``: memory allocation for a *new* instance
226
- ``cls.__init__``: initialization of a newly created instance
227
228
The benefit, compared with using a wrapper function, is that the
229
user interface has a single handle for the class::
230
231
sage: x = Partition([3,2,2])
232
sage: isinstance(x, Partition) # todo: not implemented
233
234
instead of::
235
236
sage: isinstance(x, sage.combinat.partition.Partition_class)
237
True
238
239
Another difference is that ``__classcall__`` is inherited by
240
subclasses, which may be desirable, or not. If not, one should
241
instead define the method ``__classcall_private__`` which will
242
not be called for subclasses. Specifically, if a class ``cls``
243
defines both methods ``__classcall__`` and
244
``__classcall_private__`` then, for any subclass ``sub`` of ``cls``:
245
246
- ``cls(<args>)`` will call ``cls.__classcall_private__(cls, <args>)``
247
- ``sub(<args>)`` will call ``cls.__classcall__(sub, <args>)``
248
249
250
TESTS:
251
252
We check that the idiom ``method_name in cls.__dict__`` works
253
for extension types::
254
255
sage: "_sage_" in SageObject.__dict__, "_sage_" in Parent.__dict__
256
(True, False)
257
258
We check for memory leaks::
259
260
sage: class NOCALL(object):
261
... __metaclass__ = ClasscallMetaclass
262
... pass
263
sage: sys.getrefcount(NOCALL())
264
1
265
266
We check that exception are correctly handled::
267
268
sage: class Exc(object):
269
... __metaclass__ = ClasscallMetaclass
270
... @staticmethod
271
... def __classcall__(cls):
272
... raise ValueError, "Calling classcall"
273
sage: Exc()
274
Traceback (most recent call last):
275
...
276
ValueError: Calling classcall
277
"""
278
if cls.classcall is not None:
279
return cls.classcall(cls, *args, **opts)
280
else:
281
###########################################################
282
# This is type.__call__(cls, *args, **opts) twice faster
283
# Using the following test code:
284
#
285
# sage: class NOCALL(object):
286
# ... __metaclass__ = ClasscallMetaclass
287
# ... pass
288
#
289
# with type.__call__ :
290
# sage: %timeit [NOCALL() for i in range(10000)]
291
# 125 loops, best of 3: 3.59 ms per loop
292
# with this ugly C call:
293
# sage: %timeit [NOCALL() for i in range(10000)]
294
# 125 loops, best of 3: 1.76 ms per loop
295
#
296
# Note: compared to a standard void Python class the slow down is
297
# only 5%:
298
# sage: %timeit [Rien() for i in range(10000)]
299
# 125 loops, best of 3: 1.7 ms per loop
300
res = <object> PyType_Type.tp_call(cls, args, opts)
301
Py_XDECREF(res) # During the cast to <object> Cython did INCREF(res)
302
return res
303
304
def __get__(cls, instance, owner):
305
r"""
306
This method implements instance binding behavior for nested classes.
307
308
Suppose that a class ``Outer`` contains a nested class ``cls`` which
309
is an instance of this metaclass. For any object ``obj`` of ``cls``,
310
this method implements a instance binding behavior for ``obj.cls`` by
311
delegating it to ``cls.__classget__(Outer, obj, owner)`` if available.
312
Otherwise, ``obj.cls`` results in ``cls``, as usual.
313
314
Similarily, a class binding as in ``Outer.cls`` is delegated
315
to ``cls.__classget__(Outer, None, owner)`` if available and
316
to ``cls`` if not.
317
318
.. warning:: for technical reasons, ``__classget__`` must be
319
defined as a :func:`staticmethod`, even though it receives
320
the class itself as its first argument.
321
322
For technical details, and in particular the description of the
323
``owner`` argument, see the Section :python:`Implementing Descriptor
324
<reference/datamodel.html#implementing-descriptors>` in the Python
325
reference manual.
326
327
EXAMPLES:
328
329
We show how to implement a nested class ``Outer.Inner`` with a
330
binding behavior, as if it was a method of ``Outer``: namely,
331
for ``obj`` an instance of ``Outer``, calling
332
``obj.Inner(...)`` is equivalent to ``Outer.Inner(obj, ...)``::
333
334
sage: import functools
335
sage: from sage.misc.nested_class import NestedClassMetaclass
336
sage: from sage.misc.classcall_metaclass import ClasscallMetaclass
337
sage: class Outer:
338
... __metaclass__ = NestedClassMetaclass # workaround for python pickling bug
339
...
340
... class Inner(object):
341
... __metaclass__ = ClasscallMetaclass
342
... @staticmethod
343
... def __classget__(cls, instance, owner):
344
... print "calling __classget__(%s, %s, %s)"%(
345
... cls, instance, owner)
346
... if instance is None:
347
... return cls
348
... return functools.partial(cls, instance)
349
... def __init__(self, instance):
350
... self.instance = instance
351
sage: obj = Outer()
352
sage: bar = obj.Inner()
353
calling __classget__(<class '__main__.Outer.Inner'>, <__main__.Outer object at 0x...>, <class '__main__.Outer'>)
354
sage: bar.instance == obj
355
True
356
357
Calling ``Outer.Inner`` returns the (unbinded) class as usual::
358
359
sage: Inner = Outer.Inner
360
calling __classget__(<class '__main__.Outer.Inner'>, None, <class '__main__.Outer'>)
361
sage: Inner
362
<class '__main__.Outer.Inner'>
363
sage: type(bar) is Inner
364
True
365
366
.. warning:: Inner has to be a new style class (i.e. a subclass of object).
367
368
.. warning::
369
370
calling ``obj.Inner`` does no longer return a class::
371
372
sage: bind = obj.Inner
373
calling __classget__(<class '__main__.Outer.Inner'>, <__main__.Outer object at 0x...>, <class '__main__.Outer'>)
374
sage: bind
375
<functools.partial object at 0x...>
376
"""
377
if cls.classget:
378
return cls.classget(cls, instance, owner)
379
else:
380
return cls
381
382
def __contains__(cls, x):
383
r"""
384
This method implements membership testing for a class
385
386
Let ``cls`` be a class in :class:`ClasscallMetaclass`, and consider
387
a call of the form::
388
389
x in cls
390
391
If ``cls`` defines a method ``__classcontains__``, then this
392
results in a call to::
393
394
cls.__classcontains__(cls, x)
395
396
.. warning:: for technical reasons, ``__classcontains__`` must
397
be defined as a :func:`staticmethod`, even though it
398
receives the class itself as its first argument.
399
400
EXAMPLES:
401
402
We construct a class which implements membership testing, and
403
which contains ``1`` and no other x::
404
405
sage: from sage.misc.classcall_metaclass import ClasscallMetaclass
406
sage: class Foo(object):
407
... __metaclass__ = ClasscallMetaclass
408
... @staticmethod
409
... def __classcontains__(cls, x):
410
... return x == 1
411
sage: 1 in Foo
412
True
413
sage: 2 in Foo
414
False
415
416
We now check that for a class without ``__classcontains__``
417
method, we emulate the usual error message::
418
419
sage: from sage.misc.classcall_metaclass import ClasscallMetaclass
420
sage: class Bar(object):
421
... __metaclass__ = ClasscallMetaclass
422
sage: 1 in Bar
423
Traceback (most recent call last):
424
...
425
TypeError: argument of type 'type' is not iterable
426
"""
427
if cls.classcontains:
428
return cls.classcontains(cls, x)
429
else:
430
return x in object
431
432
433
def typecall(type cls, *args, **opts):
434
r"""
435
Object construction
436
437
This is a faster equivalent to ``type.__call__(cls, <some arguments>)``.
438
439
INPUT:
440
441
- ``cls`` -- the class used for constructing the instance. It must be
442
a builtin type or a new style class (inheriting from :class:`object`).
443
444
EXAMPLES::
445
446
sage: from sage.misc.classcall_metaclass import typecall
447
sage: class Foo(object): pass
448
sage: typecall(Foo)
449
<__main__.Foo object at 0x...>
450
sage: typecall(list)
451
[]
452
sage: typecall(Integer, 2)
453
2
454
455
.. warning::
456
457
:func:`typecall` doesn't work for old style class (not inheriting from
458
:class:`object`)::
459
460
sage: class Bar: pass
461
sage: typecall(Bar)
462
Traceback (most recent call last):
463
...
464
TypeError: Argument 'cls' has incorrect type (expected type, got classobj)
465
"""
466
# See remarks in ClasscallMetaclass.__call__(cls, *args, **opts) for speed.
467
res = <object> PyType_Type.tp_call(cls, args, opts)
468
Py_XDECREF(res) # During the cast to <object> Cython did INCREF(res)
469
return res
470
471
# Class for timing::
472
473
class CRef(object):
474
def __init__(self, i):
475
"""
476
TESTS::
477
478
sage: from sage.misc.classcall_metaclass import CRef
479
sage: P = CRef(2); P.i
480
3
481
"""
482
self.i = i+1
483
484
class C2(object):
485
__metaclass__ = ClasscallMetaclass
486
def __init__(self, i):
487
"""
488
TESTS::
489
490
sage: from sage.misc.classcall_metaclass import C2
491
sage: P = C2(2); P.i
492
3
493
"""
494
self.i = i+1
495
496
class C3(object, metaclass = ClasscallMetaclass):
497
def __init__(self, i):
498
"""
499
TESTS::
500
501
sage: from sage.misc.classcall_metaclass import C3
502
sage: P = C3(2); P.i
503
3
504
"""
505
self.i = i+1
506
507
class C2C(object):
508
__metaclass__ = ClasscallMetaclass
509
@staticmethod
510
def __classcall__(cls, i):
511
"""
512
TESTS::
513
514
sage: from sage.misc.classcall_metaclass import C2C
515
sage: C2C(2)
516
3
517
"""
518
return i+1
519
520
def timeCall(T, int n, *args):
521
r"""
522
We illustrate some timing when using the classcall mechanism.
523
524
EXAMPLES::
525
526
sage: from sage.misc.classcall_metaclass import (
527
... ClasscallMetaclass, CRef, C2, C3, C2C, timeCall)
528
sage: timeCall(object, 1000)
529
530
For reference let construct basic objects and a basic Python class::
531
532
sage: %timeit timeCall(object, 1000) # not tested
533
625 loops, best of 3: 41.4 µs per loop
534
535
sage: i1 = int(1); i3 = int(3) # don't use Sage's Integer
536
sage: class PRef(object):
537
... def __init__(self, i):
538
... self.i = i+i1
539
540
For a Python class, compared to the reference class there is a 10%
541
overhead in using :class:`ClasscallMetaclass` if there is no classcall
542
defined::
543
544
sage: class P(object):
545
... __metaclass__ = ClasscallMetaclass
546
... def __init__(self, i):
547
... self.i = i+i1
548
549
sage: %timeit timeCall(PRef, 1000, i3) # not tested
550
625 loops, best of 3: 420 µs per loop
551
sage: %timeit timeCall(P, 1000, i3) # not tested
552
625 loops, best of 3: 458 µs per loop
553
554
For a Cython class (not cdef since they doesn't allows metaclasses), the
555
overhead is a little larger::
556
557
sage: %timeit timeCall(CRef, 1000, i3) # not tested
558
625 loops, best of 3: 266 µs per loop
559
sage: %timeit timeCall(C2, 1000, i3) # not tested
560
625 loops, best of 3: 298 µs per loop
561
562
Let's now compare when there is a classcall defined::
563
564
sage: class PC(object):
565
... __metaclass__ = ClasscallMetaclass
566
... @staticmethod
567
... def __classcall__(cls, i):
568
... return i+i1
569
sage: %timeit timeCall(C2C, 1000, i3) # not tested
570
625 loops, best of 3: 148 µs per loop
571
sage: %timeit timeCall(PC, 1000, i3) # not tested
572
625 loops, best of 3: 289 µs per loop
573
574
The overhead of the indirection ( ``C(...) ->
575
ClasscallMetaclass.__call__(...) -> C.__classcall__(...)``) is
576
unfortunately quite large in this case (two method calls instead of
577
one). In reasonable usecases, the overhead should be mostly hidden by the
578
computations inside the classcall::
579
580
sage: %timeit timeCall(C2C.__classcall__, 1000, C2C, i3) # not tested
581
625 loops, best of 3: 33 µs per loop
582
sage: %timeit timeCall(PC.__classcall__, 1000, PC, i3) # not tested
583
625 loops, best of 3: 131 µs per loop
584
585
Finally, there is no significant difference between Cython's V2 and V3
586
syntax for metaclass::
587
588
sage: %timeit timeCall(C2, 1000, i3) # not tested
589
625 loops, best of 3: 330 µs per loop
590
sage: %timeit timeCall(C3, 1000, i3) # not tested
591
625 loops, best of 3: 328 µs per loop
592
"""
593
cdef int i
594
for 0<=i<n:
595
T(*args)
596
597