"""Implements operations on bytes and str (unicode) array items.""" import operator import numpy as np from llvmlite import ir from numba.core import types, cgutils from numba.core.extending import (overload, intrinsic, overload_method, lower_cast, register_jitable) from numba.core.cgutils import is_nonelike from numba.cpython import unicode # bytes and str arrays items are of type CharSeq and UnicodeCharSeq, # respectively. See numpy/types/npytypes.py for CharSeq, # UnicodeCharSeq definitions. The corresponding data models are # defined in numpy/datamodel/models.py. Boxing/unboxing of item types # are defined in numpy/targets/boxing.py, see box_unicodecharseq, # unbox_unicodecharseq, box_charseq, unbox_charseq. s1_dtype = np.dtype('S1') assert s1_dtype.itemsize == 1 bytes_type = types.Bytes(types.uint8, 1, "C", readonly=True) # Currently, NumPy supports only UTF-32 arrays but this may change in # future and the approach used here for supporting str arrays may need # a revision depending on how NumPy will support UTF-8 and UTF-16 # arrays. u1_dtype = np.dtype('U1') unicode_byte_width = u1_dtype.itemsize unicode_uint = {1: np.uint8, 2: np.uint16, 4: np.uint32}[unicode_byte_width] unicode_kind = {1: unicode.PY_UNICODE_1BYTE_KIND, 2: unicode.PY_UNICODE_2BYTE_KIND, 4: unicode.PY_UNICODE_4BYTE_KIND}[unicode_byte_width] # this is modified version of numba.unicode.make_deref_codegen def make_deref_codegen(bitsize): def codegen(context, builder, signature, args): data, idx = args rawptr = cgutils.alloca_once_value(builder, value=data) ptr = builder.bitcast(rawptr, ir.IntType(bitsize).as_pointer()) ch = builder.load(builder.gep(ptr, [idx])) return builder.zext(ch, ir.IntType(32)) return codegen @intrinsic def deref_uint8(typingctx, data, offset): sig = types.uint32(data, types.intp) return sig, make_deref_codegen(8) @intrinsic def deref_uint16(typingctx, data, offset): sig = types.uint32(data, types.intp) return sig, make_deref_codegen(16) @intrinsic def deref_uint32(typingctx, data, offset): sig = types.uint32(data, types.intp) return sig, make_deref_codegen(32) @register_jitable(_nrt=False) def charseq_get_code(a, i): """Access i-th item of CharSeq object via code value """ return deref_uint8(a, i) @register_jitable def charseq_get_value(a, i): """Access i-th item of CharSeq object via code value. null code is interpreted as IndexError """ code = charseq_get_code(a, i) if code == 0: raise IndexError('index out of range') return code @register_jitable(_nrt=False) def unicode_charseq_get_code(a, i): """Access i-th item of UnicodeCharSeq object via code value """ if unicode_byte_width == 4: return deref_uint32(a, i) elif unicode_byte_width == 2: return deref_uint16(a, i) elif unicode_byte_width == 1: return deref_uint8(a, i) else: raise NotImplementedError( 'unicode_charseq_get_code: unicode_byte_width not in [1, 2, 4]') @register_jitable def unicode_get_code(a, i): """Access i-th item of UnicodeType object. """ return unicode._get_code_point(a, i) @register_jitable def bytes_get_code(a, i): """Access i-th item of Bytes object. """ return a[i] def _get_code_impl(a): if isinstance(a, types.CharSeq): return charseq_get_code elif isinstance(a, types.Bytes): return bytes_get_code elif isinstance(a, types.UnicodeCharSeq): return unicode_charseq_get_code elif isinstance(a, types.UnicodeType): return unicode_get_code def _same_kind(a, b): for t in [(types.CharSeq, types.Bytes), (types.UnicodeCharSeq, types.UnicodeType)]: if isinstance(a, t) and isinstance(b, t): return True return False def _is_bytes(a): return isinstance(a, (types.CharSeq, types.Bytes)) def is_default(x, default): return x == default or isinstance(x, types.Omitted) @register_jitable def unicode_charseq_get_value(a, i): """Access i-th item of UnicodeCharSeq object via unicode value null code is interpreted as IndexError """ code = unicode_charseq_get_code(a, i) if code == 0: raise IndexError('index out of range') # Return numpy equivalent of `chr(code)` return np.array(code, unicode_uint).view(u1_dtype)[()] # # CAST # # Currently, the following casting operations are supported: # Bytes -> CharSeq (ex: a=np.array(b'abc'); a[()] = b'123') # UnicodeType -> UnicodeCharSeq (ex: a=np.array('abc'); a[()] = '123') # CharSeq -> Bytes (ex: a=np.array(b'abc'); b = bytes(a[()])) # UnicodeType -> Bytes (ex: str('123')._to_bytes()) # # The following casting operations can be implemented when required: # Bytes -> UnicodeCharSeq (ex: a=np.array('abc'); a[()] = b'123') # UnicodeType -> CharSeq (ex: a=np.array(b'abc'); a[()] = '123') # UnicodeType -> Bytes (ex: bytes('123', 'utf8')) # @lower_cast(types.Bytes, types.CharSeq) def bytes_to_charseq(context, builder, fromty, toty, val): barr = cgutils.create_struct_proxy(fromty)(context, builder, value=val) src = builder.bitcast(barr.data, ir.IntType(8).as_pointer()) src_length = barr.nitems lty = context.get_value_type(toty) dstint_t = ir.IntType(8) dst_ptr = cgutils.alloca_once(builder, lty) dst = builder.bitcast(dst_ptr, dstint_t.as_pointer()) dst_length = ir.Constant(src_length.type, toty.count) is_shorter_value = builder.icmp_unsigned('<', src_length, dst_length) count = builder.select(is_shorter_value, src_length, dst_length) with builder.if_then(is_shorter_value): cgutils.memset(builder, dst, ir.Constant(src_length.type, toty.count), 0) with cgutils.for_range(builder, count) as loop: in_ptr = builder.gep(src, [loop.index]) in_val = builder.zext(builder.load(in_ptr), dstint_t) builder.store(in_val, builder.gep(dst, [loop.index])) return builder.load(dst_ptr) def _make_constant_bytes(context, builder, nbytes): bstr_ctor = cgutils.create_struct_proxy(bytes_type) bstr = bstr_ctor(context, builder) if isinstance(nbytes, int): nbytes = ir.Constant(bstr.nitems.type, nbytes) bstr.meminfo = context.nrt.meminfo_alloc(builder, nbytes) bstr.nitems = nbytes bstr.itemsize = ir.Constant(bstr.itemsize.type, 1) bstr.data = context.nrt.meminfo_data(builder, bstr.meminfo) bstr.parent = cgutils.get_null_value(bstr.parent.type) # bstr.shape and bstr.strides are not used bstr.shape = cgutils.get_null_value(bstr.shape.type) bstr.strides = cgutils.get_null_value(bstr.strides.type) return bstr @lower_cast(types.CharSeq, types.Bytes) def charseq_to_bytes(context, builder, fromty, toty, val): bstr = _make_constant_bytes(context, builder, val.type.count) rawptr = cgutils.alloca_once_value(builder, value=val) ptr = builder.bitcast(rawptr, bstr.data.type) cgutils.memcpy(builder, bstr.data, ptr, bstr.nitems) return bstr @lower_cast(types.UnicodeType, types.Bytes) def unicode_to_bytes_cast(context, builder, fromty, toty, val): uni_str = cgutils.create_struct_proxy(fromty)(context, builder, value=val) src1 = builder.bitcast(uni_str.data, ir.IntType(8).as_pointer()) notkind1 = builder.icmp_unsigned('!=', uni_str.kind, ir.Constant(uni_str.kind.type, 1)) src_length = uni_str.length with builder.if_then(notkind1): context.call_conv.return_user_exc( builder, ValueError, ("cannot cast higher than 8-bit unicode_type to bytes",)) bstr = _make_constant_bytes(context, builder, src_length) cgutils.memcpy(builder, bstr.data, src1, bstr.nitems) return bstr @intrinsic def _unicode_to_bytes(typingctx, s): # used in _to_bytes method assert s == types.unicode_type sig = bytes_type(s) def codegen(context, builder, signature, args): return unicode_to_bytes_cast( context, builder, s, bytes_type, args[0])._getvalue() return sig, codegen @lower_cast(types.UnicodeType, types.UnicodeCharSeq) def unicode_to_unicode_charseq(context, builder, fromty, toty, val): uni_str = cgutils.create_struct_proxy(fromty)(context, builder, value=val) src1 = builder.bitcast(uni_str.data, ir.IntType(8).as_pointer()) src2 = builder.bitcast(uni_str.data, ir.IntType(16).as_pointer()) src4 = builder.bitcast(uni_str.data, ir.IntType(32).as_pointer()) kind1 = builder.icmp_unsigned('==', uni_str.kind, ir.Constant(uni_str.kind.type, 1)) kind2 = builder.icmp_unsigned('==', uni_str.kind, ir.Constant(uni_str.kind.type, 2)) kind4 = builder.icmp_unsigned('==', uni_str.kind, ir.Constant(uni_str.kind.type, 4)) src_length = uni_str.length lty = context.get_value_type(toty) dstint_t = ir.IntType(8 * unicode_byte_width) dst_ptr = cgutils.alloca_once(builder, lty) dst = builder.bitcast(dst_ptr, dstint_t.as_pointer()) dst_length = ir.Constant(src_length.type, toty.count) is_shorter_value = builder.icmp_unsigned('<', src_length, dst_length) count = builder.select(is_shorter_value, src_length, dst_length) with builder.if_then(is_shorter_value): cgutils.memset(builder, dst, ir.Constant(src_length.type, toty.count * unicode_byte_width), 0) with builder.if_then(kind1): with cgutils.for_range(builder, count) as loop: in_ptr = builder.gep(src1, [loop.index]) in_val = builder.zext(builder.load(in_ptr), dstint_t) builder.store(in_val, builder.gep(dst, [loop.index])) with builder.if_then(kind2): if unicode_byte_width >= 2: with cgutils.for_range(builder, count) as loop: in_ptr = builder.gep(src2, [loop.index]) in_val = builder.zext(builder.load(in_ptr), dstint_t) builder.store(in_val, builder.gep(dst, [loop.index])) else: context.call_conv.return_user_exc( builder, ValueError, ("cannot cast 16-bit unicode_type to %s-bit %s" % (unicode_byte_width * 8, toty))) with builder.if_then(kind4): if unicode_byte_width >= 4: with cgutils.for_range(builder, count) as loop: in_ptr = builder.gep(src4, [loop.index]) in_val = builder.zext(builder.load(in_ptr), dstint_t) builder.store(in_val, builder.gep(dst, [loop.index])) else: context.call_conv.return_user_exc( builder, ValueError, ("cannot cast 32-bit unicode_type to %s-bit %s" % (unicode_byte_width * 8, toty))) return builder.load(dst_ptr) # # Operations on bytes/str array items # # Implementation note: while some operations need # CharSeq/UnicodeCharSeq specific implementations (getitem, len, str, # etc), many operations can be supported by casting # CharSeq/UnicodeCharSeq objects to Bytes/UnicodeType objects and # re-use existing operations. # # However, in numba more operations are implemented for UnicodeType # than for Bytes objects, hence the support for operations with bytes # array items will be less complete than for str arrays. Although, in # some cases (hash, contains, etc) the UnicodeType implementations can # be reused for Bytes objects via using `_to_str` method. # @overload(operator.getitem) def charseq_getitem(s, i): get_value = None if isinstance(i, types.Integer): if isinstance(s, types.CharSeq): get_value = charseq_get_value if isinstance(s, types.UnicodeCharSeq): get_value = unicode_charseq_get_value if get_value is not None: max_i = s.count msg = 'index out of range [0, %s]' % (max_i - 1) def getitem_impl(s, i): if i < max_i and i >= 0: return get_value(s, i) raise IndexError(msg) return getitem_impl @overload(len) def charseq_len(s): if isinstance(s, (types.CharSeq, types.UnicodeCharSeq)): get_code = _get_code_impl(s) n = s.count if n == 0: def len_impl(s): return 0 return len_impl else: def len_impl(s): # return the index of the last non-null value (numpy # behavior) i = n code = 0 while code == 0: i = i - 1 if i < 0: break code = get_code(s, i) return i + 1 return len_impl @overload(operator.add) @overload(operator.iadd) def charseq_concat(a, b): if not _same_kind(a, b): return if (isinstance(a, types.UnicodeCharSeq) and isinstance(b, types.UnicodeType)): def impl(a, b): return str(a) + b return impl if (isinstance(b, types.UnicodeCharSeq) and isinstance(a, types.UnicodeType)): def impl(a, b): return a + str(b) return impl if (isinstance(a, types.UnicodeCharSeq) and isinstance(b, types.UnicodeCharSeq)): def impl(a, b): return str(a) + str(b) return impl if (isinstance(a, (types.CharSeq, types.Bytes)) and isinstance(b, (types.CharSeq, types.Bytes))): def impl(a, b): return (a._to_str() + b._to_str())._to_bytes() return impl @overload(operator.mul) def charseq_repeat(a, b): if isinstance(a, types.UnicodeCharSeq): def wrap(a, b): return str(a) * b return wrap if isinstance(b, types.UnicodeCharSeq): def wrap(a, b): return a * str(b) return wrap if isinstance(a, (types.CharSeq, types.Bytes)): def wrap(a, b): return (a._to_str() * b)._to_bytes() return wrap if isinstance(b, (types.CharSeq, types.Bytes)): def wrap(a, b): return (a * b._to_str())._to_bytes() return wrap @overload(operator.not_) def charseq_not(a): if isinstance(a, (types.UnicodeCharSeq, types.CharSeq, types.Bytes)): def impl(a): return len(a) == 0 return impl @overload(operator.eq) def charseq_eq(a, b): if not _same_kind(a, b): return left_code = _get_code_impl(a) right_code = _get_code_impl(b) if left_code is not None and right_code is not None: def eq_impl(a, b): n = len(a) if n != len(b): return False for i in range(n): if left_code(a, i) != right_code(b, i): return False return True return eq_impl @overload(operator.ne) def charseq_ne(a, b): if not _same_kind(a, b): return left_code = _get_code_impl(a) right_code = _get_code_impl(b) if left_code is not None and right_code is not None: def ne_impl(a, b): return not (a == b) return ne_impl @overload(operator.lt) def charseq_lt(a, b): if not _same_kind(a, b): return left_code = _get_code_impl(a) right_code = _get_code_impl(b) if left_code is not None and right_code is not None: def lt_impl(a, b): na = len(a) nb = len(b) n = min(na, nb) for i in range(n): ca, cb = left_code(a, i), right_code(b, i) if ca != cb: return ca < cb return na < nb return lt_impl @overload(operator.gt) def charseq_gt(a, b): if not _same_kind(a, b): return left_code = _get_code_impl(a) right_code = _get_code_impl(b) if left_code is not None and right_code is not None: def gt_impl(a, b): return b < a return gt_impl @overload(operator.le) def charseq_le(a, b): if not _same_kind(a, b): return left_code = _get_code_impl(a) right_code = _get_code_impl(b) if left_code is not None and right_code is not None: def le_impl(a, b): return not (a > b) return le_impl @overload(operator.ge) def charseq_ge(a, b): if not _same_kind(a, b): return left_code = _get_code_impl(a) right_code = _get_code_impl(b) if left_code is not None and right_code is not None: def ge_impl(a, b): return not (a < b) return ge_impl @overload(operator.contains) def charseq_contains(a, b): if not _same_kind(a, b): return left_code = _get_code_impl(a) right_code = _get_code_impl(b) if left_code is not None and right_code is not None: if _is_bytes(a): def contains_impl(a, b): # Ideally, `return bytes(b) in bytes(a)` would be used # here, but numba Bytes does not implement # contains. So, using `unicode_type` implementation # here: return b._to_str() in a._to_str() else: def contains_impl(a, b): return str(b) in str(a) return contains_impl @overload_method(types.UnicodeCharSeq, 'isascii') @overload_method(types.CharSeq, 'isascii') @overload_method(types.Bytes, 'isascii') def charseq_isascii(s): get_code = _get_code_impl(s) def impl(s): for i in range(len(s)): if get_code(s, i) > 127: return False return True return impl @overload_method(types.UnicodeCharSeq, '_get_kind') @overload_method(types.CharSeq, '_get_kind') def charseq_get_kind(s): get_code = _get_code_impl(s) def impl(s): max_code = 0 for i in range(len(s)): code = get_code(s, i) if code > max_code: max_code = code if max_code > 0xffff: return unicode.PY_UNICODE_4BYTE_KIND if max_code > 0xff: return unicode.PY_UNICODE_2BYTE_KIND return unicode.PY_UNICODE_1BYTE_KIND return impl @overload_method(types.UnicodeType, '_to_bytes') def unicode_to_bytes_mth(s): """Convert unicode_type object to Bytes object. Note: The usage of _to_bytes method can be eliminated once all Python bytes operations are implemented for numba Bytes objects. """ def impl(s): return _unicode_to_bytes(s) return impl @overload_method(types.CharSeq, '_to_str') @overload_method(types.Bytes, '_to_str') def charseq_to_str_mth(s): """Convert bytes array item or bytes instance to UTF-8 str. Note: The usage of _to_str method can be eliminated once all Python bytes operations are implemented for numba Bytes objects. """ get_code = _get_code_impl(s) def tostr_impl(s): n = len(s) is_ascii = s.isascii() result = unicode._empty_string( unicode.PY_UNICODE_1BYTE_KIND, n, is_ascii) for i in range(n): code = get_code(s, i) unicode._set_code_point(result, i, code) return result return tostr_impl @overload_method(types.UnicodeCharSeq, "__str__") def charseq_str(s): get_code = _get_code_impl(s) def str_impl(s): n = len(s) kind = s._get_kind() is_ascii = kind == 1 and s.isascii() result = unicode._empty_string(kind, n, is_ascii) for i in range(n): code = get_code(s, i) unicode._set_code_point(result, i, code) return result return str_impl @overload(bytes) def charseq_bytes(s): if isinstance(s, types.CharSeq): return lambda s: s @overload_method(types.UnicodeCharSeq, '__hash__') def unicode_charseq_hash(s): def impl(s): return hash(str(s)) return impl @overload_method(types.CharSeq, '__hash__') def charseq_hash(s): def impl(s): # Ideally, `return hash(bytes(s))` would be used here but # numba Bytes does not implement hash (yet). However, for a # UTF-8 string `s`, we have hash(bytes(s)) == hash(s), hence, # we can convert CharSeq object to unicode_type and reuse its # hash implementation: return hash(s._to_str()) return impl @overload_method(types.UnicodeCharSeq, 'isupper') def unicode_charseq_isupper(s): def impl(s): # workaround unicode_type.isupper bug: it returns int value return not not str(s).isupper() return impl @overload_method(types.CharSeq, 'isupper') def charseq_isupper(s): def impl(s): # return bytes(s).isupper() # TODO: implement isupper for Bytes return not not s._to_str().isupper() return impl @overload_method(types.UnicodeCharSeq, 'upper') def unicode_charseq_upper(s): def impl(s): return str(s).upper() return impl @overload_method(types.CharSeq, 'upper') def charseq_upper(s): def impl(s): # return bytes(s).upper() # TODO: implement upper for Bytes return s._to_str().upper()._to_bytes() return impl @overload_method(types.UnicodeCharSeq, 'find') @overload_method(types.CharSeq, 'find') @overload_method(types.Bytes, 'find') def unicode_charseq_find(a, b): if isinstance(a, types.UnicodeCharSeq): if isinstance(b, types.UnicodeCharSeq): def impl(a, b): return str(a).find(str(b)) return impl if isinstance(b, types.UnicodeType): def impl(a, b): return str(a).find(b) return impl if isinstance(a, types.CharSeq): if isinstance(b, (types.CharSeq, types.Bytes)): def impl(a, b): return a._to_str().find(b._to_str()) return impl if isinstance(a, types.UnicodeType): if isinstance(b, types.UnicodeCharSeq): def impl(a, b): return a.find(str(b)) return impl if isinstance(a, types.Bytes): if isinstance(b, types.CharSeq): def impl(a, b): return a._to_str().find(b._to_str()) return impl @overload_method(types.UnicodeCharSeq, 'rfind') @overload_method(types.CharSeq, 'rfind') @overload_method(types.Bytes, 'rfind') def unicode_charseq_rfind(a, b): if isinstance(a, types.UnicodeCharSeq): if isinstance(b, types.UnicodeCharSeq): def impl(a, b): return str(a).rfind(str(b)) return impl if isinstance(b, types.UnicodeType): def impl(a, b): return str(a).rfind(b) return impl if isinstance(a, types.CharSeq): if isinstance(b, (types.CharSeq, types.Bytes)): def impl(a, b): return a._to_str().rfind(b._to_str()) return impl if isinstance(a, types.UnicodeType): if isinstance(b, types.UnicodeCharSeq): def impl(a, b): return a.rfind(str(b)) return impl if isinstance(a, types.Bytes): if isinstance(b, types.CharSeq): def impl(a, b): return a._to_str().rfind(b._to_str()) return impl @overload_method(types.UnicodeCharSeq, 'startswith') @overload_method(types.CharSeq, 'startswith') @overload_method(types.Bytes, 'startswith') def unicode_charseq_startswith(a, b): if isinstance(a, types.UnicodeCharSeq): if isinstance(b, types.UnicodeCharSeq): def impl(a, b): return str(a).startswith(str(b)) return impl if isinstance(b, types.UnicodeType): def impl(a, b): return str(a).startswith(b) return impl if isinstance(a, (types.CharSeq, types.Bytes)): if isinstance(b, (types.CharSeq, types.Bytes)): def impl(a, b): return a._to_str().startswith(b._to_str()) return impl @overload_method(types.UnicodeCharSeq, 'endswith') @overload_method(types.CharSeq, 'endswith') @overload_method(types.Bytes, 'endswith') def unicode_charseq_endswith(a, b): if isinstance(a, types.UnicodeCharSeq): if isinstance(b, types.UnicodeCharSeq): def impl(a, b): return str(a).endswith(str(b)) return impl if isinstance(b, types.UnicodeType): def impl(a, b): return str(a).endswith(b) return impl if isinstance(a, (types.CharSeq, types.Bytes)): if isinstance(b, (types.CharSeq, types.Bytes)): def impl(a, b): return a._to_str().endswith(b._to_str()) return impl @register_jitable def _map_bytes(seq): return [s._to_bytes() for s in seq] @overload_method(types.UnicodeCharSeq, 'split') @overload_method(types.CharSeq, 'split') @overload_method(types.Bytes, 'split') def unicode_charseq_split(a, sep=None, maxsplit=-1): if not (maxsplit == -1 or isinstance(maxsplit, (types.Omitted, types.Integer, types.IntegerLiteral))): return None if isinstance(a, types.UnicodeCharSeq): if isinstance(sep, types.UnicodeCharSeq): def impl(a, sep=None, maxsplit=-1): return str(a).split(sep=str(sep), maxsplit=maxsplit) return impl if isinstance(sep, types.UnicodeType): def impl(a, sep=None, maxsplit=-1): return str(a).split(sep=sep, maxsplit=maxsplit) return impl if is_nonelike(sep): if is_default(maxsplit, -1): def impl(a, sep=None, maxsplit=-1): return str(a).split() else: def impl(a, sep=None, maxsplit=-1): return str(a).split(maxsplit=maxsplit) return impl if isinstance(a, (types.CharSeq, types.Bytes)): if isinstance(sep, (types.CharSeq, types.Bytes)): def impl(a, sep=None, maxsplit=-1): return _map_bytes(a._to_str().split(sep._to_str(), maxsplit=maxsplit)) return impl if is_nonelike(sep): if is_default(maxsplit, -1): def impl(a, sep=None, maxsplit=-1): return _map_bytes(a._to_str().split()) else: def impl(a, sep=None, maxsplit=-1): return _map_bytes(a._to_str().split(maxsplit=maxsplit)) return impl # NOT IMPLEMENTED: rsplit @overload_method(types.UnicodeCharSeq, 'ljust') @overload_method(types.CharSeq, 'ljust') @overload_method(types.Bytes, 'ljust') def unicode_charseq_ljust(a, width, fillchar=' '): if isinstance(a, types.UnicodeCharSeq): if is_default(fillchar, ' '): def impl(a, width, fillchar=' '): return str(a).ljust(width) return impl elif isinstance(fillchar, types.UnicodeCharSeq): def impl(a, width, fillchar=' '): return str(a).ljust(width, str(fillchar)) return impl elif isinstance(fillchar, types.UnicodeType): def impl(a, width, fillchar=' '): return str(a).ljust(width, fillchar) return impl if isinstance(a, (types.CharSeq, types.Bytes)): if is_default(fillchar, ' ') or is_default(fillchar, b' '): def impl(a, width, fillchar=' '): return a._to_str().ljust(width)._to_bytes() return impl elif isinstance(fillchar, (types.CharSeq, types.Bytes)): def impl(a, width, fillchar=' '): return a._to_str().ljust(width, fillchar._to_str())._to_bytes() return impl @overload_method(types.UnicodeCharSeq, 'rjust') @overload_method(types.CharSeq, 'rjust') @overload_method(types.Bytes, 'rjust') def unicode_charseq_rjust(a, width, fillchar=' '): if isinstance(a, types.UnicodeCharSeq): if is_default(fillchar, ' '): def impl(a, width, fillchar=' '): return str(a).rjust(width) return impl elif isinstance(fillchar, types.UnicodeCharSeq): def impl(a, width, fillchar=' '): return str(a).rjust(width, str(fillchar)) return impl elif isinstance(fillchar, types.UnicodeType): def impl(a, width, fillchar=' '): return str(a).rjust(width, fillchar) return impl if isinstance(a, (types.CharSeq, types.Bytes)): if is_default(fillchar, ' ') or is_default(fillchar, b' '): def impl(a, width, fillchar=' '): return a._to_str().rjust(width)._to_bytes() return impl elif isinstance(fillchar, (types.CharSeq, types.Bytes)): def impl(a, width, fillchar=' '): return a._to_str().rjust(width, fillchar._to_str())._to_bytes() return impl @overload_method(types.UnicodeCharSeq, 'center') @overload_method(types.CharSeq, 'center') @overload_method(types.Bytes, 'center') def unicode_charseq_center(a, width, fillchar=' '): if isinstance(a, types.UnicodeCharSeq): if is_default(fillchar, ' '): def impl(a, width, fillchar=' '): return str(a).center(width) return impl elif isinstance(fillchar, types.UnicodeCharSeq): def impl(a, width, fillchar=' '): return str(a).center(width, str(fillchar)) return impl elif isinstance(fillchar, types.UnicodeType): def impl(a, width, fillchar=' '): return str(a).center(width, fillchar) return impl if isinstance(a, (types.CharSeq, types.Bytes)): if is_default(fillchar, ' ') or is_default(fillchar, b' '): def impl(a, width, fillchar=' '): return a._to_str().center(width)._to_bytes() return impl elif isinstance(fillchar, (types.CharSeq, types.Bytes)): def impl(a, width, fillchar=' '): return a._to_str().center(width, fillchar._to_str())._to_bytes() return impl @overload_method(types.UnicodeCharSeq, 'zfill') @overload_method(types.CharSeq, 'zfill') @overload_method(types.Bytes, 'zfill') def unicode_charseq_zfill(a, width): if isinstance(a, types.UnicodeCharSeq): def impl(a, width): return str(a).zfill(width) return impl if isinstance(a, (types.CharSeq, types.Bytes)): def impl(a, width): return a._to_str().zfill(width)._to_bytes() return impl @overload_method(types.UnicodeCharSeq, 'lstrip') @overload_method(types.CharSeq, 'lstrip') @overload_method(types.Bytes, 'lstrip') def unicode_charseq_lstrip(a, chars=None): if isinstance(a, types.UnicodeCharSeq): if is_nonelike(chars): def impl(a, chars=None): return str(a).lstrip() return impl elif isinstance(chars, types.UnicodeCharSeq): def impl(a, chars=None): return str(a).lstrip(str(chars)) return impl elif isinstance(chars, types.UnicodeType): def impl(a, chars=None): return str(a).lstrip(chars) return impl if isinstance(a, (types.CharSeq, types.Bytes)): if is_nonelike(chars): def impl(a, chars=None): return a._to_str().lstrip()._to_bytes() return impl elif isinstance(chars, (types.CharSeq, types.Bytes)): def impl(a, chars=None): return a._to_str().lstrip(chars._to_str())._to_bytes() return impl @overload_method(types.UnicodeCharSeq, 'rstrip') @overload_method(types.CharSeq, 'rstrip') @overload_method(types.Bytes, 'rstrip') def unicode_charseq_rstrip(a, chars=None): if isinstance(a, types.UnicodeCharSeq): if is_nonelike(chars): def impl(a, chars=None): return str(a).rstrip() return impl elif isinstance(chars, types.UnicodeCharSeq): def impl(a, chars=None): return str(a).rstrip(str(chars)) return impl elif isinstance(chars, types.UnicodeType): def impl(a, chars=None): return str(a).rstrip(chars) return impl if isinstance(a, (types.CharSeq, types.Bytes)): if is_nonelike(chars): def impl(a, chars=None): return a._to_str().rstrip()._to_bytes() return impl elif isinstance(chars, (types.CharSeq, types.Bytes)): def impl(a, chars=None): return a._to_str().rstrip(chars._to_str())._to_bytes() return impl @overload_method(types.UnicodeCharSeq, 'strip') @overload_method(types.CharSeq, 'strip') @overload_method(types.Bytes, 'strip') def unicode_charseq_strip(a, chars=None): if isinstance(a, types.UnicodeCharSeq): if is_nonelike(chars): def impl(a, chars=None): return str(a).strip() return impl elif isinstance(chars, types.UnicodeCharSeq): def impl(a, chars=None): return str(a).strip(str(chars)) return impl elif isinstance(chars, types.UnicodeType): def impl(a, chars=None): return str(a).strip(chars) return impl if isinstance(a, (types.CharSeq, types.Bytes)): if is_nonelike(chars): def impl(a, chars=None): return a._to_str().strip()._to_bytes() return impl elif isinstance(chars, (types.CharSeq, types.Bytes)): def impl(a, chars=None): return a._to_str().strip(chars._to_str())._to_bytes() return impl @overload_method(types.UnicodeCharSeq, 'join') @overload_method(types.CharSeq, 'join') @overload_method(types.Bytes, 'join') def unicode_charseq_join(a, parts): if isinstance(a, types.UnicodeCharSeq): # assuming parts contains UnicodeCharSeq or UnicodeType objects def impl(a, parts): _parts = [str(p) for p in parts] return str(a).join(_parts) return impl if isinstance(a, (types.CharSeq, types.Bytes)): # assuming parts contains CharSeq or Bytes objects def impl(a, parts): _parts = [p._to_str() for p in parts] return a._to_str().join(_parts)._to_bytes() return impl