что такое duck typing

Утипизация в C#

Многогранный Шерлок Холмс и Эраст Фандорин, идеальный аристократ, очень чтили дедуктивный метод и оба достигли в его применении потрясающих успехов. «Отбросьте все невозможное, то, что останется, и будет ответом, каким бы невероятным он ни казался» — так говорил сэр Артур Конан Дойль устами своего героя.

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

Тест на утку

Индукция — это процесс логических умозаключений, основывающийся на переходе от частных положений к общему. Известный «тест на утку» является ярким примером подобного процесса:

If it walks like a duck and quacks like a duck, it must be a duck
(Если что-то ходит, как утка, и крякает, как утка, то это утка)

Применительно к языкам программирования — в частности, к объектно-ориентированным — «тест на утку» приобретает особый смысл. В динамически типизированных языках семантика использования того или иной объекта определяется текущим набором его методов и свойств. К примеру, в статически типизированном языке, не поддерживающем утипизацию, можно создать функцию, принимающую в качестве параметра объект класса Duck и вызывающую методы Quack и Walk этого объекта. В утипизированном же (и, как следствие, в динамически типизированном) языке аналогичная функция может принимать в качестве параметра некий абстрактный объект и попытаться вызывать у него те же методы (возможно, проверив их наличие). В статически типизированном языке подобного поведения можно достигнуть с использованием наследования и реализации интерфейсов, но это будет выражением формы is-a, в отличие от has-a в динамических языках.

Вкратце объяснив, что же такое утипизация и почему она недоступна в статически типизированных языках, я сейчас слегка опровергну сам себя. Для кого-то, возможно, это не будет откровением, но тем не менее. Некое подобие утиной типизации есть и в C#. Вот об этом сейчас поподробней.

foreach

Если открыть C# Language Specification и заглянуть в раздел 8.8.4, то можно увидеть следующее:

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

using System;
using System.Collections. Generic ;

namespace DuckTyping
<
class Collection
<
public Enumerator GetEnumerator()
<
return new Enumerator();
>
>

class Enumerator
<
private bool moveNext = true ;

public bool MoveNext()
<
if (!moveNext)
return false ;

moveNext = false ;
return true ;
>
>

Collection Initializers

Довольно приятное нововведение C# 3.0. Напомню:

Все бы ничего, но осталось определить термин «Collection». И вот с этим возникли проблемы.

Пункт «наличие хотя бы одного. » требует небольшого пояснения. Заключенный в фигурные скобки список элементов не является «списком элементов, которые надо добавить» — это «список аргументов для методов Add() ». Список аргументов, таким образом, может быть гетерогенным. Другими словами, имея такой класс:

вполне легальным будет следующий инициализатор:

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

Вместо заключения

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

Источник

«Duck typing» и C#

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

Как этим пользоваться

Представим что у нас есть следующий пример:

Компилятор С# скажет следующее:

Argument type ‘AddCalculator’ is not assignable to parameter type ‘ICalculator’

И это всё. Ошибок компиляции больше нет и все работает как часы.

Как это работает

Здесь используются два независимых генератора. Первый ищет аттрибут Duckable и генерит «базовый» класс для интерфейса. Например, для ICalculator он будет иметь следующий вид:

Второй генератор ищет вызовы методов и присваивания чтобы понять как duckable интерфейс используется. Расмотрим следующий пример:

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

Что не работает

На этом хорошие новости закончились. Всё-таки реализовать прямо вездесущий duck typing не получится. Поскольку мы скованы самим компилятором. А именно будут проблемы с дженериками и ref struct-урами. В теории часть проблем с дженериками можно починить, но не все. Например, было бы прикольно чтобы мы могли использовать наши интерфейсы вместе с where как-то вот так:

В таком случае мы могли бы получили прямо zero cost duct typing(и щепотку метапрограмирования, если копнуть глубже), поскольку, мы легко можем заменить partial class на partial struct в реализации нашего duck интерфейса. В результате, было бы сгенерено множестао Do методов для каждого уникального TCalcualtor как это происходит со структурами. Но увы, компилятор нам скажет, что ничего такого он не умеет.
На этом все. Спасибо за внимание!

Источник

что такое duck typing. Смотреть фото что такое duck typing. Смотреть картинку что такое duck typing. Картинка про что такое duck typing. Фото что такое duck typing

Утиная типизация

Опытным программистам концепция утиной типизации наверняка знакома. Для новичков же это словосочетание может звучать довольно странно: какое отношение имеют утки к программированию?

Эта концепция адаптирована из следующего абдуктивного умозаключения:

Если что-то выглядит как утка, плавает как утка и крякает как утка, это наверняка и есть утка.

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

Но шутки в сторону, какое отношение утиная типизация имеет к программированию, особенно к Python — языку, интересующему нас в этой статье?

Динамическая и статическая типизация

Концепция утиной типизация в основном принята в языках программирования, поддерживающих динамическую типизацию, таких как Python и JavaScript. Общей особенностью этих языков является возможность объявления переменных без указаниях их типа. Позднее в коде при желании можно назначать другие типы данных этим переменным. Ниже приведены некоторые примеры на Python:

Однако многие языки, такие как Java или Swift, поддерживают статическую типизацию. При объявлении переменной в них необходимо указывать тип данных для этой переменной. Впоследствии, если мы захотим изменить тип данных, компилятор не позволит это сделать, поскольку это несовместимо с изначальным объявлением. Пример ниже демонстрирует, что произойдёт, когда я попытаюсь сделать то же самое на Swift:

что такое duck typing. Смотреть фото что такое duck typing. Смотреть картинку что такое duck typing. Картинка про что такое duck typing. Фото что такое duck typing

Теоретический пример

В разделе выше мы упоминали, что Python является языком с динамической типизацией, как было показано на самом простом примере, включающем встроенные типы данных. Однако динамическую типизацию можно применять и на пользовательских типах. Давайте рассмотрим теоретический пример ниже:

Принимая во внимание эти наблюдения, стоит понимать основные обозначения утиной типизации. При использовании пользовательских типов для определённых целей, реализация связанных функций важнее, чем точные типы данных. В нашем примере, хоть роботизированная птица и не является настоящей уткой, её реализация функции swim_quack “превращает” её в утку — животное, которое плавает и крякает.

Практические примеры

Итераторы

В Python итерация позволяет перебирать список элементов для выполнения определённых операций. Одним из распространённых способов создания итераций является цикл, имеющий следующий общий формат:

Вызываемые

Сортировка с Len()

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

Заключение

В этой статье мы рассмотрели, что такое утиная типизация, и её связь с динамической типизацией в Python и других языках программирования. На теоретическом примере мы рассмотрели, что означает утиная типизация в Python. Кроме того, я представил три конкретных примера, которые вы можете применять в своих проектах на Python.

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

Источник

Протоколы в Python: утиная типизация по-новому

В новых версиях Python аннотации типов получают всё большую поддержку, всё чаще и чаще используются в библиотеках, фреймворках, и проектах на Python. Помимо дополнительной документированности кода, аннотации типов позволяют таким инструментам, как mypy, статически произвести дополнительные проверки корректности программы и выявить возможные ошибки в коде. В этой статье пойдет речь об одной, как мне кажется, интересной теме, касающейся статической проверки типов в Python – протоколах, или как сказано в PEP-544, статической утиной типизации.

что такое duck typing. Смотреть фото что такое duck typing. Смотреть картинку что такое duck typing. Картинка про что такое duck typing. Фото что такое duck typing

Содержание

Утиная типизация

Часто, когда речь заходит о Python, всплывает фраза утиная типизация, или даже что-нибудь вроде:

Если это выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, и есть утка

Утиная типизация – это концепция, характерная для языков программирования с динамической типизацией, согласно которой конкретный тип или класс объекта не важен, а важны лишь свойства и методы, которыми этот объект обладает. Другими словами, при работе с объектом его тип не проверяется, вместо этого проверяются свойства и методы этого объекта. Такой подход добавляет гибкости коду, позволяет полиморфно работать с объектами, которые никак не связаны друг с другом и могут быть объектами разных классов. Единственное условие, чтобы все эти объекты поддерживали необходимый набор свойств и методов.

Но именно эта гибкость и усложняет раннее обнаружение ошибок типизации. Корректность использования объектов определяется динамически, в момент выполнения программы, и зачастую тестирование – единственный способ отловить подобные ошибки. Статическая проверка типов и корректности программы в данном случае представляет значительную сложность.

Номинальная типизация

Рассмотрим небольшой пример:

Проверка совместимости типов в соответствии с номинальной типизацией и иерархией наследования существует во многих языках программирования. Например, Java, C#, C++ и многие другие языки используют номинальную систему типов.

Структурная типизация

Структурная типизация (structural type system) определяет совместимость типов на основе структуры этих типов, а не на явных декларациях. Подобный механизм может рассматриваться как некоторый аналог утиной типизации, но для статических проверок, в некотором смысле compile time duck typing.

Структурная типизация также довольно широко распространена. Например, интерфейсы в Go – это набор методов, которые определяют некоторую функциональность. Типы, реализующие интерфейсы в Go не обязаны декларировать каким-либо образом, что они реализуют данный интерфейс, достаточно просто реализовать соответствующие методы интерфейса.

Другой пример – это TypeScript, который также использует структурную систему типов:

Python и протоколы

Начиная с версии Python 3.8 (PEP-544), появляется новый механизм протоколов для реализации структурной типизации в Python. Термин протоколы давно существует в мире Python и хорошо знаком всем, кто работает с языком. Можно вспомнить, например, протокол итераторов, протокол дескрипторов, и несколько других.

Новые протоколы в некотором смысле «перегружают» уже устоявшийся термин, добавляя возможность структурно проверять совместимость типов при статических проверках (с помощью, например, mypy). В момент исполнения программы, протоколы в большинстве случаев не имеют какого-то специального значения, являются обычными абстрактными классами ( abc.ABC ), и не предназначены для инстанциирования объектов напрямую.

Рассмотрим следующий пример:

Mypy сообщит нам об ошибке, если переданный в функцию iterate_by объект не будет поддерживать протокол итераций (напомню, у объекта должен быть метод __iter__ возвращающий итератор).

Если мы объявим собственный класс, который будет поддерживать протокол итераций, то mypy сможет точно так же, статически проверить соответствие объектов нашего класса заявленному протоколу.

В стандартной библиотеке (в модуле typing ) определено довольно много протоколов для статических проверок. Полный список и примеры использования встроенных протоколов можно посмотреть в документации mypy.

Пользовательские протоколы

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

Пример использования

Разберем небольшой пример использования пользовательских протоколов:

Если реализация протокола будет некорректной, то mypy сообщит об ошибке:

В данном примере mypy не только сообщает об ошибке в коде программы, но и подсказывает какой метод протокола не реализован (или реализован неправильно).

Явная имплементация протокола

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

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

Декоратор runtime_checkable

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

Несколько слов в заключение

Новые протоколы являются продолжением идеи утиной типизации в современном Python, в котором инструменты статической проверки типов заняли довольно важное место. Mypy и другие подобные инструменты теперь имеют возможность использовать структурную типизацию в дополнение к номинальной для проверки корректности кода на Python. Кроме того, если вы используете аннотации и проверки типов в своих проектах, то новые протоколы могут сделать код более гибким, сохраняя при этом безопасность и уверенность, которую дает статическая типизация.

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

Примечания

Все примеры рассмотренные в статье проверялись в Python 3.9 / mypy 0.812.

Источник

Что такое утиная типизация в Python?

В этом уроке мы узнаем что такое утиная типизация в Python. Это популярный термин, и он происходит от высказывания: «Если он ходит как утка, плавает как утка, выглядит как утка, то, вероятно, это должна быть утка».

Приведенное выше утверждение дает идею идентифицировать утку. Здесь нам не нужно иметь геномную последовательность утки. Мы делаем вывод по ее поведению и внешнему виду. Мы обсудим, что именно означает «duck typing» в программировании на Python.

Python следует принципу EAFP(проще просить прощения, чем разрешения), а не философии LBLY(смотрите, прежде чем прыгать). EAFP в некоторой степени связан со стилем «утиного набора текста».

Динамический и статический набор текста

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

Как видно из приведенного выше кода, мы присвоили целое число переменной x, сделав ее типом int. Затем мы присвоили строку и список одной и той же переменной. Интерпретатор Python принимает изменения типов данных одной и той же переменной. Это поведение динамической печати.

Многие другие языки программирования, такие как Java, swift, относятся к статическому типу. Нам нужно объявить переменную с типами данных. В приведенном ниже примере мы пытаемся сделать то же самое, используя Swift вместо Python.

Вышеупомянутый код не может быть скомпилирован, потому что мы не могли назначить строку на языке Swift. Потому что переменная a была объявлена как целое число.

Концепция утиного набора текста

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

В приведенном выше коде мы создали класс VisualStudio, который должен выполнять метод execute(). В классе десктопов мы передали ide в качестве аргумента в code(). Ide – это объект класса VisualStudio. С помощью ide мы вызвали метод execute() класса VisualStudio.

Посмотрим на другой пример.

В приведенном выше коде экземпляр класса Duck отражается путем вызова функции duck_testing. То же самое происходит с классом Sparrow, который реализует функцию swim(). Но в случае класса Crocodile он не проходит оценку тестирования утки, потому что он не реализует функцию swim().

Как утиная печать поддерживает EAFP

Утиная печать – наиболее подходящий стиль для EAFP, потому что нам не нужно сосредотачиваться на «типе» объекта. Нам нужно только позаботиться о его поведении и возможностях. Посмотрим на следующие утверждения. Когда мы видим много блоков if-else, это стиль кодирования LBYL. Но если мы видим много блоков try-except, то это скорее всего кодер EAFP.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *