问题
I have some functions doing math stuff which needs to take integer agrmuents.
I know that I can force using int by using condition isinstance(x, int)
or more strict type(x) == int, but IMO it isn't pythonic.
I think my Python code shouldn't reject 2.0 just because it's float.
What's the best way to check if value is logically integer?
By logically integer I mean value of any type, which represents integer.
It should be able to be used in arithmetic operations like int, but I don't have to check it,
because I belive that in Python any set of conditions can get fooled.
For example,True, -2, 2.0, Decimal(2), Fraction(4, 2) are logically integers,
when '2' and 2.5 are not.
At the moment I use int(x) == x, but I'm not sure if it's the best solution.
I know I can use float(x).is_integer().
I also saw x % 1 == 0.
回答1:
Normally one would check against the Integral ABC (Abstract Base Class), but floating point values are not normally meant to be treated as integers no matter their value. If you want that, take note of their is_integer property:
(1324.34).is_integer()
#>>> False
(1324.00).is_integer()
#>>> True
and the code is then just:
from numbers import Integral
def is_sort_of_integer(value):
if isinstance(value, Integral):
return True
try:
return value.is_integer()
except AttributeError:
return False
If you also want to deal with Decimals, Fractions and so on, which don't have an is_integer method, the best option would probably be just:
from numbers import Number, Integral
def is_sort_of_integer(value):
if isinstance(value, Integral):
return True
if isinstance(value, Number):
try:
return not value % 1
except TypeError:
return False
return False
The Integral check shouldn't be needed in this case, but it's probably best to keep it.
回答2:
One way to solve this is by using a metaclass to define your custom implementation of __instancecheck__, then define a concrete class having the metaclass, and use isinstance with your concrete class.
This has the downside of pulling in metaclass machinery, which is often extraneous.
But it has the upside of cleanly encapsulating whatever properties you desire to use for what you mean by "logically integer" for your application.
Here's some code to show this approach:
class Integral(type):
def __instancecheck__(self, other):
try:
cond1 = int(other) == other
cond2 = (other >= 1.0) or (other < 1.0)
# ... plus whatever other properties you want to check
return all([cond1, cond2,])
except:
return False
class IntLike:
__metaclass__ = Integral
print isinstance(-1, IntLike)
print isinstance('1', IntLike)
print isinstance(27.2, IntLike)
print isinstance(27.0, IntLike)
print isinstance(fractions.Decimal(2), IntLike)
print isinstance(fractions.Fraction(4, 2), IntLike)
It prints:
True
False
False
True
True
True
Note that it is important to get rid of the idea that the mathematical concept of being logically integer should apply to your program. Unless you bring in some proof-checking machinery, you won't get that. For example, you mention properties like certain functions being available, and specifically sqrt -- but this won't be available for negative integers unless you implement custom behavior to check for complex results.
It will be application-specific. For example, someone else might pick up this code and modify it so that '1' does register as IntLike, and perhaps for the sake of their application it will be correct.
This is why I like the metaclass approach here. It lets you explicitly denote each condition that you are imposing, and store them in one location. Then using the regular isinstance machinery makes it very clear to code readers what you are trying to do.
Lastly, note that no given conditions will always be perfect. For example, the class below could be used to 'fool' the int(x) == x trick:
class MyInt(object):
def __init__(self, value):
self.value = value
def __int__(self):
return int(self.value)
def __add__(self, other):
return self.value + other
#... define all needed int operators
def __eq__(self, other):
if isinstance(other, float):
raise TypeError('How dare you compare me to a float!')
return self.value == other
# ..etc
Then you get behavior like this:
In [90]: mi = MyInt(3)
In [91]: mi + 4
Out[91]: 7
In [92]: mi == 3
Out[92]: True
In [93]: int(mi) == mi
Out[93]: True
In [94]: mi == 3.0
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-93-827bea4a197f> in <module>()
----> 1 mi == 3.0
<ipython-input-89-66fec92fab7d> in __eq__(self, other)
13 def __eq__(self, other):
14 if isinstance(other, float):
---> 15 raise TypeError('How dare you compare me to a float!')
16 return self.value == other
17
TypeError: How dare you compare me to a float!
and whether or not isinstance(mi, IntLike) returns True will be totally dependent on how you have implemented the comparison operators for MyInt and also whatever extra checks you have made in Integral's __instancecheck__.
回答3:
There are some cases, which none of int(x) == x, x.isinteger() & x % 1 == 0 can handle the way I would like to.
Example:
>>> big_float = 9999999999999999.1
The big_float is big enough to ignore substracting some small number from it (AFAIK, it's called underflow):
>>> big_float -1 == big_float
True
Then
>>> def fib(n):
... current, prev = 1, 0
... while n > 0:
... current, prev, n = current+prev, current, n-1
... return prev
...
>>> fib(big_float) #unwanted infinite loop
There are some cases, which none of
int(x) == x,x.isinteger()&x % 1 == 0can handle the way I would like to.
>>> int(big_float) == big_float
True
>>> big_float.is_integer()
True
>>> big_float % 1 == 0
True
Solution
We can check if big_float in the same way as int(big_float):
>>> int(big_float) -1 == big_float -1
False
Of course, this method works also for more trivial cases, like this:
>>> x = 2.1
>>> int(x) -1 == x -1
False
Of course, you don't have to substract 1, you can use whatever mathematical operation you need.
Note that this condition may throw exception:
>>> x = '2'
>>> int(x) -1 == x -1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for -: 'str' and 'int'
回答4:
Simple, try to convert it to an integer. If int() works, it is "logically an integer", otherwise it is not.
try:
int(thing)
is_integer = True
except ValueError:
is_integer = False
But, typically, rather than do it like this you would just use int(thing) in the code you needed this for, and just catch the error if it ends up not being an integer and handle that case appropriately.
来源:https://stackoverflow.com/questions/26946237/how-to-check-if-value-is-logically-integer