1 2# -*- coding: utf-8 -*- 3 4u'''Some, basic definitions and functions. 5''' 6# make sure int/int division yields float quotient 7from __future__ import division 8division = 1 / 2 # .albers, .datums, .ellipsoidalVincenty, .ellipsoids, 9if not division: # .elliptic, .etm, .fmath, .formy, .lcc, .osgr, .utily 10 raise ImportError('%s 1/2 == %s' % ('division', division)) 11del division 12 13from pygeodesy.errors import _ImportError, _IsnotError, _TypeError, \ 14 _TypesError, _ValueError, _xkwds_get 15from pygeodesy.interns import EPS0, MISSING, NEG0, NN, _by_, _DOT_, \ 16 _N_A_, _name_, _SPACE_, _UNDER_, _utf_8_, \ 17 _version_, _0_0 18from pygeodesy.lazily import _ALL_LAZY, _FOR_DOCS 19 20from copy import copy as _copy, deepcopy as _deepcopy 21from inspect import isclass as _isclass 22from math import copysign as _copysign, isinf, isnan 23 24__all__ = _ALL_LAZY.basics 25__version__ = '21.09.14' 26 27_required_ = 'required' 28 29try: # Luciano Ramalho, "Fluent Python", page 395, O'Reilly, 2016 30 from numbers import Integral as _Ints # int objects 31except ImportError: 32 try: 33 _Ints = int, long # int objects (C{tuple}) 34 except NameError: # Python 3+ 35 _Ints = int, # int objects (C{tuple}) 36 37try: # similarly ... 38 from numbers import Real as _Scalars # scalar objects 39except ImportError: 40 try: 41 _Scalars = int, long, float # scalar objects (C{tuple}) 42 except NameError: 43 _Scalars = int, float # scalar objects (C{tuple}) 44 45try: 46 try: # use C{from collections.abc import ...} in Python 3.9+ 47 from collections.abc import Sequence as _Sequence # imported by .points 48 except ImportError: # no .abc in Python 2.7- 49 from collections import Sequence as _Sequence # imported by .points 50 if isinstance([], _Sequence) and isinstance((), _Sequence): 51 # and isinstance(range(1), _Sequence): 52 _Seqs = _Sequence 53 else: 54 raise ImportError # _AssertionError 55except ImportError: 56 _Sequence = tuple # immutable for .points._Basequence 57 _Seqs = list, _Sequence # , range for function len2 below 58 59try: 60 _Bytes = unicode, bytearray # PYCHOK expected 61 _Strs = basestring, str 62 63 def _Xstr(x): # XXX invoke only with caught import exception B{C{x}}: ... 64 t = str(x) # 'cannot import name _distributor_init' (only for numpy, ... 65 if '_distributor_init' in t: # ... scipy on arm64 macOS' Python 2.7.16?) 66 from sys import exc_info 67 from traceback import extract_tb 68 tb = exc_info()[2] # 3-tuple (type, value, traceback) 69 t4 = extract_tb(tb, 1)[0] # 4-tuple (file, line, name, 'import ...') 70 t = _SPACE_('cannot', t4[3] or _N_A_) 71 del tb, t4 72 return t 73 74except NameError: # Python 3+ 75 _Bytes = bytes, bytearray 76 _Strs = str, 77 _Xstr = str 78 79 80def clips(bstr, limit=50, white=NN): 81 '''Clip a string to the given length limit. 82 83 @arg bstr: String (C{bytes} or C{str}). 84 @kwarg limit: Length limit (C{int}). 85 @kwarg white: Optionally, replace all whitespace (C{str}). 86 87 @return: The clipped or unclipped B{C{bstr}}. 88 ''' 89 if len(bstr) > limit > 8: 90 h = limit // 2 91 bstr = NN(bstr[:h], type(bstr)('....'), bstr[-h:]) 92 if white: # replace whitespace 93 bstr = type(bstr)(white).join(bstr.split()) 94 return bstr 95 96 97def copysign0(x, y): 98 '''Like C{math.copysign(x, y)} except C{zero}, I{unsigned}. 99 100 @return: C{math.copysign(B{x}, B{y})} if B{C{x}} else C{0}. 101 ''' 102 return _copysign(x, y) if x else copytype(0, x) 103 104 105def copytype(x, y): 106 '''Return the value of B{x} as C{type} of C{y}. 107 108 @return: C{type(B{y})(B{x})}. 109 ''' 110 return type(y)(x) 111 112 113def halfs2(str2): 114 '''Split a string in 2 halfs. 115 116 @arg str2: String to split (C{str}). 117 118 @return: 2-Tuple (_1st, _2nd) half (C{str}). 119 120 @raise ValueError: Zero or odd C{len}(B{C{str2}}). 121 ''' 122 h, r = divmod(len(str2), 2) 123 if r or not h: 124 raise _ValueError(str2=str2, txt='odd') 125 return str2[:h], str2[h:] 126 127 128def isbool(obj): 129 '''Check whether an object is C{bool}ean. 130 131 @arg obj: The object (any C{type}). 132 133 @return: C{True} if B{C{obj}} is C{bool}ean, 134 C{False} otherwise. 135 ''' 136 return isinstance(obj, bool) # and (obj is True 137# or obj is False) 138 139 140if _FOR_DOCS: # XXX avoid epidoc Python 2.7 error 141 def isclass(obj): 142 '''Return C{True} if B{C{obj}} is a C{class}. 143 144 @see: Python's C{inspect.isclass}. 145 ''' 146 return _isclass(obj) 147else: 148 isclass = _isclass 149 150 151try: 152 from math import isfinite # new in Python 3+ 153except ImportError: 154 155 def isfinite(obj): 156 '''Check for C{Inf} and C{NaN} values. 157 158 @arg obj: Value (C{scalar}). 159 160 @return: C{False} if B{C{obj}} is C{INF} or C{NAN}, 161 C{True} otherwise. 162 163 @raise TypeError: Non-scalar B{C{obj}}. 164 ''' 165 if not isscalar(obj): 166 raise _IsnotError(isscalar.__name__, obj=obj) 167 return not (isinf(obj) or isnan(obj)) 168 169 170try: 171 isidentifier = str.isidentifier # Python 3, must be str 172except AttributeError: # Python 2- 173 174 def isidentifier(obj): 175 '''Return C{True} if B{C{obj}} is a valid Python identifier. 176 ''' 177 return True if (obj and obj.replace(_UNDER_, NN).isalnum() 178 and not obj[:1].isdigit()) else False 179 180# from math import isinf 181 182 183def isint(obj, both=False): 184 '''Check for C{int} type or an integer C{float} value. 185 186 @arg obj: The object (any C{type}). 187 @kwarg both: Optionally, check C{float} type and value (C{bool}). 188 189 @return: C{True} if B{C{obj}} is C{int} or an integer 190 C{float}, C{False} otherwise. 191 192 @note: C{isint(True)} or C{isint(False)} returns C{False} (and 193 no longer C{True}). 194 ''' 195 if both and isinstance(obj, float): # NOT _Scalars! 196 try: 197 return obj.is_integer() 198 except AttributeError: 199 return False # XXX float(int(obj)) == obj? 200 return isinstance(obj, _Ints) and not isbool(obj) 201 202 203try: 204 from keyword import iskeyword # Python 2.7+ 205except ImportError: 206 207 def iskeyword(unused): 208 '''Not Implemented. Return C{False}, always. 209 ''' 210 return False 211 212# from math import isnan 213 214 215def isnear0(x, eps0=EPS0): 216 '''Is B{C{x}} near zero? 217 218 @arg x: Value (C{scalar}). 219 @kwarg eps0: Near-zero (C{EPS0}). 220 221 @return: C{True} if C{abs(B{x}) < B{eps0}}, 222 C{False} otherwise. 223 224 @see: Function L{isnon0}. 225 ''' 226 return eps0 > x > -eps0 227 228 229def isneg0(x): 230 '''Check for L{NEG0}, negative C{0.0}. 231 232 @arg x: Value (C{scalar}). 233 234 @return: C{True} if B{C{x}} is C{NEG0} or C{-0.0}, 235 C{False} otherwise. 236 ''' 237 return x in (_0_0, NEG0) and _copysign(1, x) < 0 238# and str(x).startswith('-') 239 240 241def isnon0(x, eps0=EPS0): 242 '''Is B{C{x}} non-zero? 243 244 @arg x: Value (C{scalar}). 245 @kwarg eps0: Near-zero (C{EPS0}). 246 247 @return: C{True} if C{abs(B{x}) > B{eps0}}, 248 C{False} otherwise. 249 250 @see: Function L{isnear0}. 251 ''' 252 return x > eps0 or (-x) > eps0 253 254 255def isodd(x): 256 '''Is B{C{x}} odd? 257 258 @arg x: Value (C{scalar}). 259 260 @return: C{True} if B{C{x}} is odd, 261 C{False} otherwise. 262 ''' 263 return bool(int(x) & 1) 264 265 266def isscalar(obj): 267 '''Check for scalar types. 268 269 @arg obj: The object (any C{type}). 270 271 @return: C{True} if B{C{obj}} is C{scalar}, C{False} otherwise. 272 ''' 273 return isinstance(obj, _Scalars) 274 275 276def issequence(obj, *excluded): 277 '''Check for sequence types. 278 279 @arg obj: The object (any C{type}). 280 @arg excluded: Optional exclusions (C{type}). 281 282 @note: Excluding C{tuple} implies excluding C{namedtuple}. 283 284 @return: C{True} if B{C{obj}} is a sequence, C{False} otherwise. 285 ''' 286 return False if (excluded and isinstance(obj, excluded)) else \ 287 isinstance(obj, _Seqs) 288 289 290def isstr(obj): 291 '''Check for string types. 292 293 @arg obj: The object (any C{type}). 294 295 @return: C{True} if B{C{obj}} is C{str}, C{False} otherwise. 296 ''' 297 return isinstance(obj, _Strs) 298 299 300def issubclassof(Sub, *Supers): 301 '''Check whether a class is a sub-class of some class(es). 302 303 @arg Sub: The sub-class (C{class}). 304 @arg Supers: One or more C(super) classes (C{class}). 305 306 @return: C{True} if B{C{Sub}} is a sub-class of any 307 B{C{Supers}}, C{False} otherwise (C{bool}). 308 ''' 309 if isclass(Sub): 310 for S in Supers: # any() 311 if isclass(S) and issubclass(Sub, S): 312 return True 313 return False 314 315 316def len2(items): 317 '''Make built-in function L{len} work for generators, iterators, 318 etc. since those can only be started exactly once. 319 320 @arg items: Generator, iterator, list, range, tuple, etc. 321 322 @return: 2-Tuple C{(n, items)} of the number of items (C{int}) 323 and the items (C{list} or C{tuple}). 324 ''' 325 if not isinstance(items, _Seqs): # NOT hasattr(items, '__len__'): 326 items = list(items) 327 return len(items), items 328 329 330def map1(fun1, *xs): # XXX map_ 331 '''Apply each argument to a single-argument function and 332 return a C{tuple} of results. 333 334 @arg fun1: 1-Arg function to apply (C{callable}). 335 @arg xs: Arguments to apply (C{any positional}). 336 337 @return: Function results (C{tuple}). 338 ''' 339 return tuple(map(fun1, xs)) # note xs, not *xs 340 341 342def map2(func, *xs): 343 '''Apply arguments to a function and return a C{tuple} of results. 344 345 Unlike Python 2's built-in L{map}, Python 3+ L{map} returns a 346 L{map} object, an iterator-like object which generates the 347 results only once. Converting the L{map} object to a tuple 348 maintains Python 2 behavior. 349 350 @arg func: Function to apply (C{callable}). 351 @arg xs: Arguments to apply (C{list, tuple, ...}). 352 353 @return: Function results (C{tuple}). 354 ''' 355 return tuple(map(func, *xs)) # note *xs, not xs 356 357 358def neg(x): 359 '''Negate C{x} unless C{zero} or C{NEG0}. 360 361 @return: C{-B{x}} if B{C{x}} else C{0.0}. 362 ''' 363 return -x if x else _0_0 364 365 366def neg_(*xs): 367 '''Negate all of C{xs} with L{neg}. 368 369 @return: A C{tuple(neg(x) for x in B{xs})}. 370 ''' 371 return tuple(map(neg, xs)) # see map1 372 373 374def signOf(x): 375 '''Return sign of C{x} as C{int}. 376 377 @return: -1, 0 or +1 (C{int}). 378 ''' 379 return 1 if x > 0 else (-1 if x < 0 else 0) 380 381 382def splice(iterable, n=2, **fill): 383 '''Split an iterable into C{n} slices. 384 385 @arg iterable: Items to be spliced (C{list}, C{tuple}, ...). 386 @kwarg n: Number of slices to generate (C{int}). 387 @kwarg fill: Optional fill value for missing items. 388 389 @return: A generator for each of B{C{n}} slices, 390 M{iterable[i::n] for i=0..n}. 391 392 @raise TypeError: Invalid B{C{n}}. 393 394 @note: Each generated slice is a C{tuple} or a C{list}, 395 the latter only if the B{C{iterable}} is a C{list}. 396 397 @example: 398 399 >>> from pygeodesy import splice 400 401 >>> a, b = splice(range(10)) 402 >>> a, b 403 ((0, 2, 4, 6, 8), (1, 3, 5, 7, 9)) 404 405 >>> a, b, c = splice(range(10), n=3) 406 >>> a, b, c 407 ((0, 3, 6, 9), (1, 4, 7), (2, 5, 8)) 408 409 >>> a, b, c = splice(range(10), n=3, fill=-1) 410 >>> a, b, c 411 ((0, 3, 6, 9), (1, 4, 7, -1), (2, 5, 8, -1)) 412 413 >>> tuple(splice(list(range(9)), n=5)) 414 ([0, 5], [1, 6], [2, 7], [3, 8], [4]) 415 416 >>> splice(range(9), n=1) 417 <generator object splice at 0x0...> 418 ''' 419 if not isint(n): 420 raise _TypeError(n=n) 421 422 t = iterable 423 if not isinstance(t, (list, tuple)): 424 t = tuple(t) # force tuple, also for PyPy3 425 426 if n > 1: 427 if fill: 428 fill = _xkwds_get(fill, fill=MISSING) 429 if fill is not MISSING: 430 m = len(t) % n 431 if m > 0: # fill with same type 432 t += type(t)((fill,)) * (n - m) 433 for i in range(n): 434 # XXX t[i::n] chokes PyChecker 435 yield t[slice(i, None, n)] 436 else: 437 yield t 438 439 440def ub2str(ub): 441 '''Convert C{unicode} or C{bytes} to C{str}. 442 ''' 443 if isinstance(ub, _Bytes): 444 ub = str(ub.decode(_utf_8_)) 445 return ub 446 447 448def unsign0(x): 449 '''Return unsigned C{0.0}. 450 451 @return: C{B{x}} if B{C{x}} else C{0.0}. 452 ''' 453 return x if x else _0_0 454 455 456def _xcopy(inst, deep=False): 457 '''(INTERNAL) Copy an object, shallow or deep. 458 459 @arg inst: The object to copy (any C{type}). 460 @kwarg deep: If C{True} make a deep, otherwise 461 a shallow copy (C{bool}). 462 463 @return: The copy of B{C{inst}}. 464 ''' 465 return _deepcopy(inst) if deep else _copy(inst) 466 467 468def _xImportError(x, where, **name): 469 '''(INTERNAL) Embellish an C{ImportError}. 470 ''' 471 t = _SPACE_(_required_, _by_, _xwhere(where, **name)) 472 return _ImportError(_Xstr(x), txt=t) 473 474 475def _xinstanceof(*Types, **name_value_pairs): 476 '''(INTERNAL) Check C{Types} of all C{name=value} pairs. 477 478 @arg Types: One or more classes or types (C{class}). 479 @kwarg name_value_pairs: One or more C{B{name}=value} pairs 480 with the C{value} to be checked. 481 482 @raise TypeError: At least one of the B{C{name_value_pairs}} 483 is not any of the B{C{Types}}. 484 ''' 485 for n, v in name_value_pairs.items(): 486 if not isinstance(v, Types): 487 raise _TypesError(n, v, *Types) 488 489 490def _xnumpy(where, *required): 491 '''(INTERNAL) Import C{numpy} and check required version 492 ''' 493 try: 494 import numpy 495 except ImportError as x: 496 raise _xImportError(x, where) 497 return _xversion(numpy, where, *required) 498 499 500def _xor(x, *xs): 501 '''(INTERNAL) Exclusive-or C{x} and C{xs}. 502 ''' 503 for x_ in xs: 504 x ^= x_ 505 return x 506 507 508def _xscipy(where, *required): 509 '''(INTERNAL) Import C{scipy} and check required version 510 ''' 511 try: 512 import scipy 513 except ImportError as x: 514 raise _xImportError(x, where) 515 return _xversion(scipy, where, *required) 516 517 518def _xsubclassof(Class, **name_value_pairs): 519 '''(INTERNAL) Check super C{Class} of all C{name=value} pairs. 520 521 @arg Class: A class or type (C{class}). 522 @kwarg name_value_pairs: One or more C{B{name}=value} pairs 523 with the C{value} to be checked. 524 525 @raise TypeError: At least one of the B{C{name_value_pairs}} 526 is not a sub-class of B{C{Class}}. 527 ''' 528 for n, v in name_value_pairs.items(): 529 if not issubclassof(v, Class): 530 raise _TypesError(n, v, Class) 531 532 533def _xversion(package, where, *required, **name): # in .karney 534 '''(INTERNAL) Check the C{package} version vs B{C{required}}. 535 ''' 536 n = len(required) 537 if n: 538 t = map2(int, package.__version__.split(_DOT_)) 539 if t[:n] < required: 540 t = _SPACE_(package.__name__, _version_, _DOT_(*t), 541 'below', _DOT_(*required), 542 _required_, _by_, _xwhere(where, **name)) 543 raise ImportError(t) 544 return package 545 546 547def _xwhere(where, **name): 548 '''(INTERNAL) Get the fully qualified name. 549 ''' 550 from pygeodesy.named import modulename 551 m = modulename(where, prefixed=True) 552 n = name.get(_name_, NN) 553 if n: 554 m = _DOT_(m, n) 555 return m 556 557 558# **) MIT License 559# 560# Copyright (C) 2016-2021 -- mrJean1 at Gmail -- All Rights Reserved. 561# 562# Permission is hereby granted, free of charge, to any person obtaining a 563# copy of this software and associated documentation files (the "Software"), 564# to deal in the Software without restriction, including without limitation 565# the rights to use, copy, modify, merge, publish, distribute, sublicense, 566# and/or sell copies of the Software, and to permit persons to whom the 567# Software is furnished to do so, subject to the following conditions: 568# 569# The above copyright notice and this permission notice shall be included 570# in all copies or substantial portions of the Software. 571# 572# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 573# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 574# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 575# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 576# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 577# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 578# OTHER DEALINGS IN THE SOFTWARE. 579