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