I have a class called Divider1 which divides a numerator by a denominator, set at initialisation:
class Divider1:
def __init__(self, numerator=0, denominator=1):
self.numerator = numerator
self.denominator = denominator
def divide(self):
return self.numerator / self.denominator
The class is easily abused by initialising incorrectly, but it is not immediately clear until dividing actually occurs:
>>> x = Divider1('hello', 5)
>>> x.divide()
TypeError: unsupported operand type(s) for /: 'str' and 'int'
It seems that raising an exception earlier would be beneficial for debugging and maintenance, so here is my next logical step:
class Divider2:
def __init__(self, numerator=0, denominator=1):
try:
numerator = float(numerator)
except ValueError:
raise ValueError("Numerator must be numeric!")
try:
denominator = float(denominator)
except ValueError:
raise ValueError("Denominator must be numeric!")
if denominator == 0:
raise ValueError("Denominator must be non-zero!")
self.numerator = numerator
self.denominator = denominator
def divide(self):
return self.numerator / self.denominator
Now we have some safety:
>>> x = Divider2('hello', 5)
ValueError: Numerator must be numeric!
It's very clear to me, immediately what is wrong on initialisation, but it can still be abused by changing the attribute after initialisation:
>>> x = Divider2(10, 5)
>>> x.numerator = 'hello'
>>> x.divide()
TypeError: unsupported operand type(s) for /: 'str' and 'int'
I'm trying to figure out the Pythonic way of dealing with these issues. I could possibly just name the numerator and denominator _numerator and _denominator, to indicate that they should be treated as private.
Or alternatively, I could use properties:
class Divider4:
def __init__(self, numerator=0, denominator=1):
self.numerator = numerator
self.denominator = denominator
@property
def numerator(self):
return self._numerator
@numerator.setter
def numerator(self, value):
try:
value = float(value)
except ValueError:
raise ValueError("Denominator must be numeric!")
self._numerator = value
@property
def denominator(self):
return self._denominator
@denominator.setter
def denominator(self, value):
try:
value = float(value)
except ValueError:
raise ValueError("Denominator must be numeric!")
if value == 0:
raise ValueError("Denominator must be non-zero!")
self._denominator = value
def divide(self):
return self.numerator / self.denominator
So now:
>>> x = Divider4(10, 5)
>>> x.numerator = 'hello'
ValueError: Numerator must be numeric!
>>> x = Divider4(10, 0)
ValueError: Denominator must be non-zero!
It is clearly much safer, and I know the cause of the error immediately, no matter how the class was used - it's impossible for the Divider to reach an impossible state. But I had to write many more lines of code to achieve this, especially compared to Divider1. But I still think that this sort of safety should always be reached, though I haven't seen people do this all the time when they write Python code. Am I using Python in a way that was unintended? Is it Pythonic to always write such cumbersome properties to ensure safety? Are there times when it should or shouldn't be done?
答案 0 :(得分:1)
很多丑陋来自value = float(value)
。您可以使用抽象基类numbers.Number
来检查输入是否为数字。
from numbers import Number
class Divider4:
def __init__(self, numerator=0, denominator=1):
self.numerator = numerator
self.denominator = denominator
@property
def numerator(self):
return self._numerator
@numerator.setter
def numerator(self, value):
if not isinstance(value, Number):
raise ValueError("Bad numerator: {}".format(value))
self._numerator = value
@property
def denominator(self):
return self._denominator
@denominator.setter
def denominator(self, value):
if not isinstance(value, Number) or value == 0:
raise ValueError('Bad denominator: {}'.format(value))
self._denominator = value
def divide(self):
return self.numerator / self.denominator
数字的类型层次结构在PEP 3141
中定义