LINUX.ORG.RU

Ищу аналог union/enum/adt для Python/Go

 , ,


1

2

Интересуют не либы, а идиоматическое решение задачи вида:

Rust:

enum Data {
    Number(u32),
    String(String),
}

let mut items = Vec::new();
items.push(Data::Number(5));
items.push(Data::String("text".to_string()));

for item in items {
    match item {
        Data::Number(n) => println!("Number {}", n),
        Data::String(ref s) => println!("String {}", s),
    }
}

C++:

std::vector<std::variant<int, std::string>> items;
items.push_back(5);
items.push_back("text");

for (const auto &item : items) {
    if (const auto n = std::get_if<int>(&item)) {
        std::cout << "Number " << *n << std::endl;
    } else if (const auto &s = std::get_if<std::string>(&item)) {
        std::cout << "String " << *s << std::endl;
    }
}

Как это повторить на python и go?

То есть нужно заполнить вектор/список/массив объектами разных типов (желательно с гарантиями) и затем пробежаться по ним, забирая значение каждого типа.

★★★★★

Последнее исправление: RazrFalcon (всего исправлений: 1)

не очень ясно, что ты хочешь

type switch

var a interface{}

switch t := a.(type) {
    case string:
        fmt.Println("String %v", t)
    case int:
        fmt.Println("Number %v", t)
    case SomeInterface:
        // ...
    default:
        fmt.Println("Another type %T", t)
}

enum

type EnumType int

const (
    EnumValue1 = iota
    EnumValue2
    // ...
)

switch enumVar {
    case EnumValue1:
        fmt.Println("Value1")
    case EnumValue2:
        fmt.Println("Value2")
    // ...
}
derlafff ★★★★★
()
Последнее исправление: derlafff (всего исправлений: 1)

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

проверить тип с помощью isinstance().

ну и да, switch - case в питоне нет. в данном случае просто цепочка if - elif сработает

eternal_sorrow ★★★★★
()

Даже вариант на Си++ не эквивалентен коду на Rust. Эквивалент невозможно написать ни на чем, кроме статически типизированных языков с ADT (окей, на Си++ можно написать более похоже).

tailgunner ★★★★★
()
Ответ на: комментарий от RazrFalcon

Я не могу гарантировать что там только нужные типы?

Можешь.

Вместо пустого интерфейса используй свой. Во всех своих типах его реализуй.

Правда, не ясно, зачем. Только если потешить своё эго, на практике не нужно

derlafff ★★★★★
()
Последнее исправление: derlafff (всего исправлений: 2)
Ответ на: комментарий от eternal_sorrow

А как у него с производительностью? Или других способов всё равно нет.

Хотелось бы не любые типы пихать, а только конкретные. Если это возможно.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от eternal_sorrow

В список можно класть объекты любых типов, в том числе разнотипные. хоть это и не очень правильно, для этого есть tuple

tuple совершенно точно не для той задачи, которую предлагает ТС. Ему нужно, чтобы объект содержал ровно одно значение, которое может принадлежать только одному из перечисленных типов. В Python нет возможности это выразить.

tailgunner ★★★★★
()

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

def print_var(var):
    if type(var) == str:
        print('String: '+var)
    elif type(var) == int or type(var) == float:
        print('Number: '+str(var))

var = 'string'
print_var(var)
var = 123
print_var(var)
hippi90 ★★★★★
()
Ответ на: комментарий от tailgunner

Это просто пример. Оно не должно быть абсолютно идентичным. Задача состоит в том, что нужно запихать в вектор/список/массив разные данные, а потом по ним пробежаться.

PS: в чём конкретно не эквивалентность?

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от derlafff

Я даже не знаю с какой стороны этот код читать. Нужно потренироваться чтобы осилить.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

А как у него с производительностью?

У кого? У CPython'а, к сожалению, никак. Если тебе нужна очень высокая производительность, то от питона придётся отказаться.

Хотелось бы не любые типы пихать, а только конкретные.

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

eternal_sorrow ★★★★★
()
Ответ на: комментарий от RazrFalcon

isinstance в данном случае будет каноничнее, различия будут при использовании объектов производных классов:

class A:
    pass

class B(A):
    pass

o = B()

print(type(o) == A) # False
print(isinstance(o, A)) # True

hippi90 ★★★★★
()
Ответ на: комментарий от RazrFalcon

Задача состоит в том, что нужно запихать в вектор/список/массив разные данные, а потом по ним пробежаться.

В Rust это не «разные данные», а строго указанные. А в Python - в самом деле просто разные, любые.

PS: в чём конкретно не эквивалентность?

В примере на Си++ ты можешь пропустить проверку одного из вариантов и компилятор ничего не скажет.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

В примере на Си++ ты можешь пропустить проверку одного из вариантов и компилятор ничего не скажет.

Можно это обойти?

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon
items = []
items.append(5)
items.append("text")

for item in items:
    if isinstance(item, int):
        print(f"Number {item}")
    elif isinstance(item, str):
        print(f"String {item}")
eternal_sorrow ★★★★★
()
Ответ на: комментарий от RazrFalcon

В примере на Си++ ты можешь пропустить проверку одного из вариантов и компилятор ничего не скажет.

Можно это обойти?

Если сделать свой sum type, обмазаться макросами и использовать факт, что компилятор предупреждает об отсутствующем варианте в switch по enum - да. Может, и для variant такое можно провернуть.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Ну я чисто про variant. У него есть variant_size, поэтому можно провернуть костыль на switch, но это будет выглядеть жутко.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

Ну, со своим вариантом я еще не встречал проблем, которые не сломали бы компиляцию. Нетривиальных сломов не припоминаю.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Почему нет то?


In [1]: class MyList(list):
   ...:     def __init__(self, types, lst=[]):
   ...:         self._types = types
   ...:         for el in lst:
   ...:             self._check_type(el)
   ...:         super().__init__(lst)
   ...:     def append(self, el):
   ...:         self._check_type(el)
   ...:         super().append(el)
   ...:     def insert(self, i, el):
   ...:         self._check_type(el)
   ...:         super().insert(i, el)
   ...:     def __setitem__(self, i, el):
   ...:         self._check_type(el)
   ...:         super().__setitem__(i, el)
   ...:     def extend(self, lst):
   ...:         for el in lst:
   ...:             self._check_type(el)
   ...:         super().extend(lst)
   ...:     def _check_type(self, el):
   ...:         if type(el) not in self._types:
   ...:             raise TypeError('{} is not in {}'.format(el, self._types))
   ...:

In [2]: l = MyList((str, int), [])

In [3]: l.append(2)

In [4]: l.append(2.0)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-736d236

pawnhearts ★★★★★
()
Ответ на: комментарий от derlafff

Спасибо. Как минимум, такое решение молча допускает код, который обрабатывает не все варианты. Наверняка оно еще допускает и расширение набора допустимых типов, так что... это не эквивалент.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Не эквивалент, да. это все интересно только в академических целях, на практике хватает обычного interface или выбрать другой язык (если так хочется дрочить на корректность на этапе компиляции)

ещё можно посмотреть, мб что-то с кодогенерацией на эту тему придумали

derlafff ★★★★★
()
Последнее исправление: derlafff (всего исправлений: 2)
Ответ на: комментарий от Deleted

До discriminated unions из TypeScript, конечно, далеко, но хоть что-то

Deleted
()
Ответ на: комментарий от tailgunner

расслабься

для слабаков. только хейтерство, только баттхерт, только ЛОР

вопрос был про go

Наверняка оно еще допускает и расширение набора допустимых типов

Кстати да, интересно, можно ли реализовать интерфейс, если в нем только приватный метод и он объявлен в другом пакете? Если нельзя, остается только решить вопрос с «дает возможность забыть об одном из типов в switch»

derlafff ★★★★★
()
Последнее исправление: derlafff (всего исправлений: 3)
Ответ на: комментарий от derlafff

расслабься

для слабаков. только хейтерство, только баттхерт, только ЛОР

ДА!!!111

tailgunner ★★★★★
()
Ответ на: комментарий от pawnhearts

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

Почему нет то?

Ты прав, можно - реализовав свой класс с динамическими проверками на каждый чих. Хотя, с другой стороны, variant тоже в некотором смысле «свой класс».

tailgunner ★★★★★
()

Для питона общий алгоритм примерно такой будет - конвертишь Enum в trait (т.е. каждому мемберу энума делаешь struct, делаешь trait с сигнатурами общих функций, для каждого структа impl trait for), потом для каждого трейта создаешь питоновский класс. Дальше duck typing. понятное дело, о статических проверках только мечтаешь, сигнатуры типов в питоне для документации.

provaton ★★★★★
()
Ответ на: комментарий от derlafff

Кстати да, интересно, можно ли реализовать интерфейс, если в нем только приватный метод и он объявлен в другом пакете?

Нет, если в интерфейсе есть приватное поле, то реализовать его можно только в пакете, где он объявлен

nikolnik ★★★
()
Ответ на: комментарий от provaton

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

да ладно

(1) ph@mbp ~> cat re.py
from typing import List, Union

x = [] # type: List[Union[int, float]]

x.append(1)
x.append(2.0)
x.append('a')

ph@mbp ~> mypy re.py
re.py:7: error: Argument 1 to "append" of "list" has incompatible type "str"; expected "Union[int, float]"

pawnhearts ★★★★★
()
Ответ на: комментарий от tailgunner

Куда-то ж этот энум должен передаваться. Вот собственно те функции, куда он передается и будут общими.

Я осознаю, что это неважное решение. Энумы - одна из причин, почему мне хочется смигрировать на rust с питона...

provaton ★★★★★
()
Ответ на: комментарий от RazrFalcon

Вариант с type() менее универсален. Например, если мемберы энума имеют по два значения, то код на тайпах выйдет достаточно индусско-китайским.

provaton ★★★★★
()
Ответ на: комментарий от provaton

Куда-то ж этот энум должен передаваться.

Не факт. Возможно, он откуда-то приходит, матчится, и потом «куда-то» передаются его компоненты.

Вот собственно те функции, куда он передается и будут общими.

Но общих функций у него может и не быть.

tailgunner ★★★★★
()
Последнее исправление: tailgunner (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.