Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

519

520

521

522

523

524

525

526

527

528

529

530

531

532

533

534

535

536

537

538

539

540

541

542

543

544

545

546

547

548

549

550

551

552

553

554

555

556

557

558

559

560

561

562

563

564

565

566

567

568

569

570

571

572

573

574

575

576

577

578

579

580

581

582

583

584

585

586

587

588

589

590

591

592

593

594

595

596

597

598

599

600

601

602

603

604

605

606

607

608

609

610

611

612

613

614

615

616

617

618

619

620

621

r""" 

Special Methods for Classes 

  

AUTHORS: 

  

- Nicolas M. Thiery (2009-2011) implementation of 

``__classcall__``, ``__classget__``, ``__classcontains__``; 

- Florent Hivert (2010-2012): implementation of ``__classcall_private__``, 

documentation, Cythonization and optimization. 

""" 

  

#***************************************************************************** 

# Copyright (C) 2009 Nicolas M. Thiery <nthiery at users.sf.net> 

# Copyright (C) 2010-2012 Florent Hivert <Florent.Hivert at lri.fr> 

# 

# This program is free software: you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 2 of the License, or 

# (at your option) any later version. 

# http://www.gnu.org/licenses/ 

#***************************************************************************** 

from __future__ import print_function 

  

from cpython.object cimport * 

from cpython.type cimport type as pytype 

  

__all__ = ['ClasscallMetaclass', 'typecall', 'timeCall'] 

  

cdef class ClasscallMetaclass(NestedClassMetaclass): 

""" 

A metaclass providing support for special methods for classes. 

  

From the Section :python:`Special method names 

<reference/datamodel.html#special-method-names>` of the Python Reference 

Manual: 

  

\`a class ``cls`` can implement certain operations on its instances 

that are invoked by special syntax (such as arithmetic operations or 

subscripting and slicing) by defining methods with special 

names\'. 

  

The purpose of this metaclass is to allow for the class ``cls`` to 

implement analogues of those special methods for the operations on the 

class itself. 

  

Currently, the following special methods are supported: 

  

- ``.__classcall__`` (and ``.__classcall_private__``) for 

customizing ``cls(...)`` (analogue of ``.__call__``). 

  

- ``.__classcontains__`` for customizing membership testing 

``x in cls`` (analogue of ``.__contains__``). 

  

- ``.__classget__`` for customizing the binding behavior in 

``foo.cls`` (analogue of ``.__get__``). 

  

See the documentation of :meth:`__call__` and of :meth:`__get__` 

and :meth:`__contains__` for the description of the respective 

protocols. 

  

.. WARNING:: 

  

For technical reasons, ``__classcall__``, 

``__classcall_private__``, ``__classcontains__``, and 

``__classget__`` must be defined as :func:`staticmethod`'s, 

even though they receive the class itself as their first 

argument. 

  

.. WARNING:: 

  

For efficiency reasons, the resolution for the special methods 

is done once for all, upon creation of the class. Thus, later 

dynamic changes to those methods are ignored. But see also 

:meth:`_set_classcall`. 

  

``ClasscallMetaclass`` is an extension of the base :class:`type`. 

  

TODO: find a good name for this metaclass. 

  

TESTS:: 

  

sage: PerfectMatchings(2).list() 

[[(1, 2)]] 

  

.. NOTE:: 

  

If a class is put in this metaclass it automatically becomes a 

new-style class:: 

  

sage: from sage.misc.classcall_metaclass import ClasscallMetaclass 

sage: class Foo: 

....: __metaclass__ = ClasscallMetaclass 

sage: x = Foo(); x 

<__main__.Foo object at 0x...> 

sage: issubclass(Foo, object) 

True 

sage: isinstance(Foo, type) 

True 

""" 

_included_private_doc_ = ['__call__', '__contains__', '__get__'] 

  

def __cinit__(self, *args, **opts): 

r""" 

TESTS:: 

  

sage: from sage.misc.classcall_metaclass import ClasscallMetaclass 

sage: class FOO(object): 

....: __metaclass__ = ClasscallMetaclass 

sage: isinstance(FOO, ClasscallMetaclass) # indirect doctest 

True 

""" 

if '__classcall_private__' in self.__dict__: 

self.classcall = self.__classcall_private__ 

elif hasattr(self, "__classcall__"): 

self.classcall = self.__classcall__ 

else: 

self.classcall = None 

  

self.classcontains = getattr(self, "__classcontains__", None) 

self.classget = getattr(self, "__classget__", None) 

  

def _set_classcall(cls, function): 

r""" 

Change dynamically the classcall function for this class 

  

EXAMPLES:: 

  

sage: from sage.misc.classcall_metaclass import ClasscallMetaclass 

sage: class FOO(object): 

....: __metaclass__ = ClasscallMetaclass 

sage: FOO() 

<__main__.FOO object at ...> 

  

For efficiency reason, the resolution of the ``__classcall__`` 

method is done once for all, upon creation of the class. Thus, 

later dynamic changes to this method are ignored by FOO:: 

  

sage: FOO.__classcall__ = ConstantFunction(1) 

sage: FOO() 

<__main__.FOO object at ...> 

  

but not by subclasses created later on:: 

  

sage: class BAR(FOO): pass 

sage: BAR() 

1 

  

To update the ``classcall`` special function for FOO, one 

should use this setter:: 

  

sage: FOO._set_classcall(ConstantFunction(2)) 

sage: FOO() 

2 

  

Note that it has no influence on subclasses:: 

  

sage: class BAR(FOO): pass 

sage: BAR() 

1 

""" 

cls.classcall = function 

  

def __call__(cls, *args, **kwds): 

r""" 

This method implements ``cls(<some arguments>)``. 

  

Let ``cls`` be a class in :class:`ClasscallMetaclass`, and 

consider a call of the form:: 

  

cls(<some arguments>) 

  

- If ``cls`` defines a method ``__classcall_private__``, then 

this results in a call to:: 

  

cls.__classcall_private__(cls, <some arguments>) 

  

- Otherwise, if ``cls`` has a method ``__classcall__``, then instead 

the following is called:: 

  

cls.__classcall__(cls, <some arguments>) 

  

- If neither of these two methods are implemented, then the standard 

``type.__call__(cls, <some arguments>)`` is called, which in turn 

uses :meth:`~object.__new__` and :meth:`~object.__init__` as usual 

(see Section :python:`Basic Customization 

<reference/datamodel.html#basic-customization>` in the Python 

Reference Manual). 

  

.. warning:: for technical reasons, ``__classcall__`` must be 

defined as a :func:`staticmethod`, even though it receives 

the class itself as its first argument. 

  

EXAMPLES:: 

  

sage: from sage.misc.classcall_metaclass import ClasscallMetaclass 

sage: class Foo(object): 

....: __metaclass__ = ClasscallMetaclass 

....: @staticmethod 

....: def __classcall__(cls): 

....: print("calling classcall") 

....: return type.__call__(cls) 

....: def __new__(cls): 

....: print("calling new") 

....: return super(Foo, cls).__new__(cls) 

....: def __init__(self): 

....: print("calling init") 

sage: Foo() 

calling classcall 

calling new 

calling init 

<__main__.Foo object at ...> 

  

This behavior is inherited:: 

  

sage: class Bar(Foo): pass 

sage: Bar() 

calling classcall 

calling new 

calling init 

<__main__.Bar object at ...> 

  

We now show the usage of ``__classcall_private__``:: 

  

sage: class FooNoInherits(object): 

....: __metaclass__ = ClasscallMetaclass 

....: @staticmethod 

....: def __classcall_private__(cls): 

....: print("calling private classcall") 

....: return type.__call__(cls) 

... 

sage: FooNoInherits() 

calling private classcall 

<__main__.FooNoInherits object at ...> 

  

Here the behavior is not inherited:: 

  

sage: class BarNoInherits(FooNoInherits): pass 

sage: BarNoInherits() 

<__main__.BarNoInherits object at ...> 

  

We now show the usage of both:: 

  

sage: class Foo2(object): 

....: __metaclass__ = ClasscallMetaclass 

....: @staticmethod 

....: def __classcall_private__(cls): 

....: print("calling private classcall") 

....: return type.__call__(cls) 

....: @staticmethod 

....: def __classcall__(cls): 

....: print("calling classcall with %s" % cls) 

....: return type.__call__(cls) 

... 

sage: Foo2() 

calling private classcall 

<__main__.Foo2 object at ...> 

  

sage: class Bar2(Foo2): pass 

sage: Bar2() 

calling classcall with <class '__main__.Bar2'> 

<__main__.Bar2 object at ...> 

  

  

.. rubric:: Discussion 

  

Typical applications include the implementation of factories or of 

unique representation (see :class:`UniqueRepresentation`). Such 

features are traditionaly implemented by either using a wrapper 

function, or fiddling with :meth:`~object.__new__`. 

  

The benefit, compared with fiddling directly with 

:meth:`~object.__new__` is a clear separation of the three distinct 

roles: 

  

- ``cls.__classcall__``: what ``cls(<...>)`` does 

- ``cls.__new__``: memory allocation for a *new* instance 

- ``cls.__init__``: initialization of a newly created instance 

  

The benefit, compared with using a wrapper function, is that the 

user interface has a single handle for the class:: 

  

sage: x = Partition([3,2,2]) 

sage: isinstance(x, Partition) # todo: not implemented 

  

instead of:: 

  

sage: isinstance(x, sage.combinat.partition.Partition) 

True 

  

Another difference is that ``__classcall__`` is inherited by 

subclasses, which may be desirable, or not. If not, one should 

instead define the method ``__classcall_private__`` which will 

not be called for subclasses. Specifically, if a class ``cls`` 

defines both methods ``__classcall__`` and 

``__classcall_private__`` then, for any subclass ``sub`` of ``cls``: 

  

- ``cls(<args>)`` will call ``cls.__classcall_private__(cls, <args>)`` 

- ``sub(<args>)`` will call ``cls.__classcall__(sub, <args>)`` 

  

  

TESTS: 

  

We check that the idiom ``method_name in cls.__dict__`` works 

for extension types:: 

  

sage: "_sage_" in SageObject.__dict__, "_sage_" in Parent.__dict__ 

(True, False) 

  

We check for memory leaks:: 

  

sage: class NOCALL(object): 

....: __metaclass__ = ClasscallMetaclass 

....: pass 

sage: sys.getrefcount(NOCALL()) 

1 

  

We check that exceptions are correctly handled:: 

  

sage: class Exc(object): 

....: __metaclass__ = ClasscallMetaclass 

....: @staticmethod 

....: def __classcall__(cls): 

....: raise ValueError("Calling classcall") 

sage: Exc() 

Traceback (most recent call last): 

... 

ValueError: Calling classcall 

""" 

if cls.classcall is not None: 

return cls.classcall(cls, *args, **kwds) 

else: 

# Fast version of type.__call__(cls, *args, **kwds) 

return (<PyTypeObject*>type).tp_call(cls, args, kwds) 

  

def __get__(cls, instance, owner): 

r""" 

This method implements instance binding behavior for nested classes. 

  

Suppose that a class ``Outer`` contains a nested class ``cls`` which 

is an instance of this metaclass. For any object ``obj`` of ``cls``, 

this method implements a instance binding behavior for ``obj.cls`` by 

delegating it to ``cls.__classget__(Outer, obj, owner)`` if available. 

Otherwise, ``obj.cls`` results in ``cls``, as usual. 

  

Similarly, a class binding as in ``Outer.cls`` is delegated 

to ``cls.__classget__(Outer, None, owner)`` if available and 

to ``cls`` if not. 

  

.. warning:: for technical reasons, ``__classget__`` must be 

defined as a :func:`staticmethod`, even though it receives 

the class itself as its first argument. 

  

For technical details, and in particular the description of the 

``owner`` argument, see the Section :python:`Implementing Descriptor 

<reference/datamodel.html#implementing-descriptors>` in the Python 

reference manual. 

  

EXAMPLES: 

  

We show how to implement a nested class ``Outer.Inner`` with a 

binding behavior, as if it was a method of ``Outer``: namely, 

for ``obj`` an instance of ``Outer``, calling 

``obj.Inner(...)`` is equivalent to ``Outer.Inner(obj, ...)``:: 

  

sage: import functools 

sage: from sage.misc.nested_class import NestedClassMetaclass 

sage: from sage.misc.classcall_metaclass import ClasscallMetaclass 

sage: class Outer: 

....: __metaclass__ = NestedClassMetaclass # workaround for python pickling bug 

... 

....: class Inner(object): 

....: __metaclass__ = ClasscallMetaclass 

....: @staticmethod 

....: def __classget__(cls, instance, owner): 

....: print("calling __classget__(%s, %s, %s)" % ( 

....: cls, instance, owner)) 

....: if instance is None: 

....: return cls 

....: return functools.partial(cls, instance) 

....: def __init__(self, instance): 

....: self.instance = instance 

sage: obj = Outer() 

sage: bar = obj.Inner() 

calling __classget__(<class '__main__.Outer.Inner'>, <__main__.Outer object at 0x...>, <class '__main__.Outer'>) 

sage: bar.instance == obj 

True 

  

Calling ``Outer.Inner`` returns the (unbinded) class as usual:: 

  

sage: Inner = Outer.Inner 

calling __classget__(<class '__main__.Outer.Inner'>, None, <class '__main__.Outer'>) 

sage: Inner 

<class '__main__.Outer.Inner'> 

sage: type(bar) is Inner 

True 

  

.. warning:: Inner has to be a new style class (i.e. a subclass of object). 

  

.. warning:: 

  

calling ``obj.Inner`` does no longer return a class:: 

  

sage: bind = obj.Inner 

calling __classget__(<class '__main__.Outer.Inner'>, <__main__.Outer object at 0x...>, <class '__main__.Outer'>) 

sage: bind 

<functools.partial object at 0x...> 

""" 

if cls.classget: 

return cls.classget(cls, instance, owner) 

else: 

return cls 

  

def __contains__(cls, x): 

r""" 

This method implements membership testing for a class 

  

Let ``cls`` be a class in :class:`ClasscallMetaclass`, and consider 

a call of the form:: 

  

x in cls 

  

If ``cls`` defines a method ``__classcontains__``, then this 

results in a call to:: 

  

cls.__classcontains__(cls, x) 

  

.. warning:: for technical reasons, ``__classcontains__`` must 

be defined as a :func:`staticmethod`, even though it 

receives the class itself as its first argument. 

  

EXAMPLES: 

  

We construct a class which implements membership testing, and 

which contains ``1`` and no other x:: 

  

sage: from sage.misc.classcall_metaclass import ClasscallMetaclass 

sage: class Foo(object): 

....: __metaclass__ = ClasscallMetaclass 

....: @staticmethod 

....: def __classcontains__(cls, x): 

....: return x == 1 

sage: 1 in Foo 

True 

sage: 2 in Foo 

False 

  

We now check that for a class without ``__classcontains__`` 

method, we emulate the usual error message:: 

  

sage: from sage.misc.classcall_metaclass import ClasscallMetaclass 

sage: class Bar(object): 

....: __metaclass__ = ClasscallMetaclass 

sage: 1 in Bar 

Traceback (most recent call last): 

... 

TypeError: argument of type 'type' is not iterable 

""" 

if cls.classcontains: 

return cls.classcontains(cls, x) 

else: 

return x in object 

  

  

def typecall(pytype cls, *args, **kwds): 

r""" 

Object construction 

  

This is a faster equivalent to ``type.__call__(cls, <some arguments>)``. 

  

INPUT: 

  

- ``cls`` -- the class used for constructing the instance. It must be 

a builtin type or a new style class (inheriting from :class:`object`). 

  

EXAMPLES:: 

  

sage: from sage.misc.classcall_metaclass import typecall 

sage: class Foo(object): pass 

sage: typecall(Foo) 

<__main__.Foo object at 0x...> 

sage: typecall(list) 

[] 

sage: typecall(Integer, 2) 

2 

  

.. warning:: 

  

:func:`typecall` doesn't work for old style class (not inheriting from 

:class:`object`):: 

  

sage: class Bar: pass 

sage: typecall(Bar) 

Traceback (most recent call last): 

... 

TypeError: Argument 'cls' has incorrect type (expected type, got classobj) 

""" 

return (<PyTypeObject*>type).tp_call(cls, args, kwds) 

  

# Class for timing:: 

  

class CRef(object): 

def __init__(self, i): 

""" 

TESTS:: 

  

sage: from sage.misc.classcall_metaclass import CRef 

sage: P = CRef(2); P.i 

3 

""" 

self.i = i+1 

  

class C2(object, metaclass=ClasscallMetaclass): 

def __init__(self, i): 

""" 

TESTS:: 

  

sage: from sage.misc.classcall_metaclass import C2 

sage: P = C2(2); P.i 

3 

""" 

self.i = i+1 

  

class C3(object, metaclass = ClasscallMetaclass): 

def __init__(self, i): 

""" 

TESTS:: 

  

sage: from sage.misc.classcall_metaclass import C3 

sage: P = C3(2); P.i 

3 

""" 

self.i = i+1 

  

class C2C(object, metaclass=ClasscallMetaclass): 

@staticmethod 

def __classcall__(cls, i): 

""" 

TESTS:: 

  

sage: from sage.misc.classcall_metaclass import C2C 

sage: C2C(2) 

3 

""" 

return i+1 

  

def timeCall(T, int n, *args): 

r""" 

We illustrate some timing when using the classcall mechanism. 

  

EXAMPLES:: 

  

sage: from sage.misc.classcall_metaclass import ( 

....: ClasscallMetaclass, CRef, C2, C3, C2C, timeCall) 

sage: timeCall(object, 1000) 

  

For reference let construct basic objects and a basic Python class:: 

  

sage: %timeit timeCall(object, 1000) # not tested 

625 loops, best of 3: 41.4 µs per loop 

  

sage: i1 = int(1); i3 = int(3) # don't use Sage's Integer 

sage: class PRef(object): 

....: def __init__(self, i): 

....: self.i = i+i1 

  

For a Python class, compared to the reference class there is a 10% 

overhead in using :class:`ClasscallMetaclass` if there is no classcall 

defined:: 

  

sage: class P(object): 

....: __metaclass__ = ClasscallMetaclass 

....: def __init__(self, i): 

....: self.i = i+i1 

  

sage: %timeit timeCall(PRef, 1000, i3) # not tested 

625 loops, best of 3: 420 µs per loop 

sage: %timeit timeCall(P, 1000, i3) # not tested 

625 loops, best of 3: 458 µs per loop 

  

For a Cython class (not cdef since they doesn't allows metaclasses), the 

overhead is a little larger:: 

  

sage: %timeit timeCall(CRef, 1000, i3) # not tested 

625 loops, best of 3: 266 µs per loop 

sage: %timeit timeCall(C2, 1000, i3) # not tested 

625 loops, best of 3: 298 µs per loop 

  

Let's now compare when there is a classcall defined:: 

  

sage: class PC(object): 

....: __metaclass__ = ClasscallMetaclass 

....: @staticmethod 

....: def __classcall__(cls, i): 

....: return i+i1 

sage: %timeit timeCall(C2C, 1000, i3) # not tested 

625 loops, best of 3: 148 µs per loop 

sage: %timeit timeCall(PC, 1000, i3) # not tested 

625 loops, best of 3: 289 µs per loop 

  

The overhead of the indirection ( ``C(...) -> 

ClasscallMetaclass.__call__(...) -> C.__classcall__(...)``) is 

unfortunately quite large in this case (two method calls instead of 

one). In reasonable usecases, the overhead should be mostly hidden by the 

computations inside the classcall:: 

  

sage: %timeit timeCall(C2C.__classcall__, 1000, C2C, i3) # not tested 

625 loops, best of 3: 33 µs per loop 

sage: %timeit timeCall(PC.__classcall__, 1000, PC, i3) # not tested 

625 loops, best of 3: 131 µs per loop 

  

Finally, there is no significant difference between Cython's V2 and V3 

syntax for metaclass:: 

  

sage: %timeit timeCall(C2, 1000, i3) # not tested 

625 loops, best of 3: 330 µs per loop 

sage: %timeit timeCall(C3, 1000, i3) # not tested 

625 loops, best of 3: 328 µs per loop 

""" 

cdef int i 

for 0<=i<n: 

T(*args)