Как создать класс, расширяющий базовый класс int с проверкой ввода? - PullRequest
1 голос
/ 09 мая 2020

Я хотел бы создать класс, расширяющий базовый класс int, чтобы сам объект был целым числом (т.е. вы устанавливаете его и читаете напрямую), но также имел бы проверку ввода - например, только разрешить заданный диапазон.

Из того, что я исследовал, метод __init__ не вызывается, когда вы расширяете обычные базовые классы, поэтому я не уверен, как это сделать. Я также не понимаю, как вы получаете доступ к значению объекта (т.е. к фактическому целому числу, присвоенному ему) или изменяете это значение из класса. Я вижу ту же проблему при расширении любого базового класса (string, float, tuple, list и т.д. c.).

Если бы я мог использовать __init__, это было бы примерно так:

class MyInt(int):
    def __init__(self, value):
        if value < 0 or value > 100:
            raise ValueError('MyInt objects must be in range 0-100, value was {}'.format(value))
        self = value

Как я могу проверить новые значения, входящие в мой расширенный класс int?

Ответы [ 3 ]

2 голосов
/ 10 мая 2020

В этом случае вам не нужно заменять __new__. После создания объекта будет вызываться __init__, и вы можете проверить, находится ли он в диапазоне или нет.

class MyInt(int):
    def __init__(self, x, **kwargs):
        if self < 0 or self > 100:
            raise ValueError('MyInt objects must be in range 0-100, value was {}'.format(value))

Вы можете переопределить __new__, чтобы вызвать исключение, прежде чем MyInt(...) вернет новый объект.

class MyInt(int):
    def __new__(cls, *args, **kwargs):
        x = super().__new__(cls, *args, **kwargs)  # Let any value error propagate
        if x < 0 or x > 100:
            raise ValueError('MyInt objects must be in range 0-100, value was {}'.format(value))
        return x

Вы можете попробовать проверить аргумент перед вызовом super().__new__, но, строго говоря, это плохо работает с другими классами.

0 голосов
/ 25 мая 2020

Используйте GetAttr, чтобы заставить объект нормального класса возвращать обернутый «частный» атрибут

Вот еще один ответ, который я встретил, и который заслуживает записи здесь. Поскольку он похоронен в конце ответа, который не является общепринятым ответом на несколько иной вопрос, я публикую его здесь.

Я искал эту конкретную альтернативу c для ряда чехлы, и вот оно на самом деле !! Кажется, что при непосредственном запросе любого объекта должен быть какой-то способ ответить, как вы хотите, при разработке класса. Вот что делает этот ответ.

Так, например, вы можете использовать обычный класс, но запрос объекта класса возвращает int. Это позволяет объекту хранить int в отдельной переменной, поэтому он может быть изменен, но упрощает использование объекта класса, не требуя доступа к определенному полю, просто запрашивая объект напрямую (т.е. myint = MyInt(1); print(myint)

Обратите внимание на его рекомендацию на самом деле подумать, почему вы не можете сначала подклассифицировать соответствующий тип (т.е. использовать один из других ответов здесь).


Альтернативный ответ

Это скопировано из этого ответа StackOverflow . Все кредиты на Francis Avila . В качестве подкласса используется list вместо int, но та же идея:


  1. Взгляните на это руководство по использованию метода magi c в Python.
  2. Обоснуйте, почему вы не подклассифицируете list если то, что вы хотите, очень похоже на список. Если подклассы не подходят, вы можете вместо этого делегировать экземпляр обернутого списка:
class MyClass(object):
    def __init__(self):
        self._list = []
    def __getattr__(self, name):
        return getattr(self._list, name)

    # __repr__ and __str__ methods are automatically created
    # for every class, so if we want to delegate these we must
    # do so explicitly
    def __repr__(self):
        return "MyClass(%s)" % repr(self._list)
    def __str__(self):
        return "MyClass(%s)" % str(self._list)

This will now act like a list without being a list (i.e., without subclassing `list`).
```sh
>>> c = MyClass()
>>> c.append(1)
>>> c
MyClass([1])

0 голосов
/ 10 мая 2020

После исследования функции __new__ из MarkM предложенной ссылки , этот подход будет делать то, что я ищу, но я также сомневаюсь, что это правильный подход. См. Следующие разделы обсуждения и решения.


Обсуждение подкласса базовых неизменяемых типов

Однако я также сомневаюсь в необходимости этого, по крайней мере, для простых проверок проверки ввода . При создании подкласса неизменяемого базового класса хорошо то, что вы получаете все преимущества неизменяемости, такие как встроенное хеширование, строковое представление и т. Д. c., Которые просто наследуются напрямую от базового класса. Но вы получите те же преимущества, просто добавив свойство в другой класс, добавив функцию установки и сделав его неизменяемым. и / или дополнительные методы, которые могут применяться ко многим различным классам / модулям, без дополнительной сложности создания отдельного класса, который преобразует их в изменяемые объекты, а затем требует дополнительного уровня доступа к свойствам или методам (т. е. вы делаете "myint "объект из класса" MyInt ", но для доступа к базовому int требуется свойство" value "или что-то подобное, например myint.value, а не просто myint).


Решение / Реализация

Так что, возможно, есть лучший способ сделать это, но чтобы ответить на мой собственный вопрос, вот тестовый сценарий, который я написал, чтобы хотя бы заставить это работать.

Обратите внимание, что int может иметь несколько аргументов , при интерпретации строки в специфицированную c базу, например ('1111', 2), который преобразует двоичную строку '1111' в десятичную 15. Базу также можно ввести как kwarg, поэтому требуется передача * args и ** kwargs полностью в функцию int __new__.

Кроме того, я сначала позвонил в int перед выполнением проверки, чтобы он сначала преобразовал числа с плавающей запятой и строки в int, прежде чем пытаться выполнить проверку.

Обратите внимание, что, поскольку MyInt подклассы int, вы должны вернуть значение типа int - вы не можете вернуть «None» в случае ошибки (хотя вы можете вернуть 0 или -1). Это заставило меня вызвать ValueError и обработать ошибки обратно в основном скрипте.

class MyInt(int):

    def __new__(cls, *args, **kwargs):
        value = super().__new__(cls, *args, **kwargs)
        argstr = ', '.join([str(arg) for arg in args]) # Only for prototype test
        print('MyInt({}); Returned {}'.format(argstr, value)) # Only for prototype test
        if value < 0 or value > 100:
            raise ValueError('  ERROR!! Out of range! Must be 0-100, was {}'.format(value))
        return value


if __name__ == '__main__':
    myint = MyInt('1111', 2)  # Test conversion from binary string, base 2
    print('myint = {} (type: {})\n'.format(myint, type(myint)))

    for index, arg in enumerate([-99, -0.01, 0, 0.01, 0.5, 0.99, 1.5, 100, 100.1, '101']):
        try:
            a = MyInt(arg)
        except ValueError as ex:
            print(ex)
            a = None
        finally:
            print('  a : {} = {}'.format(type(a), a))
...