# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function, unicode_literals
import sys
import six
import functools
import re
import types
from utool import util_inject
from utool._internal.meta_util_six import IntType, LongType, FloatType, BooleanType
from utool._internal import meta_util_six
#import warnings
print, rrr, profile = util_inject.inject2(__name__, '[type]')
__STR__ = meta_util_six.__STR__
if six.PY2:
def type_str(type_):
str_ = str(type_)
str_ = str_.replace('<type \'', '').replace('\'>', '')
str_ = str_.replace('<class \'', '').replace('\'>', '')
return str_
else:
[docs] def type_str(type_):
return str(type_).replace('<class \'', '').replace('\'>', '')
# Very odd that I have to put in dtypes in two different ways.
try:
import numpy as np
HAVE_NUMPY = True
NUMPY_SCALAR_NAMES = sorted(list(set(
(str_.replace('numpy.', '')
for str_ in (type_str(type_) for type_ in np.ScalarType)
if str_.startswith('numpy.')
))))
VALID_INT_TYPES = (IntType, LongType,
np.typeDict['int64'],
np.typeDict['int32'],
np.typeDict['uint8'],
np.dtype('int32'),
np.dtype('uint8'),
np.dtype('int64'),)
VALID_FLOAT_TYPES = (FloatType,
np.typeDict['float64'],
np.typeDict['float32'],
np.typeDict['float16'],
np.dtype('float64'),
np.dtype('float32'),
np.dtype('float16'),)
VALID_BOOL_TYPES = (BooleanType, np.bool_)
NP_NDARRAY = np.ndarray
LISTLIKE_TYPES = (tuple, list, NP_NDARRAY)
NUMPY_TYPE_TUPLE = (
tuple([NP_NDARRAY] + list(set(np.typeDict.values()))))
except (ImportError, AttributeError):
# TODO remove numpy
HAVE_NUMPY = False
VALID_INT_TYPES = (IntType, LongType,)
VALID_FLOAT_TYPES = (FloatType,)
VALID_BOOL_TYPES = (BooleanType,)
LISTLIKE_TYPES = (tuple, list)
NP_NDARRAY = None
NUMPY_TYPE_TUPLE = tuple()
PRIMATIVE_TYPES = (
tuple(six.string_types) + (bytes, list, dict, int, float, bool, type(None))
)
[docs]def is_valid_floattype(type_):
"""
Args:
type_ (``type``): type to check
Returns:
bool: if a ``type_`` is a valid float ``type_`` (not variable)
"""
return type_ in VALID_FLOAT_TYPES
#try:
# #flags = [type_ == float_type for float_type in VALID_FLOAT_TYPES]
# #return any(flags)
# tried = []
# for float_type in VALID_FLOAT_TYPES:
# tried.append(float_type)
# if type_ == float_type:
# return True
# return False
#except Exception:
# print('tried=%r' % (tried,))
# print('type_=%r' % (type_,))
# print('float_type=%r' % (float_type,))
[docs]def try_cast(var, type_, default=None):
if type_ is None:
return var
try:
return smart_cast(var, type_)
except Exception:
return default
[docs]def smart_cast(var, type_):
"""
casts var to type, and tries to be clever when var is a string
Args:
var (object): variable to cast
type_ (type or str): type to attempt to cast to
Returns:
object:
CommandLine:
python -m utool.util_type --exec-smart_cast
Example:
>>> # ENABLE_DOCTEST
>>> from utool.util_type import * # NOQA
>>> var = '1'
>>> type_ = 'fuzzy_subset'
>>> result = smart_cast(var, type_)
>>> print(result)
[1]
"""
if is_str(var):
if type_ in VALID_BOOL_TYPES:
return bool_from_str(var)
elif type_ is slice:
args = [None if len(arg) == 0 else int(arg) for arg in var.split(':')]
return slice(*args)
elif type_ is list:
subvar_list = var.split(',')
return [smart_cast2(subvar) for subvar in subvar_list]
elif isinstance(type_, six.string_types):
if type_ == 'fuzzy_subset':
return fuzzy_subset(var)
#elif type_ == 'fuzzy_int':
# return fuzzy_subset(var)
else:
raise NotImplementedError('Uknown smart type_=%r' % (type_,))
return type_(var)
[docs]def smart_cast2(var):
r"""
if the variable is a string tries to cast it to a reasonable value.
maybe can just use eval. FIXME: funcname
Args:
var (unknown):
Returns:
unknown: some var
CommandLine:
python -m utool.util_type --test-smart_cast2
Example:
>>> # ENABLE_DOCTEST
>>> from utool.util_type import * # NOQA
>>> import utool as ut
>>> # build test data
>>> var_list = ['?', 1, '1', '1.0', '1.2', 'True', None, 'None']
>>> # execute function
>>> castvar_list = [smart_cast2(var) for var in var_list]
>>> # verify results
>>> result = ut.list_str(castvar_list, nl=False)
>>> print(result)
['?', 1, 1, 1.0, 1.2, True, None, None]
"""
if var is None:
return None
if isinstance(var, six.string_types):
castvar = None
lower = var.lower()
if lower == 'true':
return True
elif lower == 'false':
return False
elif lower == 'none':
return None
if var.startswith('[') and var.endswith(']'):
#import re
#subvar_list = re.split(r',\s*' + ut.negative_lookahead(r'[^\[\]]*\]'), var[1:-1])
return smart_cast(var[1:-1], list)
elif var.startswith('(') and var.endswith(')'):
#import re
#subvar_list = re.split(r',\s*' + ut.negative_lookahead(r'[^\[\]]*\]'), var[1:-1])
return tuple(smart_cast(var[1:-1], list))
type_list = [int, float]
for type_ in type_list:
castvar = try_cast(var, type_)
if castvar is not None:
break
if castvar is None:
castvar = var
else:
castvar = var
return castvar
[docs]def bool_from_str(str_):
lower = str_.lower()
if lower == 'true':
return True
elif lower == 'false':
return False
else:
raise TypeError('string does not represent boolean')
[docs]def fuzzy_subset(str_):
"""
converts a string into an argument to list_take
"""
if str_ is None:
return str_
if ':' in str_:
return smart_cast(str_, slice)
if str_.startswith('['):
return smart_cast(str_[1:-1], list)
else:
return smart_cast(str_, list)
[docs]def fuzzy_int(str_):
"""
lets some special strings be interpreted as ints
"""
try:
ret = int(str_)
return ret
except Exception:
# Parse comma separated values as ints
if re.match(r'\d*,\d*,?\d*', str_):
return tuple(map(int, str_.split(',')))
# Parse range values as ints
if re.match(r'\d*:\d*:?\d*', str_):
return tuple(range(*map(int, str_.split(':'))))
raise
[docs]def assert_int(var, lbl='var'):
from utool.util_arg import NO_ASSERTS
if NO_ASSERTS:
return
try:
assert is_int(var), 'type(%s)=%r is not int' % (lbl, get_type(var))
except AssertionError:
print('[tools] %s = %r' % (lbl, var))
print('[tools] VALID_INT_TYPES: %r' % VALID_INT_TYPES)
raise
if HAVE_NUMPY:
if sys.platform == 'win32':
# Well this is a weird system specific error
# https://github.com/numpy/numpy/issues/3667
def get_type(var):
"""Gets types accounting for numpy"""
return var.dtype if isinstance(var, NP_NDARRAY) else type(var)
else:
def get_type(var):
"""Gets types accounting for numpy"""
return var.dtype.type if isinstance(var, NP_NDARRAY) else type(var)
else:
[docs] def get_type(var):
return type(var)
[docs]def is_type(var, valid_types):
""" Checks for types accounting for numpy """
#printDBG('checking type var=%r' % (var,))
#var_type = type(var)
#printDBG('type is type(var)=%r' % (var_type,))
#printDBG('must be in valid_types=%r' % (valid_types,))
#ret = var_type in valid_types
#printDBG('result is %r ' % ret)
return get_type(var) in valid_types
[docs]def is_int(var):
"""
Returns:
bool: True if var is an integer.
Note:
Yuck, isinstance(True, int) returns True. This function does not have
that flaw.
References:
http://www.peterbe.com/plog/bool-is-int
CommandLine:
python -m utool.util_type --test-is_int
Example:
>>> # ENABLE_DOCTEST
>>> from utool.util_type import * # NOQA
>>> var1 = 1
>>> var2 = np.array([1, 2, 3])
>>> var3 = True
>>> var4 = np.array([True, True, False])
>>> result = [is_int(var) for var in [var1, var2, var3, var4]]
>>> print(result)
[True, True, False, False]
"""
#if _newbehavior:
# if is_bool(var):
# msg = 'Comparing bool to int. Make sure legacy code does is updated accordingly.'
# print('Warning: ' + msg)
# warnings.warn(msg)
# return False
# else:
# return is_type(var, VALID_INT_TYPES)
#else:
return is_type(var, VALID_INT_TYPES)
[docs]def is_float(var):
r"""
Args:
var (ndarray or scalar):
Returns:
var:
CommandLine:
python -m utool.util_type --test-is_float
Example:
>>> # DISABLE_DOCTEST
>>> from utool.util_type import * # NOQA
>>> # build test data
>>> var = np.array([1.0, 2.0, 3.0])
>>> # execute function
>>> assert is_float(var) is True, 'var is a float'
>>> # verify results
>>> print(result)
"""
return is_type(var, VALID_FLOAT_TYPES)
[docs]def is_str(var):
return isinstance(var, six.string_types)
#return is_type(var, VALID_STRING_TYPES)
[docs]def is_bool(var):
return isinstance(var, VALID_BOOL_TYPES)
[docs]def is_dict(var):
return isinstance(var, dict)
[docs]def is_list(var):
return isinstance(var, list)
[docs]def is_listlike(var):
return isinstance(var, LISTLIKE_TYPES)
[docs]def is_tuple(var):
return isinstance(var, tuple)
[docs]def is_method(var):
return isinstance(var, (types.MethodType,))
[docs]def is_func_or_method(var):
return isinstance(var, (types.MethodType, types.FunctionType))
[docs]def is_func_or_method_or_partial(var):
return isinstance(var, (types.MethodType, types.FunctionType,
functools.partial))
[docs]def is_funclike(var):
return hasattr(var, '__call__')
#def get_list_type(list_):
# if isinstance(list_, np.ndarray):
# return list_.dtype
# pass
[docs]def get_homogenous_list_type(list_):
"""
Returns the best matching python type even if it is an ndarray assumes all
items in the list are of the same type. does not check this
"""
# TODO Expand and make work correctly
if HAVE_NUMPY and isinstance(list_, np.ndarray):
item = list_
elif isinstance(list_, list) and len(list_) > 0:
item = list_[0]
else:
item = None
if item is not None:
if is_float(item):
type_ = float
elif is_int(item):
type_ = int
elif is_bool(item):
type_ = bool
elif is_str(item):
type_ = str
else:
type_ = get_type(item)
else:
type_ = None
return type_
if __name__ == '__main__':
"""
CommandLine:
python -m utool.util_type
python -m utool.util_type --allexamples
python -m utool.util_type --allexamples --noface --nosrc
"""
import multiprocessing
multiprocessing.freeze_support() # for win32
import utool as ut # NOQA
ut.doctest_funcs()