Есть ли способ угодить проверке типов, кроме isinstance?

Как я могу проверить, к какому подклассу принадлежит экземпляр, не используя isinstance? (Насколько я понимаю, использование isinstance считается плохой практикой?...) Например:

from typing import Protocol

class Entry(Protocol):
    name: str
    
    def is_file(self) -> bool: ...
    def is_folder(self) -> bool: ...

class File(Entry):
    name: str
    content: str

    def is_file(self) -> bool:
        return True
    def is_folder(self) -> bool:
        return False
    def get_size(self) -> int:
        return len(self.content)

class Folder(Entry):
    name: str
    children: list[Entry]
    
    def is_file(self) -> bool:
        return False
    def is_folder(self) -> bool:
        return True

def do_something(entry: Entry) -> None:
    if entry.is_file():
        print(entry.get_size())

В последней строке do_something средство проверки типов по понятным причинам жалуется, что у entry нет метода с именем get_size, потому что оно явно не понимает, что я использую is_file для того, чтобы убедиться в этом. (Моя проверка типов — Pylance в VSCode) Что я могу сделать вместо этого? (Я знаю, что этот код работает нормально, но я также хотел бы, чтобы он прошел проверку типов)

Независимо от проверки типов, do_something в любом случае кажется слишком общим, но FWIW с использованием isinstance само по себе не является плохой практикой.

DeepSpace 10.01.2023 20:07

По крайней мере, в этом примере, почему do_something принимает произвольное Entry, если оно работает только с File объектами?

chepner 10.01.2023 20:07

Неодобрение вызывает не isinstance, а проверка типов во время выполнения в любой форме, вместо таких вещей, как утиная типизация и полиморфизм.

chepner 10.01.2023 20:08

Я согласен с комментариями выше, хотя, если это предполагаемое использование, вы можете использовать typing.cast(File, entry)

Jab 10.01.2023 20:16

Вероятно, нет необходимости в cast; просто используйте if isinstance(entry, File):, и средство проверки типов примет (путем сужения типов), что entry.get_size() допустимо.

chepner 10.01.2023 20:43

Вы должны перепроектировать свою реализацию, чтобы использовать functools.singledispatch.

dROOOze 10.01.2023 20:51

Это упрощенная версия моего кода, на самом деле в моем коде нет do_something и вместо этого он предназначен для рекурсивной печати папки. Таким образом, он перебирает дочерние элементы данной папки, и если это файл, он печатает этот файл, но если это папка, он вызывает ту же функцию для этой папки. Должен ли я отредактировать код примера, чтобы показать это?

Shahar Nacht 10.01.2023 22:19

Где именно вы узнали, что isinstance это плохая практика? Это такое широкое заявление, это просто глупо. Я, конечно, видел чрезмерное использование проверки типов во время выполнения, на что ссылается @chepner, но по сути в этом нет ничего плохого. Весь ваш посыл очень сомнительный. Если ваша цель — узнать тип чего-либо, для этого и нужен isinstance. Другой вопрос, нужно ли вам вообще знать тип. Здесь вы просто ищете охранники нестандартного типа, которые заново изобрели бы isinstance колесо.

Daniil Fajnberg 11.01.2023 02:22

Не связанное с этим примечание: наследование от протокола с конкретными классами побеждает всю его цель. Протоколы явно предназначены для структурного подтипа, то есть что-то является его подтипом, когда оно соответствует протоколу в своем интерфейсе.

Daniil Fajnberg 11.01.2023 02:27
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Веб-скрейпинг, как мы все знаем, это дисциплина, которая развивается с течением времени. Появляются все более сложные средства борьбы с ботами, а...
Библиотека для работы с мороженым
Библиотека для работы с мороженым
Лично я попрощался с операторами print() в python. Без шуток.
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Эмиссия счетов-фактур с помощью Telegram - Python RPA (BotCity)
Привет, люди RPA, это снова я и я несу подарки! В очередном моем приключении о том, как создавать ботов для облегчения рутины. Вот, думаю, стоит...
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
Учебник по веб-скрапингу
Учебник по веб-скрапингу
Привет, ребята... В этот раз мы поговорим о веб-скрейпинге. Целью этого обсуждения будет узнать и понять, что такое веб-скрейпинг, а также узнать, как...
Тонкая настройка GPT-3 с помощью Anaconda
Тонкая настройка GPT-3 с помощью Anaconda
Зарегистрируйте аккаунт Open ai, а затем получите ключ API ниже.
0
9
64
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Возможно, более питонический способ справиться с этим — полностью избавиться от is_file и is_folder. Они не что иное, как isinstance под другим именем.

class Entry:
    def get_size(self) -> int:
        raise NotImplementedError

class File(Entry):
    ...
    def get_size(self) -> int:
        return len(self.content)

class Folder(Entry):
    ...


def do_something(entry: Entry) -> None:
    try:
        print(entry.get_size())
    except NotImplementedError:
        pass

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

chepner 10.01.2023 20:44

@чепнер. Я в основном согласен с вами. Но я не знаю, как этот код будет развиваться. Собирается ли OP добавить больше методов? Или они собираются добавить больше подклассов, некоторые из которых будут иметь разумный метод get_size(). Мне бы очень хотелось узнать больше о методе do_something. . .

Frank Yellin 10.01.2023 22:08
Ответ принят как подходящий

Нет причин не использовать здесь isinstance. Это уменьшает количество ненужного шаблонного кода. Нахмурился не isinstance, а ненужная проверка типов во время выполнения любого рода, включая ваши пользовательские is_* методы. Предполагая, что есть веская причина для того, чтобы сделать do_something таким излишне общим, просто используйте следующее.

from typing import Protocol

class Entry:
    def __init__(self, *, name: str, **kwargs):
        super().__init__(**kwargs)
        self.name = name
    

class File(Entry):    
    def __init__(self, *, content: str, **kwargs):
        super().__init__(**kwargs)
        self.content = content

    def get_size(self) -> int:
        return len(self.content)


class Folder(Entry):    
    def __init__(self, *, children: list[Entry], **kwargs):
        super().__init__(**kwargs)
        self.children = children
    

def do_something(entry: Entry) -> None:
    if isinstance(entry, File):
        print(entry.get_size())

Однако, если do_something не должен работать ни с чем, кроме объектов File, то укажите это в типе, чтобы вам не нужно было такое сужение типа:

def do_something(entry: File) -> None:
    print(entry.get_size())

Другие вопросы по теме

Перебрать файл excel и создать новый словарь для каждого нового элемента в столбце.
Установите условие внутри лямбда-функции в зависимости от того, является ли значение, захваченное с помощью регулярного выражения, None или ""
Как преобразовать код для записи данных в xlsx вместо csv, используя python без панд?
Обновление ключей словаря в одну строку
Правильный способ обработки гигантских строк с многопроцессорной обработкой
Создает новый словарь, состоящий из другого ключа словаря и двух переменных
Установите регулярное выражение с множественным выбором, чтобы попытки сопоставления всегда выполнялись слева направо, независимо от того, пытается ли другое предыдущее регулярное выражение захватить больше символов?
Ошибка из селена: AttributeError: объект «Сервис» не имеет атрибута «процесс»
Создать новый столбец типа списка на основе операции деления существующих столбцов в pandas
Ошибка установки Anaconda на macOS Ventura