Как ограничить атрибуты в методе класса?
В классе необходимо реализовать метод с проверкой введённых атрибутов. Например для метода scale нужно задать три параметра: тип датчика (гироскоп или акселерометр), ось (x, y, z) и заданное значение типа float или int в диапазоне [-1; 1]. Как правильно всё это проверить и в случае введении пользователем не верного значения вызвать ошибку? Я сделал это с помощью обычных проверок и вышло довольно объёмно с учётом того, что тут нет дальнейшей логики работы с датчиком. Может быть есть более красивый и правильный способ решения?
class Sensor:
def scale(self, sensor_type, axis, value):
if not (isinstance(sensor_type, str) or isinstance(axis, str) \
or isinstance(value, (int, float))):
raise Exception("Введён не верный тип данных")
elif not (sensor_type == 'gyro' or sensor_type == 'accl'):
raise Exception("Введён не верный тип датчика")
elif not (axis == 'x' or axis == 'y' or axis == 'z'):
raise Exception("Введён не верный тип оси")
elif -1 < value > 1:
raise Exception("Вышли за пределы допустимого интервала")
if sensor_type == 'gyro':
if axis == 'x':
print('do something gyro x')
elif axis == 'y':
print('do something gyro y')
elif axis == 'z':
print('do something gyro z')
elif sensor_type == 'accl':
if axis == 'x':
print('do something accl x')
elif axis == 'y':
print('do something accl y')
elif axis == 'z':
print('do something accl z')
Кроме этого, чтобы метод работал необходимо вызывать его так:
sensor.scale('gyro', 'z', 0)
Мне бы хотелось избавится от кавычек и делать вызов следующим образом:
sensor.scale(gyro, z, 0)
Но я, к сожалению, не нашёл информации как это реализовать.
Ответы (1 шт):
Идеальная ситуация, когда каждую функцию легко понять. Чтоб это можно было сделать, нужно чтобы выполнялись два условия:
- функция должна быть достаточно короткая
- все действия в ней должны быть на одном уровне абстракции
Проблема, которая у вас возникла, как раз этого рода - функция разрослась. И в ней смешаны сразу несколько уровней абстракции. В частности функция знает что делать ("нужно проверить что переменные правильные"), но также она знает как это делать.
Ситуацию можно сильно улучшить выделив функцию или функции для проверки параметров а также для действий, как раз чтобы разделить "что" и "как".
def scale(self, sensor_type, axis, value):
self.validate_scale_parameters(sensor_type, axis, value)
if sensor_type == 'gyro':
self.scale_gyro(axis, value)
elif sensor_type == 'accl':
self.scale_accl(axis, value)
def validate_scale_parameters(self, sensor_type, axis, value):
if not (isinstance(sensor_type, str) or isinstance(axis, str) \
or isinstance(value, (int, float))):
raise Exception("Введён не верный тип данных")
elif not (sensor_type == 'gyro' or sensor_type == 'accl'):
raise Exception("Введён не верный тип датчика")
elif not (axis == 'x' or axis == 'y' or axis == 'z'):
raise Exception("Введён не верный тип оси")
elif not (-1 < value < 1):
raise Exception("Вышли за пределы допустимого интервала")
def scale_gyro(self, axis, value):
if axis == 'x':
print('do something gyro x')
elif axis == 'y':
print('do something gyro y')
elif axis == 'z':
print('do something gyro z')
def scale_accl(self, axis, value):
if axis == 'x':
print('do something accl x')
elif axis == 'y':
print('do something accl y')
elif axis == 'z':
print('do something accl z')
Далее очень сильно поможет использование типизированных объектов вместо обобщенных. Т.е. enum-ов для перечислимых типов. Это уберет некоторые проверки вообще и сделает код в целом более поддерживаемым:
from enum import Enum
class SensorType(Enum):
GYRO = 'gyro'
ACCL = 'accl'
class Axis(Enum):
X = 'x'
Y = 'y'
Z = 'z'
class Sensor:
def validate_scale_parameters(self, sensor_type, axis, value):
# теперь достаточно проверить только тип объекта
if not (isinstance(sensor_type, SensorType) or isinstance(axis, Axis) \
or isinstance(value, (int, float))):
raise Exception("Введён не верный тип данных")
elif not (-1 < value < 1):
raise Exception("Вышли за пределы допустимого интервала")
# вызов выглядит так - почти то, что вы хотели. В определенном смысле даже лучше
sensor.scale(SensorType.GYRO, Axis.X, 0.5)
Для value имеет смысл то же самое сделать, ввести тип, который уже сам будет контролировать ограничения:
class SensorValue:
def __init__(self, value):
if not isinstance(value, (int, float)):
raise Exception("Введён не верный тип данных")
if not (-1 < value < 1):
raise Exception("Вышли за пределы допустимого интервала")
self._value = value
@property
def value(self):
return self._value
Теперь опять же в scale достаточно контролировать что value имеет тип SensorValue:
def scale(self, sensor_type, axis, value):
# теперь достаточно проверить только тип объекта
# отдельная функция не нужна, т.к. проверка уже упростилась до почти тривиальной
if not (isinstance(sensor_type, SensorType) or isinstance(axis, Axis) \
or isinstance(value, SensorValue)):
raise Exception("Введён не верный тип данных")
if sensor_type == SensorType.GYRO:
self.scale_gyro(axis, value)
elif sensor_type == SensorType.ACCL:
self.scale_accl(axis, value)
def scale_gyro(self, axis, value):
if axis == Axis.X:
print('do something gyro x')
print(value.value)
elif axis == Axis.Y:
print('do something gyro y')
elif axis == Axis.Z:
print('do something gyro z')
Ну и вызов:
sensor.scale(SensorType.GYRO, Axis.X, SensorValue(0.5))
Далее можно избавится еще и от диспечеризации по типу сенсора используя полиморфизм, но это уже тема для другого вопроса и ответа.