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

import typing 

import inspect 

import functools 

from . import _uarray # type: ignore 

import copyreg # type: ignore 

import atexit 

import pickle 

 

ArgumentExtractorType = typing.Callable[..., typing.Tuple["Dispatchable", ...]] 

ArgumentReplacerType = typing.Callable[ 

[typing.Tuple, typing.Dict, typing.Tuple], typing.Tuple[typing.Tuple, typing.Dict] 

] 

 

from ._uarray import ( # type: ignore 

BackendNotImplementedError, 

_Function, 

_SkipBackendContext, 

_SetBackendContext, 

) 

 

__all__ = [ 

"set_backend", 

"set_global_backend", 

"skip_backend", 

"register_backend", 

"clear_backends", 

"create_multimethod", 

"generate_multimethod", 

"_Function", 

"BackendNotImplementedError", 

"Dispatchable", 

"wrap_single_convertor", 

"all_of_type", 

"mark_as", 

] 

 

 

def unpickle_function(mod_name, qname): 

import importlib 

 

try: 

module = importlib.import_module(mod_name) 

func = getattr(module, qname) 

return func 

except (ImportError, AttributeError) as e: 

from pickle import UnpicklingError 

 

raise UnpicklingError from e 

 

 

def pickle_function(func): 

mod_name = getattr(func, "__module__", None) 

qname = getattr(func, "__qualname__", None) 

 

try: 

test = unpickle_function(mod_name, qname) 

except pickle.UnpicklingError: 

test = None 

 

if test is not func: 

raise pickle.PicklingError( 

"Can't pickle {}: it's not the same object as {}".format(func, test) 

) 

 

return unpickle_function, (mod_name, qname) 

 

 

copyreg.pickle(_Function, pickle_function) 

atexit.register(_uarray.clear_all_globals) 

 

 

def create_multimethod(*args, **kwargs): 

""" 

Creates a decorator for generating multimethods. 

 

This function creates a decorator that can be used with an argument 

extractor in order to generate a multimethod. Other than for the 

argument extractor, all arguments are passed on to 

:obj:`generate_multimethod`. 

 

See Also 

-------- 

generate_multimethod 

Generates a multimethod. 

""" 

 

def wrapper(a): 

return generate_multimethod(a, *args, **kwargs) 

 

return wrapper 

 

 

def generate_multimethod( 

argument_extractor: ArgumentExtractorType, 

argument_replacer: ArgumentReplacerType, 

domain: str, 

default: typing.Optional[typing.Callable] = None, 

): 

""" 

Generates a multimethod. 

 

Parameters 

---------- 

argument_extractor : ArgumentExtractorType 

A callable which extracts the dispatchable arguments. Extracted arguments 

should be marked by the :obj:`Dispatchable` class. It has the same signature 

as the desired multimethod. 

argument_replacer : ArgumentReplacerType 

A callable with the signature (args, kwargs, dispatchables), which should also 

return an (args, kwargs) pair with the dispatchables replaced inside the args/kwargs. 

domain : str 

A string value indicating the domain of this multimethod. 

default: Optional[Callable], optional 

The default implementation of this multimethod, where ``None`` (the default) specifies 

there is no default implementation. 

 

Examples 

-------- 

In this example, ``a`` is to be dispatched over, so we return it, while marking it as an ``int``. 

The trailing comma is needed because the args have to be returned as an iterable. 

 

>>> def override_me(a, b): 

... return Dispatchable(a, int), 

 

Next, we define the argument replacer that replaces the dispatchables inside args/kwargs with the 

supplied ones. 

 

>>> def override_replacer(args, kwargs, dispatchables): 

... return (dispatchables[0], args[1]), {} 

 

Next, we define the multimethod. 

 

>>> overridden_me = generate_multimethod( 

... override_me, override_replacer, "ua_examples" 

... ) 

 

Notice that there's no default implementation, unless you supply one. 

 

>>> overridden_me(1, "a") 

Traceback (most recent call last): 

... 

uarray.backend.BackendNotImplementedError: ... 

>>> overridden_me2 = generate_multimethod( 

... override_me, override_replacer, "ua_examples", default=lambda x, y: (x, y) 

... ) 

>>> overridden_me2(1, "a") 

(1, 'a') 

 

See Also 

-------- 

uarray 

See the module documentation for how to override the method by creating backends. 

""" 

kw_defaults, arg_defaults, opts = get_defaults(argument_extractor) 

ua_func = _Function( 

argument_extractor, 

argument_replacer, 

domain, 

arg_defaults, 

kw_defaults, 

default, 

) 

 

return functools.update_wrapper(ua_func, argument_extractor) 

 

 

def set_backend(backend, coerce=False, only=False): 

""" 

A context manager that sets the preferred backend. 

 

Parameters 

---------- 

backend 

The backend to set. 

coerce 

Whether or not to coerce to a specific backend's types. Implies ``only``. 

only 

Whether or not this should be the last backend to try. 

 

See Also 

-------- 

skip_backend: A context manager that allows skipping of backends. 

set_global_backend: Set a single, global backend for a domain. 

""" 

try: 

return backend.__ua_cache__["set", coerce, only] 

except AttributeError: 

backend.__ua_cache__ = {} 

except KeyError: 

pass 

 

ctx = _SetBackendContext(backend, coerce, only) 

backend.__ua_cache__["set", coerce, only] = ctx 

return ctx 

 

 

def skip_backend(backend): 

""" 

A context manager that allows one to skip a given backend from processing 

entirely. This allows one to use another backend's code in a library that 

is also a consumer of the same backend. 

 

Parameters 

---------- 

backend 

The backend to skip. 

 

See Also 

-------- 

set_backend: A context manager that allows setting of backends. 

set_global_backend: Set a single, global backend for a domain. 

""" 

try: 

return backend.__ua_cache__["skip"] 

except AttributeError: 

backend.__ua_cache__ = {} 

except KeyError: 

pass 

 

ctx = _SkipBackendContext(backend) 

backend.__ua_cache__["skip"] = ctx 

return ctx 

 

 

def get_defaults(f): 

sig = inspect.signature(f) 

kw_defaults = {} 

arg_defaults = [] 

opts = set() 

for k, v in sig.parameters.items(): 

if v.default is not inspect.Parameter.empty: 

kw_defaults[k] = v.default 

if v.kind in ( 

inspect.Parameter.POSITIONAL_ONLY, 

inspect.Parameter.POSITIONAL_OR_KEYWORD, 

): 

arg_defaults.append(v.default) 

opts.add(k) 

 

return kw_defaults, tuple(arg_defaults), opts 

 

 

def set_global_backend(backend, coerce=False, only=False): 

""" 

This utility method replaces the default backend for permanent use. It 

will be tried in the list of backends automatically, unless the 

``only`` flag is set on a backend. This will be the first tried 

backend outside the :obj:`set_backend` context manager. 

 

Note that this method is not thread-safe. 

 

.. warning:: 

We caution library authors against using this function in 

their code. We do *not* support this use-case. This function 

is meant to be used only by users themselves, or by a reference 

implementation, if one exists. 

 

Parameters 

---------- 

backend 

The backend to register. 

 

See Also 

-------- 

set_backend: A context manager that allows setting of backends. 

skip_backend: A context manager that allows skipping of backends. 

""" 

_uarray.set_global_backend(backend, coerce, only) 

 

 

def register_backend(backend): 

""" 

This utility method sets registers backend for permanent use. It 

will be tried in the list of backends automatically, unless the 

``only`` flag is set on a backend. 

 

Note that this method is not thread-safe. 

 

Parameters 

---------- 

backend 

The backend to register. 

""" 

_uarray.register_backend(backend) 

 

 

def clear_backends(domain, registered=True, globals=False): 

""" 

This utility method clears registered backends. 

 

.. warning:: 

We caution library authors against using this function in 

their code. We do *not* support this use-case. This function 

is meant to be used only by the users themselves. 

 

.. warning:: 

Do NOT use this method inside a multimethod call, or the 

program is likely to crash. 

 

Parameters 

---------- 

domain : Optional[str] 

The domain for which to de-register backends. ``None`` means 

de-register for all domains. 

registered : bool 

Whether or not to clear registered backends. See :obj:`register_backend`. 

globals : bool 

Whether or not to clear global backends. See :obj:`set_global_backend`. 

 

See Also 

-------- 

register_backend : Register a backend globally. 

set_global_backend : Set a global backend. 

""" 

_uarray.clear_backends(domain, registered, globals) 

 

 

class Dispatchable: 

""" 

A utility class which marks an argument with a specific dispatch type. 

 

 

Attributes 

---------- 

value 

The value of the Dispatchable. 

 

type 

The type of the Dispatchable. 

 

Examples 

-------- 

>>> x = Dispatchable(1, str) 

>>> x 

<Dispatchable: type=<class 'str'>, value=1> 

 

See Also 

-------- 

all_of_type 

Marks all unmarked parameters of a function. 

 

mark_as 

Allows one to create a utility function to mark as a given type. 

""" 

 

def __init__(self, value, dispatch_type, coercible=True): 

self.value = value 

self.type = dispatch_type 

self.coercible = coercible 

 

def __getitem__(self, index): 

return (self.type, self.value)[index] 

 

def __str__(self): 

return "<{0}: type={1!r}, value={2!r}>".format( 

type(self).__name__, self.type, self.value 

) 

 

__repr__ = __str__ 

 

 

def mark_as(dispatch_type): 

""" 

Creates a utility function to mark something as a specific type. 

 

Examples 

-------- 

>>> mark_int = mark_as(int) 

>>> mark_int(1) 

<Dispatchable: type=<class 'int'>, value=1> 

""" 

return functools.partial(Dispatchable, dispatch_type=dispatch_type) 

 

 

def all_of_type(arg_type): 

""" 

Marks all unmarked arguments as a given type. 

 

Examples 

-------- 

>>> @all_of_type(str) 

... def f(a, b): 

... return a, Dispatchable(b, int) 

>>> f('a', 1) 

(<Dispatchable: type=<class 'str'>, value='a'>, <Dispatchable: type=<class 'int'>, value=1>) 

""" 

 

def outer(func): 

@functools.wraps(func) 

def inner(*args, **kwargs): 

extracted_args = func(*args, **kwargs) 

return tuple( 

Dispatchable(arg, arg_type) 

if not isinstance(arg, Dispatchable) 

else arg 

for arg in extracted_args 

) 

 

return inner 

 

return outer 

 

 

def wrap_single_convertor(convert_single): 

""" 

Wraps a ``__ua_convert__`` defined for a single element to all elements. 

If any of them return ``NotImplemented``, the operation is assumed to be 

undefined. 

 

Accepts a signature of (value, type, coerce). 

""" 

 

@functools.wraps(convert_single) 

def __ua_convert__(dispatchables, coerce): 

converted = [] 

for d in dispatchables: 

c = convert_single(d.value, d.type, coerce and d.coercible) 

 

if c is NotImplemented: 

return NotImplemented 

 

converted.append(c) 

 

return converted 

 

return __ua_convert__