Рубрики
Без рубрики

Распознавание уток

Обзор методов проверки типов в Python. Теги с Python, Ducttyping, метаклассами.

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

Python дает нам несколько способов проверить, что объекты, которые мы передаем к функциям, являются типов, которые мы ожидаем. Каждый метод имеет свои преимущества и недостатки.

Просто не заботясь

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

def poke(duck):
    duck.quack()
    duck.walk()

Наследование

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

class Anas:
    def quack(self): pass
    def walk(self): pass

class Duck(Anas):
    def quack(self): print('Quack!')
    def walk(self): print('Walks like duck.')

class Mallard(Anas):
    def quack(self): print('Quack!')
    def walk(self): print('Walks like duck.')

def poke(duck):
    assert isinstance(duck, Anas)

Интерфейсы

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

from abc import ABC, abstractmethod

class IDuck(ABC):
    @abstractmethod
    def quack(self): pass

    @abstractmethod
    def walk(self): pass

class Duck(IDuck):
    def quack(self): print('Quack!')
    def walk(self): print('Walks like duck.')

class RoboticDuck(IDuck):
    def quack(self): print('Quack!')
    def walk(self): print('Walks like duck.')

def poke(duck):
    assert isinstance(duck, IDuck)

Здорово. И если мы не контролируем типы, мы всегда можем писать адаптеры.

Тест утки

Но это Питон. Мы можем сделать лучше.

Как мы знаем, Python использует набрав уток. Таким образом, мы должны быть в состоянии использовать Тест утки для типов. В нашем примере каждый объект реализует Квалак () и Прогулка () это утка. Это достаточно легко, чтобы проверить.

def is_a_duck(duck):
    for attr in ('quack', 'walk'):
        if not hasattr(duck, attr):
            return False
    return True

def poke(duck):
    assert is_a_duck(duck)
    duck.quack()
    duck.walk()

Это работает. Но мы перечислим Isinstance (...) вызов. Мы, безусловно, можем сделать лучше.

Метаклассы и подклассные крючки

Метаклассы являются замечательными конструкциями. Поскольку их имя может предложить, они принимают участие в строительстве занятий. Они даже позволяют нам установить крючки в основные механизмы Python, такие как Isinstance (...) , используя __subclasshook__ Отказ

from abc import ABC

def is_a_duck(duck):
    for attr in ('quack', 'walk'):
        if not hasattr(duck, attr):
            return False
    return True

class DuckChecker(ABC):
    @classmethod
    def __subclasshook__(cls, C):
        if cls is not DuckChecker:
            return NotImplemented
        return is_a_duck(C)

def poke(duck):
    assert isinstance(duck, DuckChecker)
    duck.quack()
    duck.walk()

И мы вернемся к бизнесу. Что сказал, IS_A_DUCK По-прежнему напечатанный беспорядок, и будет очень больно поддерживать.

Не было бы неплохо, если бы мы могли просто использовать наши Iduck Интерфейс для проверки на укуса?

Абстрактные методы, опять же!

Счастливчик за нас – мы можем!

Среди прочего, ABC Родительский класс перечисляет все @abstractmethod S и хранит их в __abstractmethods__ Переменная участника. Это означает, что мы можем легко перечислить их в нашем подклассе и проверять их.

from abc import ABC, abstractmethod

class IDuck(ABC):
    @abstractmethod
    def quack(self): pass

    @abstractmethod
    def walk(self): pass

    @classmethod
    def __subclasshook__(cls, C):
        if cls is not IDuck:
            return NotImplemented
        for attr in cls.__abstractmethods__:
            if not hasattr(C, attr):
                return False
        return True

class Duck:
    def quack(self): print('Quack!')
    def walk(self): print('Walks like a duck.')

def poke(duck):
    assert isinstance(duck, IDuck)
    duck.quack()
    duck.walk()

poke(Duck())  # Quack!
              # Walks like a duck.

Потрясающий. Следующий шаг – разделяя интерфейс от проверки логики.

Протоколы

Чтение через Python Documentation и номенклатуру, вы могли бы увидеть термин «протокол» здесь и там. Это способ Python, чтобы вызвать набранные утки интерфейсы. Таким образом, вы могли бы сказать, что мы только что создали «Протокол Checker». Теперь мы можем разделить его в базовый класс.

from abc import ABC, abstractmethod

class Protocol(ABC):
    _is_protocol = True
    @classmethod
    def __subclasshook__(cls, C):
        if not cls._is_protocol:
            return NotImplemented
        for attr in cls.__abstractmethods__:
            if not hasattr(C, attr):
                return False
        return True

class IDuck(Protocol):
    @abstractmethod
    def quack(self): pass

    @abstractmethod
    def walk(self): pass

И это все. Что мало _is_protocol Флаг там зря. Обычно мы проверим протокол-несс, используя Isinstance (...) Отказ В этом случае, однако, мы подключаемся к этому механизму, и это приведет к бесконечному рекурсии.

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

Эта собака – утка

В некоторых случаях проверки протокола не могут быть то, что мы хотим. Очевидные причины, приходящие к уму, являются:

  1. Мы не можем проверить желаемую семантику, используя трюк протокола.
  2. Мы хотим разрушить хаос.

Для тех случаев (ну, в основном для первого) ABC Базовый класс обеспечивает еще один трюк. Вместо определения __subclasshook__ Чтобы проверить интерфейс, мы можем простые зарегистрированные классы как действительные, «виртуальные подклассы».

from abc import ABC

class IDuck(ABC): pass

class Duck:
    def quack(self): print('Quack!')
    def walk(self): print('Walk like a duck.')

IDuck.register(Duck)

def poke(duck):
    assert isinstance(duck, IDuck)
    duck.quack()
    duck.walk()

Помните, что этот метод ставит все давление на программист. Писать Iduck.register (собака) Это эквивалент заявления «Я поручил эту собаку, чтобы быть уткой». Это может пройти проверку, но не обязательно принесет желаемые результаты.

Резюме

В этой статье мы накрыли несколько способов проверки «утки» объектов. От принадлежности к роду Анаса просто разместил наклейку на голову, говоря «утки!». Некоторые из этих методов более полезны или применимы, чем другие, но я все еще думаю, что стоит знать со всеми из них. Кроме того, есть много тем, которые не покрываются здесь, как проверка статического типа.

Дальнейшее чтение

Методы метакласс, продемонстрированные здесь, упрощенные версии кода из ABC и Набрав модули. Я настоятельно рекомендую пройти через эти модули и их соответствующие документы, по крайней мере, сразу, чтобы расширить свои знания и покрывать любые отверстия, оставленные моими ручными волнистыми объяснениями.

Оригинал: “https://dev.to/tmr232/recognizing-ducks-n4c”