Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
The istr
module makes it possible to use strings as if they were integers.
This can be very handy for solving puzzles, but also for other purposes. For instance the famous send more money puzzle, where each letter has to be replaced by a unique digit (0-9)
S E N D
M O R E
--------- +
M O N E Y
can be nicely, albeit not very efficient, coded as:
import itertools
import istr
for s, e, n, d, m, o, r, y in istr(itertools.permutations(range(10), 8)):
if m and ((s|e|n|d) + (m|o|r|e) == (m|o|n|e|y)):
print(f' {s|e|n|d}')
print(f' {m|o|r|e}')
print('-----')
print(f'{m|o|n|e|y}')
Of, if we want to add all the digits in a string:
sum_digits = sum(istr('9282334')) # answer 31
And the module is a demonstration of extending a class (str) with extra and changed functionality.
Installing istr with pip is easy.
pip install istr-python
or when you want to upgrade,
pip install istr-python --upgrade
Alternatively, istr.py can be just copied into you current work directory from GitHub (https://github.com/salabim/istr).
No dependencies!
Just start with
import istr
or the more conventional, more verbose:
from istr import istr
We can define an istr:
four = istr('4')
five = istr('5')
The variables four
and five
can now be used as if they were int:
twenty = four * five
, after which x is istr('20')
The same can be done with
twenty = 4 * five
or
twenty = four * 5
And now twenty
can be used as if it was an int as well. So
twenty - four
is istr('16')
We can do all the usual arithmetic operations on istrs, e.g.
-four + (twenty / 2)
is istr('6')
And we can test for equality. So:
twenty == 20
is True.
But istrs are also strings. So
twenty == '20'
is also True!
For the order comparisons (<=, <, >, >=), the istr is always interpreted as an int.
That means that
twenty < 30
twenty >= '10' # here '10' is converted to the integer 10 for the comparison
are bothTrue
.
In contrast to an ordinary string
print(four + five)
prints 9
, as istr are treated as ints (if possible).
Please note that four
could have also been initialized with
four = istr(4)
or even
four, five = istr(4, 5)
All calculations are strictly integer calculations. That means that if a float or decimal variable is ever produced it will be converted to an int. Also divisions are always floor divisions!
We should realize that istrs are in fact strings.
In order to concatenate two istrs (or an istr and a str), we cannot use the +
operator (remember four + five
is istr('9')
).
In order to concatenate istrs, we use the or operator (|
). So
four | five
will be istr(
45`).
And
(four | five) / 3
is istr('9')
.
In order to repeat a string in the usual sense, you cannot use the *
operator (remember 3 * four
is istr('12')
.
In order to repeat we use the matrix multiplication operator (@
). So
3 @ four
is istr('444')
And
four @ 3
is also istr('444')
It is not allowed to use the
@
operator for two istrs. So,four @ five
raises a TypeError.
Although usualy istrs are to be interpreted as an int, that's not a requirement.
So
istr('abc')
or
istr('1,2,3')
are perfectly acceptable.
But, we can't do any arithmetic or comparison with them.
If we try
istr('abc') + 5
a TypeError
will be raised.
That holds for any arithmetic we try.
If we want to test if an istr can be interpreted (and thus used in an arithmetic and comparison expression). we can use the is_int()
method. So
ìstr(20).is_int()
is True
, whereas
ìstr('abc').is_int()
is False
.
The bool
operator works normally on the integer value of an istr. So
bool(istr('0'))
==> False
bool(istr('1'))
==> True
But if the istr can't be interpreted as an int, the string value will be used to test. So
bool(istr('abc'))
==> True
bool(istr(''))
==> False
For the in
operator, an istr is treated as an ordinary string, although it is possible to use ints as well:
'34' in istr(1234)
34 in istr(1234)
On the left hand side an istr is always treated as a string:
istr(1234) in '01234566890ABCDEF'
Sorting a list of istrs is based on the integer value, not the string. So
' '.join(sorted('1 3 2 4 5 6 11 7 9 8 10 12 0'.split()))
is
'0 1 10 11 2 3 4 5 6 7 8 9'
,whereas
' '.join(sorted(istr('1 3 2 4 5 6 11 7 9 8 10 12 0'.split()))
is
'0 1 2 3 4 5 6 7 8 9 10 11'
Apart from with numeric (to be interpreted as an int) or str, istr can be initialized with several other types:
if a dict (or subtype of dict), the same type dict will be returned with all values istr'ed
istr({'one': 1, 'two':2}) ==> {'one': istr('1'), 'two': istr('2')}
if an iterator, the iterator will be mapped with istr
mapped = (i for i in istr((i for i in range(2))))
print(mapped)
print(list(mapped))
this wil print something like
<generator object <genexpr> at 0x000002A10DE569B0>
[istr('0'), istr('1')]
if an iterable, the same type will be returned with all elements istr'ed
istr([0, 1, 4]) ==> [istr('0'), istr('1'), istr('4')]
istr((0, 1, 4)) ==> (istr('0'), istr('1'), istr('4'))
istr({0, 1, 4}) ==> `{istr('4'), istr('0'), istr('1')} # or similar
istr(range(3)) ==> istr.range(3)
list(istr(range(3))) ==> [istr('0'), istr('1'), istr('2')]
len(istr(range(3))) ==> 3
if an istr.range instance, the same istr.range will be returned
istr(istr.range(5)) ==> istr.range(5)
if an istr, the same istr will be returned
istr(istr('4')) ==> istr ('4')
It is possible to give more than one parameter, in which case a tuple of the istrs of the parameters will be returned, which can be handy to unpack multiple values, e.g.
a, b, c = istr(5, 6, 7) ==> a=istr('5') , b=istr('6'), c=istr('7')
It is possible to test for even/odd (provided the istr can be interpreted as an int) with the is_even
and is_odd
method, e.g.
istr(4).is_even()) ==> True
istr(5).is_odd()) ==> True
It is also possible to test for even/odd of an ordinary int:
istr.is_even(4) ==> True
istr.is_odd(5) ==> True
It is possible to test whether the value is a perfect square (provided the istr can be interpreted as an int) with the is_square
method, e.g.
istr(4).is_square() ==> True
istr(5).is_square()) ==> False
It is also possible to test for square of an ordinary int:
istr.is_square(4) ==> True
istr.is_square(5) ==> False
It is possible to test whether the value is a prime number (provided the istr can be interpreted as an int) with the is_prime
method, e.g.
istr(4).is_prime() ==> False
istr(5).is_prime()) ==> True
It is also possible to test for prime of an ordinary int:
istr.is_prime(4) ==> False
istr.is_prime(5) ==> True
It is possible to test whether an istr is divisible by a given value with the is_divisible_by method,
e.g.
istr(18).is_divisible_by(3) ==> True
istr(18).is_divisible_by(istr(3)) ==> True
istr(19).is_divisible_by(3) ==> False
istr(19).is_divisible_by(istr(3)) == False
It is also possible to test for divisibility of an ordinary int:
istr.is_divisible(18, 3) ==> True
istr.is_divisible(19, 3) ==> False
With the all_distinct
method, it is possible to test whether all characters are distinct (i.e. no character appears more than once).
istr('01234').all_distict() ==> True
istr('012340').all_distict() ==> False
n98 = istr(98)
n100 = n98 + 2
istr(n98).all_distinct() ==> True
istr(n100).all_distinct() ==> False
The method reversed()
will return an istr with the reversed content:
istr(456).reversed() ==> istr('654')
istr('0456').reversed() ==> istr('6540')
The same can -of course- be achieved with
istr(456)[::-1] ==> istr('654')
istr('0456')[::-1] ==> istr('6540')
It is possible to reverse a negative istr, but the result can't be interpreted as an int anymore.
istr(-456).reversed() + 3 ==> TypeError
The istr.enumerate
class method can be used just as the built-in enumerate function.
The iteration counter however is an istr rather than an int. E.g.
for i, c in istr.enumerate('abc'):
print(f'{repr(i)} {c}')
prints
istr('0') a
istr('1') b
istr('2') c
All methods in itertools are also available directly from istr. Note that the result is istr-ed (apart from groupby and tee).
The following class methods are supported (provided their counterpart exists in the installed Python version's itertools):
This can be handy as these methods don't have to be imported from itertools anymore.
All methods have exactly the same (optional) parameters as their itertools counterpart.
For example:
list(istr.repeat(1, 4)) ==> [istr('1'), istr('1'), istr('1'), istr('1')]
next(istr.count(3)) ==> istr('3')
One more example:
for t in istr.permutations(range(3)):
print(t)
results in
(istr('0'), istr('1'), istr('2'))
(istr('0'), istr('2'), istr('1'))
(istr('1'), istr('0'), istr('2'))
(istr('1'), istr('2'), istr('0'))
(istr('2'), istr('0'), istr('1'))
(istr('2'), istr('1'), istr('0'))
The istr.concat
method can be useful to map all items of an iterable
to istr
and then concatenate these.
`
list(istr.concat(((1,2),(3,4))) ==> istr([12,34])
list(istr.concat(itertools.permutations(range(3),2))) ==>
[istr('01'), istr('02'), istr('10'), istr('12'), istr('20'), istr('21')]
The class method digits
can be used to return an istr of digits according to a given specification.
The method takes either no or a number of arguments.
If no arguments are given, the result will be istr('0123456789')
.
The given argument(s) result in a range of digits.
<n>
==> n<n-m>
==> n, n+1, ..., m-n>
==> 0, 1, ... nn->
==> n, n+1, ..., 9 if n is numeric (0-9), n, n+1, ... Z if n is a letter'-'
==> 0, 1, ..., 9''
==> 0, 1, ..., 9(n and m must be digits between 0 and 9 or letters letters between A and Z)
When no stop value is specified, it will be
The final result is an istr composed of the given range(s).
Here are some examples:
istr.digits() ==> istr('0123456789')
istr.digits('') ==> istr('0123456789')
istr.digits('1') ==> istr('1')
istr.digits('3-') ==> istr('3456789')
istr.digits('-3') ==> istr('0123')
istr('1-4', '6', '8-9') ==> istr('1234689')
istr('1', '1-2', '1-3') ==> istr('11213')
istr.digits('-z') ==> istr('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ')
istr.digits('A-F') ==> istr('ABCDEF')
istr.digits('C') ==> istr('C')
istr.digits('3-') ==> istr('34567879')
istr.digits('X-') ==> istr('XYZ')
When a class is derived from istr, all methods will return that newly derived class.
E.g.
class jstr(istr.type):
...
print(repr(jstr(4) * jstr(5)))
will print jstr('20')
It is possible to control the way an istr
instance will be repr'ed.
By default, istr(5)
is represented as istr('5')
.
With the istr.repr_mode()
context manager, that can be changed:
with istr.repr_mode('str'):
five = istr(5)
print(repr(five))
with istr.repr_mode('int'):
five = istr(5)
print(repr(five))
with istr.repr_mode('istr'):
five = istr(5)
print(repr(five))
This will print
'5'
5
istr('5')
If the repr_mode is 'int'
and the istr can't be interpreted as an int the string ?
will be returned:
with istr.repr_mode('int'):
abc = istr('abc')
print(repr(abc))
This will print
?
The way an
istr
is represented is determined at initialization.
It is also possible to set the repr mode without a context manager:
istr.repr_mode('str')
five = istr('5')
print(repr(five))
This will print
'5'
Finally, the current repr mode can be queried with istr.repr_mode()
. So upon start:
print(repr(istr.repr_mode()))
will output istr
.
By default, istr
works in base 10. However it is possible to change the base system with the istr.base()
context manager / method.
Any base between 2 and 36 may be used.
Note that the integer is always stored in base 10 mode, but the string representation will reflect the chosen base (at time of initialization).
Some examples:
with istr.base(16):
a = istr('7fff')
print(int(a))
b = istr(127)
print(repr(b))
This will result in
32767
istr('7F')
All calculations are done in the decimal 10 base system.
Note that the way an istr
is interpreted is determined at initialization.
It is also possible to set the repr mode without a context manager:
istr.base(16)
print(int(istr('7fff')))
This will print
32767
Finally, the current base can be queried with istr.base()
, so upon start:
print(istr.base())
will result in 10
.
When an istr is initialized with a string the istr will be always stored as such.
repr('4')) ==> istr('4')
repr(' 4')) ==> istr(' 4')
repr('4 ')) ==> istr('4 ')
For initializing with an int (or other numeric) value, the string is by default simply the str representation
repr(4)) ==> istr('4')
With the istr.int_format()
context manager this behavior can be changed.
If the format specifier is a number, most likely a single digit, that
will be the minimum number of characters in the string:
with istr.int_format('3'):
print(repr(istr(1)))
print(repr(istr(12)))
print(repr(istr(123)))
print(repr(istr(1234)))
will print
istr(' 1')
istr(' 12')
istr('123')
istr('1234')
If the string starts with a 0
, the string will be zero filled:
with istr.int_format('03'):
print(repr(istr(1)))
print(repr(istr(12)))
print(repr(istr(123)))
print(repr(istr(1234)))
will print
istr('001')
istr('012')
istr('123')
istr('1234')
For bases other than 10, the string will never be reformatted!
The table below shows whether the string or the int version of istr is applied.
operator/function int str Example
-----------------------------------------------------------------------------------------
+ x istr(20) + 3 ==> istr('23')
_ x istr(20) - 3 ==> istr('17')
* x istr(20) * 3 ==> istr('60')
/ x istr(20) / 3 ==> istr('6')
// x istr(20) // 3 ==> istr('6')
% x istr(20) % 3 ==> istr('2')
divmod x divmod(istr(20), 3) ==> (istr('6'), istr('2'))
** x istr(2) ** 3 ==> istr('8')
<=, <, >, >= x istr('100') > istr('2') ==> True
abs x abs(istr(-20)) ==> istr('20')
== x x istr(20) == 20 ==> True | istr(20) == '20' ==> True
bool x x *) bool(istr(' 0 ')) ==> False | istr('') ==> False
@ x istr(20) @ 3 ==> istr('202020')
| x istr(20) | '5' ==> istr('205')
slicing x istr(12345)[1:3] ==> istr('23')
iterate x [x for x in istr(20)] ==> [istr('2'), istr('0')]
len x len(istr(' 20 ')) ==> 4
count x istr(100).count('0') ==> 2
index x istr(' 100 ').index('0') ==> 2
split x istr('1 2').split() ==> (istr('1'), istr('2'))
string format x f"|{istr(1234):6}|" ==> '|1234 |'
other string methods x istr('aAbBcC').lower() ==> istr('aabbcc')
istr('aAbBcC').islower() ==> False
istr(' abc ').strip() ==> istr('abc')
...
-----------------------------------------------------------------------------------------
*) str is applied if is_int() is False
There's an extensive pytest script in the \tests
directory.
This script also shows clearly the ways istr can be used, including several edge cases. Highly recommended to have a look at.
FAQs
istr - strings you can count on
We found that istr-python demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.