Замены для оператора switch в Python?

Я хочу написать функцию в Python, которая возвращает разные фиксированные значения на основе значения входного индекса.

В других языках я бы использовал оператор switch или case, но у Python не было оператора switch. Каковы рекомендуемые решения Python в этом сценарии?

+1707
13 сент. '08 в 0:36
источник поделиться
52 ответа
  • 1
  • 2

Вы можете использовать словарь:

def f(x):
    return {
        'a': 1,
        'b': 2,
    }[x]
+1324
13 сент. '08 в 0:38
источник

Связанные вопросы


Похожие вопросы

Если вы хотите по умолчанию использовать словарь get(key[, default]) метод:

def f(x):
    return {
        'a': 1,
        'b': 2
    }.get(x, 9)    # 9 is default if x not found
+1245
19 сент. '08 в 15:45
источник

Мне всегда нравилось это делать

result = {
  'a': lambda x: x * 5,
  'b': lambda x: x + 7,
  'c': lambda x: x - 2
}[value](x)

Отсюда

+347
13 сент. '08 в 0:41
источник

В дополнение к методам словаря (что мне очень нравится, BTW) вы также можете использовать if-elif-else для получения функциональности switch/case/default:

if x == 'a':
    # Do the thing
elif x == 'b':
    # Do the other thing
if x in 'bc':
    # Fall-through by not using elif, but now the default case includes case 'a'!
elif x in 'xyz':
    # Do yet another thing
else:
    # Do the default

Это, конечно, не идентично переключателю/случаю - вы не можете провалиться так же легко, как оставить инструкцию break;, но у вас может быть более сложный тест. Его форматирование лучше, чем серия вложенных ifs, даже если функционально то, к чему оно ближе.

+296
13 сент. '08 в 1:10
источник

Мой любимый рецепт Python для переключателя/случая:

choices = {'a': 1, 'b': 2}
result = choices.get(key, 'default')

Короткий и простой для простых сценариев.

Сравните с 11 + строками кода C:

// C Language version of a simple 'switch/case'.
switch( key ) 
{
    case 'a' :
        result = 1;
        break;
    case 'b' :
        result = 2;
        break;
    default :
        result = -1;
}

Вы даже можете назначить несколько переменных, используя кортежи:

choices = {'a': (1, 2, 3), 'b': (4, 5, 6)}
(result1, result2, result3) = choices.get(key, ('default1', 'default2', 'default3'))
+155
17 июн. '15 в 2:25
источник
class switch(object):
    value = None
    def __new__(class_, value):
        class_.value = value
        return True

def case(*args):
    return any((arg == switch.value for arg in args))

Использование:

while switch(n):
    if case(0):
        print "You typed zero."
        break
    if case(1, 4, 9):
        print "n is a perfect square."
        break
    if case(2):
        print "n is an even number."
    if case(2, 3, 5, 7):
        print "n is a prime number."
        break
    if case(6, 8):
        print "n is an even number."
        break
    print "Only single-digit numbers are allowed."
    break

Тесты:

n = 2
#Result:
#n is an even number.
#n is a prime number.
n = 11
#Result:
#Only single-digit numbers are allowed.
+96
07 июл. '11 в 6:09
источник

Вот шаблон, который я узнал из кода Twisted Python.

class SMTP:
    def lookupMethod(self, command):
        return getattr(self, 'do_' + command.upper(), None)
    def do_HELO(self, rest):
        return 'Howdy ' + rest
    def do_QUIT(self, rest):
        return 'Bye'

SMTP().lookupMethod('HELO')('foo.bar.com') # => 'Howdy foo.bar.com'
SMTP().lookupMethod('QUIT')('') # => 'Bye'

Вы можете использовать его в любое время, когда вам нужно отправить маркер и выполнить расширенный код. В машине состояния у вас есть методы state_ и отправьте на self.state. Этот коммутатор может быть просто расширен путем наследования от базового класса и определения ваших собственных методов do_. Часто у вас даже не будет методов do_ в базовом классе.

Изменить: как именно используется

В случае SMTP вы получите HELO от провода. Соответствующий код (из twisted/mail/smtp.py, измененный для нашего случая) выглядит следующим образом:

class SMTP:
    # ...

    def do_UNKNOWN(self, rest):
        raise NotImplementedError, 'received unknown command'

    def state_COMMAND(self, line):
        line = line.strip()
        parts = line.split(None, 1)
        if parts:
            method = self.lookupMethod(parts[0]) or self.do_UNKNOWN
            if len(parts) == 2:
                return method(parts[1])
            else:
                return method('')
        else:
            raise SyntaxError, 'bad syntax'

SMTP().state_COMMAND('   HELO   foo.bar.com  ') # => Howdy foo.bar.com

Вы получите ' HELO foo.bar.com ' (или вы можете получить 'QUIT' или 'RCPT TO: foo'). Это обозначается как parts как ['HELO', 'foo.bar.com']. Фактическое имя поиска метода взято из parts[0].

(Исходный метод также называется state_COMMAND, поскольку он использует тот же шаблон для реализации конечного автомата, т.е. getattr(self, 'state_' + self.mode))

+45
13 сент. '08 в 1:26
источник

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

class switch(object):
    def __init__(self, value):
        self.value = value
        self.fall = False

    def __iter__(self):
        """Return the match method once, then stop"""
        yield self.match
        raise StopIteration

    def match(self, *args):
        """Indicate whether or not to enter a case suite"""
        if self.fall or not args:
            return True
        elif self.value in args: # changed for v1.5, see below
            self.fall = True
            return True
        else:
            return False

Вот пример:

# The following example is pretty much the exact use-case of a dictionary,
# but is included for its simplicity. Note that you can include statements
# in each suite.
v = 'ten'
for case in switch(v):
    if case('one'):
        print 1
        break
    if case('two'):
        print 2
        break
    if case('ten'):
        print 10
        break
    if case('eleven'):
        print 11
        break
    if case(): # default, could also just omit condition or 'if True'
        print "something else!"
        # No need to break here, it'll stop anyway

# break is used here to look as much like the real thing as possible, but
# elif is generally just as good and more concise.

# Empty suites are considered syntax errors, so intentional fall-throughs
# should contain 'pass'
c = 'z'
for case in switch(c):
    if case('a'): pass # only necessary if the rest of the suite is empty
    if case('b'): pass
    # ...
    if case('y'): pass
    if case('z'):
        print "c is lowercase!"
        break
    if case('A'): pass
    # ...
    if case('Z'):
        print "c is uppercase!"
        break
    if case(): # default
        print "I dunno what c was!"

# As suggested by Pierre Quentel, you can even expand upon the
# functionality of the classic 'case' statement by matching multiple
# cases in a single shot. This greatly benefits operations such as the
# uppercase/lowercase example above:
import string
c = 'A'
for case in switch(c):
    if case(*string.lowercase): # note the * for unpacking as arguments
        print "c is lowercase!"
        break
    if case(*string.uppercase):
        print "c is uppercase!"
        break
    if case('!', '?', '.'): # normal argument passing style also applies
        print "c is a sentence terminator!"
        break
    if case(): # default
        print "I dunno what c was!"
+42
07 июл. '11 в 6:12
источник
class Switch:
    def __init__(self, value):
        self.value = value

    def __enter__(self):
        return self

    def __exit__(self, type, value, traceback):
        return False # Allows a traceback to occur

    def __call__(self, *values):
        return self.value in values


from datetime import datetime

with Switch(datetime.today().weekday()) as case:
    if case(0):
        # Basic usage of switch
        print("I hate mondays so much.")
        # Note there is no break needed here
    elif case(1,2):
        # This switch also supports multiple conditions (in one line)
        print("When is the weekend going to be here?")
    elif case(3,4):
        print("The weekend is near.")
    else:
        # Default would occur here
        print("Let go have fun!") # Didn't use case for example purposes
+39
03 мая '15 в 9:05
источник

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

result = {
  'a': obj.increment(x),
  'b': obj.decrement(x)
}.get(value, obj.default(x))

Что здесь происходит, так это то, что python оценивает все методы в словаре. Поэтому, даже если ваше значение равно "a", объект будет увеличиваться и на x.

Решение:

func, args = {
  'a' : (obj.increment, (x,)),
  'b' : (obj.decrement, (x,)),
}.get(value, (obj.default, (x,)))

result = func(*args)

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

+24
30 сент. '10 в 8:31
источник

Я просто собираюсь бросить здесь свои два цента. Причина в том, что в Python нет оператора case/switch, потому что Python следует принципу "Theres только один правильный способ сделать что-то". Таким образом, очевидно, что вы можете придумать различные способы воссоздания функциональности переключателя/случая, но питонический способ выполнения этого - построить if/elif. то есть

if something:
    return "first thing"
elif somethingelse:
    return "second thing"
elif yetanotherthing:
    return "third thing"
else:
    return "default thing"

Я просто почувствовал, что PEP 8 заслуживает похвалы. Одна из красивейших вещей о Python - его простота и элегантность. Это в значительной степени вытекает из принципов, заложенных в PEP 8, включая "Есть только один правильный способ сделать что-то"

+19
14 авг. '17 в 22:19
источник

расширяется по идее "dict as switch". если вы хотите использовать значение по умолчанию для вашего коммутатора:

def f(x):
    try:
        return {
            'a': 1,
            'b': 2,
        }[x]
    except KeyError:
        return 'default'
+16
19 сент. '08 в 15:37
источник

Если вы ищете дополнительный оператор, как "switch", я построил модуль python, который расширяет Python. Он назвал ESPY "Enhanced Structure for Python" и доступен как для Python 2.x, так и для Python 3.x.

Например, в этом случае оператор switch может быть выполнен следующим кодом:

macro switch(arg1):
    while True:
        cont=False
        val=%arg1%
        socket case(arg2):
            if val==%arg2% or cont:
                cont=True
                socket
        socket else:
            socket
        break

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

a=3
switch(a):
    case(0):
        print("Zero")
    case(1):
        print("Smaller than 2"):
        break
    else:
        print ("greater than 1")

поэтому espy переводит его в Python как:

a=3
while True:
    cont=False
    if a==0 or cont:
        cont=True
        print ("Zero")
    if a==1 or cont:
        cont=True
        print ("Smaller than 2")
        break
    print ("greater than 1")
    break
+15
06 дек. '10 в 15:08
источник

Если у вас есть сложный блок, вы можете рассмотреть таблицу поиска словаря функций...

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

ПРИМЕЧАНИЕ. Не используйте "()" внутри поиска в словаре/словаре или он будет вызывать каждую из ваших функций при создании блока словаря/случая. Помните это, потому что вы хотите только один раз вызвать каждую функцию, используя поиск стиля хеша.

def first_case():
    print "first"

def second_case():
    print "second"

def third_case():
    print "third"

mycase = {
'first': first_case, #do not use ()
'second': second_case, #do not use ()
'third': third_case #do not use ()
}
myfunc = mycase['first']
myfunc()
+15
22 апр. '12 в 21:43
источник

Я не нашел простой ответ, который я искал в любом месте в поиске Google. Но я все равно понял это. Это очень просто. Решил опубликовать его и, возможно, предотвратить несколько менее царапин на кого-то другого. Ключ - это просто "в" и кортежи. Вот поведение оператора switch с провалом, включая RANDOM fall-through.

l = ['Dog', 'Cat', 'Bird', 'Bigfoot',
     'Dragonfly', 'Snake', 'Bat', 'Loch Ness Monster']

for x in l:
    if x in ('Dog', 'Cat'):
        x += " has four legs"
    elif x in ('Bat', 'Bird', 'Dragonfly'):
        x += " has wings."
    elif x in ('Snake',):
        x += " has a forked tongue."
    else:
        x += " is a big mystery by default."
    print(x)

print()

for x in range(10):
    if x in (0, 1):
        x = "Values 0 and 1 caught here."
    elif x in (2,):
        x = "Value 2 caught here."
    elif x in (3, 7, 8):
        x = "Values 3, 7, 8 caught here."
    elif x in (4, 6):
        x = "Values 4 and 6 caught here"
    else:
        x = "Values 5 and 9 caught in default."
    print(x)

Содержит:

Dog has four legs
Cat has four legs
Bird has wings.
Bigfoot is a big mystery by default.
Dragonfly has wings.
Snake has a forked tongue.
Bat has wings.
Loch Ness Monster is a big mystery by default.

Values 0 and 1 caught here.
Values 0 and 1 caught here.
Value 2 caught here.
Values 3, 7, 8 caught here.
Values 4 and 6 caught here
Values 5 and 9 caught in default.
Values 4 and 6 caught here
Values 3, 7, 8 caught here.
Values 3, 7, 8 caught here.
Values 5 and 9 caught in default.
+13
12 окт. '13 в 15:04
источник

Я обнаружил, что общая структура коммутатора:

switch ...parameter...
case p1: v1; break;
case p2: v2; break;
default: v3;

может быть выражен в Python следующим образом:

(lambda x: v1 if p1(x) else v2 if p2(x) else v3)

или отформатирован более четко:

(lambda x:
     v1 if p1(x) else
     v2 if p2(x) else
     v3)

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

+13
02 янв. '15 в 18:01
источник

Решения, которые я использую:

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

result = {
  'a': lambda x: x * 5,
  'b': lambda x: x + 7,
  'c': lambda x: x - 2
}.get(whatToUse, lambda x: x - 22)(value)

где

.get('c', lambda x: x - 22)(23)

просматривает "lambda x: x - 2" в dict и использует его с x=23

.get('xxx', lambda x: x - 22)(44)

не находит его в dict и использует значение по умолчанию "lambda x: x - 22" с x=44.

+11
28 июн. '10 в 1:32
источник

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

Во-первых, официальные Python FAQ затрагивают это и рекомендуют цепочку elif для простых случаев и dict для более крупных или более сложных случаев. Он также предлагает набор методов visit_ (стиль, используемый многими серверными фреймворками) для некоторых случаев:

def dispatch(self, value):
    method_name = 'visit_' + str(value)
    method = getattr(self, method_name)
    method()

В FAQ также упоминается PEP 275, который был написан, чтобы получить официальное единогласное решение о добавлении операторов переключения C-стиля. Но этот PEP был фактически отложен на Python 3, и он был официально отклонен как отдельное предложение PEP 3103. Ответ был, конечно, не-но, но у двух ПИР есть ссылки на дополнительную информацию, если вас интересуют причины или история.


Одна вещь, которая возникла несколько раз (и может быть замечена в PEP 275, хотя она была вырезана как настоящая рекомендация) заключается в том, что если вы действительно обеспокоены тем, что 8 строк кода обрабатывают 4 случая, то против 6 линии, которые у вас есть на C или Bash, вы всегда можете написать это:

if x == 1: print('first')
elif x == 2: print('second')
elif x == 3: print('third')
else: print('did not place')

Это не совсем поощряется PEP 8, но оно читаемо и не слишком унииоматично.


В течение более чем десятилетия с тех пор, как PEP 3103 был отклонен, проблема сложениями в стиле C или даже более мощная версия Go, считалась мертвой; всякий раз, когда кто-либо выводит его на python -ideas или -dev, они ссылаются на старое решение.

Однако идея полного сопоставления шаблонов ML-типа возникает каждые несколько лет, особенно потому, что такие языки, как Swift и Rust, приняли это. Проблема в том, что трудно получить много пользы от сопоставления шаблонов без алгебраических типов данных. Хотя Гвидо был сочувствующим этой идее, никто не придумал предложение, которое очень хорошо вписывается в Python. (Вы можете прочитать мой пример соломы 2014 года.) Это может измениться с помощью dataclass в 3.7 и некоторые спорадические предложения для более мощного enum для обработки типов сумм или с различными предложениями для разных видов локальных привязок операторов (например, PEP 3150, или набор предложений, которые в настоящее время обсуждаются на -ideas). Но пока это не так.

Есть также иногда предложения для сопоставления в стиле 6 в стиле Perl, что в основном представляет собой путаницу всего от elif до regex до однонаправленного переключения типов.

+10
10 апр. '18 в 6:13
источник
def f(x):
    dictionary = {'a':1, 'b':2, 'c':3}
    return dictionary.get(x,'Not Found') 
##Returns the value for the letter x;returns 'Not Found' if x isn't a key in the dictionary
+10
28 сент. '18 в 17:11
источник
# simple case alternative

some_value = 5.0

# this while loop block simulates a case block

# case
while True:

    # case 1
    if some_value > 5:
        print ('Greater than five')
        break

    # case 2
    if some_value == 5:
        print ('Equal to five')
        break

    # else case 3
    print ( 'Must be less than 5')
    break
+9
13 авг. '15 в 17:40
источник

Я сделал (относительно) гибкое и повторно используемое решение для этого. Его можно найти в GitHub как этот смысл. Если результат функции переключателя является вызываемым, он автоматически вызывается.

+7
05 июн. '09 в 14:20
источник

Мне понравился ответ Mark Bies

Так как переменная x должна использоваться дважды, я изменил функции лямбда на без параметров.

Мне нужно работать с results[value](value)

In [2]: result = {
    ...:   'a': lambda x: 'A',
    ...:   'b': lambda x: 'B',
    ...:   'c': lambda x: 'C'
    ...: }
    ...: result['a']('a')
    ...: 
Out[2]: 'A'

In [3]: result = {
    ...:   'a': lambda : 'A',
    ...:   'b': lambda : 'B',
    ...:   'c': lambda : 'C',
    ...:   None: lambda : 'Nothing else matters'

    ...: }
    ...: result['a']()
    ...: 
Out[3]: 'A'

Изменить: Я заметил, что могу использовать тип None со словарями. Таким образом, это будет эмулировать switch ; case else

+7
30 нояб. '14 в 10:12
источник
def f(x):
     return 1 if x == 'a' else\
            2 if x in 'bcd' else\
            0 #default

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

Однако он менее эффективен, чем решение со словарем. Например, Python должен проверять все условия перед возвратом значения по умолчанию.

+6
05 нояб. '12 в 20:05
источник

Решение для запуска функций:

result = {
    'case1':     foo1, 
    'case2':     foo2,
    'case3':     foo3,
    'default':   default,
}.get(option)()

где foo1(), foo2(), foo3() и default() являются функциями

+6
17 февр. '18 в 21:54
источник

Вы можете использовать отправленный диктовку:

#!/usr/bin/env python


def case1():
    print("This is case 1")

def case2():
    print("This is case 2")

def case3():
    print("This is case 3")


token_dict = {
    "case1" : case1,
    "case2" : case2,
    "case3" : case3,
}


def main():
    cases = ("case1", "case3", "case2", "case1")
    for case in cases:
        token_dict[case]()


if __name__ == '__main__':
    main()

Выход:

This is case 1
This is case 3
This is case 2
This is case 1
+5
06 февр. '19 в 8:49
источник

Простой, не проверенный; Каждое условие оценивается независимо: нет сквозного перехода, но оцениваются все случаи (хотя включаемое выражение вычисляется только один раз), если только нет оператора break. Например,

for case in [expression]:
    if case == 1:
        print(end='Was 1. ')

    if case == 2:
        print(end='Was 2. ')
        break

    if case in (1, 2):
        print(end='Was 1 or 2. ')

    print(end='Was something. ')

отпечатков Was 1. Was 1 or 2. Was something. (Черт возьми! Почему у меня не может быть конечных пробелов в блоках встроенного кода?), Если expression оценивается как 1, Was 2. если expression оценивается как 2, или Was something. если expression оценивает что-то еще.

+5
05 февр. '18 в 1:54
источник

Расширение на Greg Hewgill answer - Мы можем инкапсулировать словарное решение с помощью декоратора:

def case(callable):
    """switch-case decorator"""
    class case_class(object):
        def __init__(self, *args, **kwargs):
            self.args = args
            self.kwargs = kwargs

        def do_call(self):
            return callable(*self.args, **self.kwargs)

return case_class

def switch(key, cases, default=None):
    """switch-statement"""
    ret = None
    try:
        ret = case[key].do_call()
    except KeyError:
        if default:
            ret = default.do_call()
    finally:
        return ret

Затем это можно использовать с @case -decorator

@case
def case_1(arg1):
    print 'case_1: ', arg1

@case
def case_2(arg1, arg2):
    print 'case_2'
    return arg1, arg2

@case
def default_case(arg1, arg2, arg3):
    print 'default_case: ', arg1, arg2, arg3

ret = switch(somearg, {
    1: case_1('somestring'),
    2: case_2(13, 42)
}, default_case(123, 'astring', 3.14))

print ret

Хорошей новостью является то, что это уже сделано в NeoPySwitch -module. Просто установите с помощью pip:

pip install NeoPySwitch
+5
21 апр. '17 в 7:17
источник

Решение, которое я использую, также использует словари:

def decision_time( key, *args, **kwargs):
    def action1()
        """This function is a closure - and has access to all the arguments"""
        pass
    def action2()
        """This function is a closure - and has access to all the arguments"""
        pass
    def action3()
        """This function is a closure - and has access to all the arguments"""
        pass

   return {1:action1, 2:action2, 3:action3}.get(key,default)()

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

+5
06 июн. '18 в 16:45
источник

Я думаю, что лучший способ - использовать идиомы языка python, чтобы проверить ваш код. Как показано в предыдущих ответах, я использую словари для , используя преимущества структур python и языка, и сохраняю код "case" изолированным в разных методах. Ниже приведен класс, но вы можете напрямую использовать модуль, глобалы и функции. Класс имеет методы, которые можно протестировать с помощью изоляции. В зависимости от ваших потребностей вы также можете играть со статическими методами и атрибутами.

class ChoiceManager:

    def __init__(self):
        self.__choice_table = \
        {
            "CHOICE1" : self.my_func1,
            "CHOICE2" : self.my_func2,
        }

    def my_func1(self, data):
        pass

    def my_func2(self, data):
        pass

    def process(self, case, data):
        return self.__choice_table[case](data)

ChoiceManager().process("CHOICE1", my_data)

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

Предположим, вам нужно обработать много сообщений или пакетов из сети или вашего MQ. Каждый пакет имеет свою структуру и код управления (в общем виде). С приведенным выше кодом можно сделать что-то вроде этого:

class PacketManager:

    def __init__(self):
        self.__choice_table = \
        {
            ControlMessage : self.my_func1,
            DiagnosticMessage : self.my_func2,
        }

    def my_func1(self, data):
        # process the control message here
        pass

    def my_func2(self, data):
        # process the diagnostic message here
        pass

    def process(self, pkt):
        return self.__choice_table[pkt.__class__](pkt)

pkt = GetMyPacketFromNet()
PacketManager().process(pkt)


# isolated test or isolated usage example
def test_control_packet():
    p = ControlMessage()
    PacketManager().my_func1(p)

Таким образом, сложность не распространяется в потоке кода, но отображается в структуре кода.

+5
18 мар. '16 в 8:01
источник

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

exec {
    1: """
print ('one')
""", 
    2: """
print ('two')
""", 
    3: """
print ('three')
""",
}.get(value, """
print ('None')
""")

Где value - значение. В C это будет:

switch (value) {
    case 1:
        printf("one");
        break;
    case 2:
        printf("two");
        break;
    case 3:
        printf("three");
        break;
    default:
        printf("None");
        break;
}

Мы также можем создать вспомогательную функцию для этого:

def switch(value, cases, default):
    exec cases.get(value, default)

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

switch(value, {
    1: """
print ('one')
    """, 
    2: """
print ('two')
    """, 
    3: """
print ('three')
    """,
}, """
print ('None')
""")
+4
25 мая '16 в 22:56
источник
  • 1
  • 2

Посмотрите другие вопросы по меткам или Задайте вопрос