Приложение B: KCL-грамматика
B.1. Как создать простую пару ключ-значение с помощью KCL
Создайте файл с именем config.k
:
cpu = 256
memory = 512
image = "nginx:1.14.2"
service = "my-service"
В приведённом выше коде KCL определены четыре переменные cpu
и memory
, которые объявлены как целые числа, и их значения равны 256 и 512 соответственно. Переменные image
и service
являются строками, их значения — «nginx:1.14.2» и «my-service».
Чтобы скомпилировать файл KCL в YAML для вывода, используйте следующую команду:
kcl config.k
Полученный вывод YAML:
cpu: 256
memory: 512
image: nginx:1.14.2
service: my-service
Если вы хотите вывести данные в файл, можно использовать параметр -o|--output
:
kcl config.k -o config.yaml
В.2. Какие основные типы данных есть в KCL?
KCL на данный момент включает следующие основные числовые типы и значения:
Целочисленный тип int
:
Тип с плавающей точкой float
:
Булевый тип bool
:
True
, ложное значение False
.Строковый тип str
: используется с кавычками '
, "
для обозначения.
"string"
, """string"""
, строка в одинарных кавычках 'string'
, '''string'''
.Список типов list
: используется для обозначения [
, ]
.
[]
, список строк ["string1", "string2", "string3"]
.Словарь типов dict
: используется для обозначения {
, }
.
{}
, словарь со всеми ключами и значениями типа string {"key1": "value1", "key2": "value2"}
.Структурный тип schema
: используется ключевое слово schema
для определения, а затем применяется соответствующее имя схемы для создания экземпляра.
Пустой тип None
: используется для представления переменной без значения, соответствует значению null
в выводе YAML.
Неопределённый тип Undefined
: используется для представления переменной, которой не было присвоено значение, переменная со значением Undefined
не будет выведена в YAML.
Все переменные типов KCL могут быть присвоены значения None
или Undefined
.
В.3. Что означают переменные KCL с префиксом _
? В чём разница между ними и переменными без префикса _
? Для каких сценариев они подходят?
Переменные KCL с префиксом _
обозначают скрытые, изменяемые переменные. Скрытые переменные с префиксом подчёркивания не выводятся в YAML, включая переменные уровня пакета с префиксом подчёркивания и переменные схемы с префиксом подчёркивания. Изменяемые переменные с префиксом подчёркивания могут быть назначены повторно, тогда как переменные без префикса подчёркивания, которым было присвоено значение, становятся неизменяемыми.
Разница между переменными с префиксом и без него заключается в том, что переменные без префикса по умолчанию выводятся в YAML и являются неизменяемыми, тогда как переменные с префиксом являются скрытыми и изменяемыми.
Пример:
name = 'Foo' # Выводимая переменная, неизменяемая переменная
name = 'Bar' # Ошибка: выводимую переменную можно установить только один раз
_name = 'Foo' # Скрытая переменная, изменяемая переменная
_name = 'Bar'
schema Person:
_name: str # hidden and mutable
В.4. Как добавить элементы в dict?
Элементы можно добавить в dict с помощью оператора объединения |
, либо с помощью оператора распаковки dict **
; также можно использовать ключевые слова in
, not in
и т. д. для проверки наличия ключа в переменной dict.
Примеры:
_left = {key = {key1 = "value1"}, intKey = 1} # Обратите внимание на использование = для перезаписи
_right = {key = {key2 = "value2"}, intKey = 2}
dataUnion = _left | _right # {"key": {"key1": "value1", "key2": "value2"}, "intKey": 2}
dataUnpack = {**_left, **_right} # {"key": {"key1": "value1", "key2": "value2"}, "intKey": 2}
Вывод YAML:
dataUnion:
key:
key1: value1
key2: value2
dataUnpack:
key:
key2: value2
Также можно использовать строковую интерполяцию или функцию форматирования строки для добавления пар ключ-значение в kcl dict.
Пример:
dictKey1 = "key1"
dictKey2 = "key2"
data = {
"${dictKey1}" = "value1"
"{}".format(dictKey2) = "value2"
}
Вывод YAML:
dictKey1: key1
dictKey2: key2
data:
key1: value1
key2: value2
В.5. Как изменить элементы dict?
Для изменения элементов dict можно использовать оператор объединения |
, либо оператор распаковки **
.
Пример:
_data = {key = "value"} # {"key": "value"}
_data = _data | {key = "override_value1"} # {"key": "override_value1"}
_data = {**_data, **{key = "override_value2"}} # {"key": "override_value2"}
Если нужно удалить элемент dict с ключом key
, можно использовать оператор распаковки {key = Undefined}
или оператор слияния | {key = Undefined}
, после чего значение ключа станет Undefined
, и оно не будет выведено в YAML.
В.6. Как добавить элементы в list?
Есть два способа добавить элементы в список:
+
, +=
и срезы для соединения и сборки переменных списка.Пример:
_args = ["a", "b", "c"]
_args += ["end"] # Добавить элемент "end" в конец списка, ["a", "b", "c", "end"]
_args = _args[:2] + ["x"] + _args[2:] # Вставить элемент "x" в индекс 2 списка, ["a", "b", "x", "c", "end"]
_args = ["start"] + _args # Добавить элемент "start" в начало списка, ["start", "a", "b", "x", "c", "end"]
*
для объединения и слияния списков.Пример:
_args = ["a", "b", "c"]
_args = [*_args, "end"] # Добавить элемент "end" в конец списка, ["a", "b", "c", "end"]
_args = ["start", *_args] # Добавить элемент "start" в начало списка, ["start", "a", "b", "c", "end"]
Обратите внимание: если соединённые переменные равны None/Undefined
, использование +
может привести к ошибке. В этом случае можно использовать оператор списка *
или оператор or
для получения значения по умолчанию списка. B.7. Как модифицировать/удалить элементы списка?
Модификация элементов списка может быть выполнена двумя способами:
_index = 1
_args = ["a", "b", "c"]
_args = _args[:index] + ["x"] + _args[index+1:] # модификация элемента списка с индексом 1 на значение «x», результат: [«a», «x», «c»]
_args = ["a", "b", "c"]
_args = ["x" if a == "b" else a for a in _args] # замена всех элементов со значением «b» на «x», результат: [«a», «x», «c»]
Удаление элементов из списка также может быть выполнено двумя способами:
Использование list comprehension с условием фильтрации.
Применение filter для фильтрации элементов списка.
Например, если необходимо удалить все числа больше 2 из списка [1, 2, 3, 4, 5]
, то в KCL это можно записать следующим образом:
originList = [1, 2, 3, 4, 5]
oneWayDeleteListItem = [item for item in originList if item <= 2]
anotherWayDeleteListItem = filter item in originList {
item <= 2
}
Результат:
originList:
- 1
- 2
- 3
- 4
- 5
oneWayDeleteListItem:
- 1
- 2
anotherWayDeleteListItem:
- 1
- 2
B.8. Как писать циклы for? Как понимать и использовать list comprehension и dict comprehension?
KCL поддерживает только функциональное/декларативное использование циклов for в виде list comprehension и dict comprehension. С их помощью можно перебирать переменные типа dict и list.
Форма записи list comprehension:
[expression for expr in sequence1
if condition1
for expr2 in sequence2
if condition2
for expr3 in sequence3 ...
if condition3
for exprN in sequenceN
if conditionN]
Форма записи dict comprehension:
{expression for expr in sequence1
if condition1
for expr2 in sequence2
if condition2
for expr3 in sequence3 ...
if condition3
for exprN in sequenceN
if conditionN}
Здесь if
обозначает условие фильтрации, и только те выражения expr
, которые удовлетворяют условию, будут включены в новый список или словарь.
Пример использования list comprehension:
_listData = [1, 2, 3, 4, 5, 6]
_listData = [l * 2 for l in _listData] # умножение каждого элемента списка на 2, результат: [2, 4, 6, 8, 10, 12]
_listData = [l for l in _listData if l % 4 == 0] # фильтрация элементов, кратных 4, результат: [4, 8, 12]
_listData = [l + 100 if l % 8 == 0 else l for l in _listData] # добавление 100 к элементам, кратным 8, результат: [4, 108, 12]
Обратите внимание на разницу между первым и вторым if
:
if
является условием фильтрации для всего списка _listData
. Если элемент удовлетворяет этому условию, он остаётся в списке. Если нет — исключается, что может привести к изменению длины списка.if
представляет собой условие выбора для переменной цикла l
. Он всегда сопровождается else
, и независимо от того, выполняется условие или нет, элемент остаётся в списке, а длина списка не меняется.Пример использования dict comprehension:
_dictData = {key1 = "value1", key2 = "value2"}
_dictData = {k = _dictData[k] for k in _dictData if k == "key1" and _dictData[k] == "value1"} # фильтрация элемента с ключом «key1» и значением «value1», результат: {"key1": "value1"}
Получение всех ключей словаря:
dictData = {key1 = "value1", key2 = "value2"}
dictDataKeys = [k for k in dictData] # ["key1", "key2"]
Сортировка словаря по ключам:
dictData = {key3 = "value3", key2 = "value2", key1 = "value1"} # {'key3': 'value3', 'key2': 'value2', 'key1': 'value1'}
dictSortedData = {k = dictData[k] for k in sorted(dictData)} # {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
Многоуровневое list comprehension:
array1 = [1, 2, 3]
array2 = [4, 5, 6]
data = [a1 + a2 for a1 in array1 for a2 in array2] # [5, 6, 7, 6, 7, 8, 7, 8, 9] len(data) == len(array1) * len(array2)
Цикл с двойной переменной (for comprehension поддерживает итерацию по индексам списка, а также по значениям словаря, что упрощает процесс перебора):
data = [1000, 2000, 3000]
# одноуровневый цикл
dataLoop1 = [i * 2 for i in data] # [2000, 4000, 6000]
dataLoop2 = [i for i in data if i == 2000] # [2000]
dataLoop3 = [i if i > 2 else i + 1 for i in data] # [1000, 2000, 3000]
# двухуровневый цикл
dataLoop4 = [i + v for i, v in data] # [1000, 2001, 3002]
dataLoop5 = [v for i, v in data if v == 2000] # [2000]
# использование _ для игнорирования переменной цикла
dataLoop6 = [v if v > 2000 else v + i for i, v in data] # [1000, 2001, 3000]
dataLoop7 = [i for i, _ in data] # [0, 1, 2]
dataLoop8 = [v for _, v in data if v == 2000] # [2000]
data = {key1 = "value1", key2 = "value2"}
# одноуровневый цикл
dataKeys1 = [k for k in data] # ["key1", "key2"]
dataValues1 = [data[k] for k in data] # ["value1", "value2"]
# двухуровневый цикл
dataKeys2 = [k for k, v in data] # ["key1", "key2"] ```
dataValues2 = [v for k, v in data] # ["value1", "value2"]
dataFilter = {k: v for k, v in data if k == "key1" and v == "value1"} # {"key1": "value1"}
# Использование _ для игнорирования переменной цикла
dataKeys3 = [k for k, _ in data] # ["key1", "key2"]
dataValues3 = [v for _, v in data] # ["value1", "value2"]
KCL поддерживает два способа написания if условия:
success = True
_result = "failed"
if success:
_result = "success"
success = True
if success:
_result = "success"
else:
_result = "failed"
_result = 0
if condition == "one":
_result = 1
elif condition == "two":
_result = 2
elif condition == "three":
_result = 3
else:
_result = 4
<expr1> if <condition> else <expr2>
, похожее на C-подобный язык <condition> ? <expr1> : <expr2>
.success = True
_result = "success" if success else "failed"
Обратите внимание, что при написании if-elif-else блока необходимо обращать внимание на двоеточие :
после условия и поддерживать единообразие отступов.
Кроме того, можно напрямую писать условные выражения в структурах list или dict (отличие состоит в том, что в условных выражениях, записанных в структуре, необходимо писать значения, а не операторы):
env = "prod"
data = [
"env_value"
":"
if env == "prod":
"prod" # значение, которое нужно добавить в data
else:
"other_prod"
] # ["env_value", ":", "prod"]
env = "prod"
config = {
if env == "prod":
MY_PROD_ENV = "prod_value" # ключ-значение, которое необходимо добавить в config
else:
OTHER_ENV = "other_value"
} # {"MY_PROD_ENV": "prod_value"}
В KCL используются and
для «логического и», or
для «логического или» и not
для «не», что соответствует C-подобному языку &&
, ||
и ~
.
done = True
col == 0
if done and (col == 0 or col == 3):
ok = 1
Для «побитового и», «побитового или» и «побитового исключающего или» целых чисел в KCL используются операторы &
, |
и ^
, что аналогично C-подобным языкам &
, |
и ^
.
value = 0x22
bitmask = 0x0f
assert (value & bitmask) == 0x02
assert (value & ~bitmask) == 0x20
assert (value | bitmask) == 0x2f
assert (value ^ bitmask) == 0x2d
«Логическое или» or
также может быть использовано для упрощения записи таких шаблонов, как A if A else B
. Например, следующий код:
value = [0]
default = [1]
x0 = value if value else default
x1 = value or default # использование value or default вместо value if value else default
Следует отметить, что в условиях if выражения False
, None
, Undefined
, число 0
, пустой список []
, пустой словарь {}
и пустая строка ""
, ''
, """"""
, ''''''
рассматриваются как выражения со значением false
.
Например, чтобы определить, что строковая переменная strData
не равна None/Undefined
и не является пустой строкой (длина строки больше 0), можно просто использовать следующее выражение:
strData = "value"
if strData:
isEmptyStr = False
Пример проверки пустого словаря и пустого списка:
_emptyList = []
_emptyDict = {}
isEmptyList = False if _emptyList else True
isEmptyDict = False if _emptyDict else True
Вывод YAML:
isEmptyList: true
isEmptyDict: true
Или используйте логическую функцию bool
для проверки.
_emptyList = []
_emptyDict = {}
isEmptyList = bool(_emptyList)
isEmptyDict = bool(_emptyDict)
+
для объединения двух строк.data1 = "string1" + "string2" # "string1string2"
data2 = "string1" + " " + "string2" # "string1 string2"
"{}".format()
;${}
.hello = "hello"
a = "{} world".format(hello)
b = "${hello} world"
# a и b присваиваются значение "hello world"
Обратите внимание, если вы хотите использовать {
или }
отдельно в "{}".format()
, вам необходимо использовать {{
и }}
соответственно для экранирования {
и }
. Например, для экранирования JSON-строки:
data = "value"
jsonData = '{{"key": "{}"}}'.format(data)
Вывод YAML:
data: value
jsonData: '{"key": "value"}'
Если вы хотите использовать $
отдельно в ${}
, вам нужно использовать $$
для экранирования $
.
world = "world"
a = "hello {}".format(world) # "hello world"
b = "hello ${world}" # "hello world"
c = "$$hello ${world}$$" # "$hello world$"
c2 = "$" + "hello ${world}" + "$" # "$hello world$"
Вывод YAML:
world: world
a: hello world
b: hello world
c: $hello world$
c2: $hello world$
startswith
и endswith
строк для проверки префиксов и суффиксов строк.data = "length"
isEndsWith = data.endswith("th") # True
isStartsWith = "length".startswith('len') # True
import regex
data1 =
``` ```
import regex
schema Resource:
cpu: str = "1"
memory: str = "1024Mi"
disk: str = "10Gi"
check:
regex.match(cpu, r"^([+-]?[0-9.]+)([m]*[-+]?[0-9]*)$"), "cpu must match specific regular expression"
regex.match(memory, r"^([1-9][0-9]{0,63})(E|P|T|G|M|K|Ei|Pi|Ti|Gi|Mi|Ki)$"), "memory must match specific regular expression"
regex.match(disk, r"^([1-9][0-9]{0,63})(E|P|T|G|M|K|Ei|Pi|Ti|Gi|Mi|Ki)$"), "disk must match specific regular expression"
import regex
schema Env:
name: str
value?: str
check:
len(name) <= 63, "a valid env name must be no more than 63 characters"
regex.match(name, r"[A-Za-z_][A-Za-z0-9_]*"), "a valid env name must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_'"
Schema в KCL — это элемент языка, который используется для определения типа конфигурационных данных. Он похож на struct в C или class в Java и позволяет определять атрибуты с соответствующими типами. Пример использования schema показан в вопросе 17. Как объявить schema
В KCL schema объявляется с помощью ключевого слова schema. В ней можно определить различные атрибуты.
Пример простого объявления:
# Структура Person, которая содержит атрибуты firstName, lastName и age
schema Person:
firstName: str
lastName: str
# Значение по умолчанию для атрибута age равно 0
age: int = 0
Более сложный пример:
schema Deployment:
name: str
cpu: int
memory: int
image: str
service: str
replica: int
command: [str]
labels: {str:str}
Здесь cpu
и memory
определены как целые числа (int), name
, image
и service
— строки (str), command
— список строк, а labels
— словарь, где ключи и значения также являются строками.
Для того чтобы сделать свойство schema «необязательным», используется оператор ?
. По умолчанию все свойства schema являются обязательными.
Пример:
# Структура Person, которая содержит атрибуты firstName, lastName и age
schema Person:
firstName?: str # firstName является необязательным свойством, может быть None/Undefined
lastName?: str # age является необязательным свойством, может быть None/Undefined
# Возраст является обязательным свойством, не может быть None/Undefined и является неизменным
age: int = 18 # age — обязательное свойство, которое не может быть None/Undefined, и оно неизменное
age = 10 # Ошибка, age — неизменяемое свойство
Правила проверки свойств schema записываются с использованием ключевого слова check внутри определения schema. Каждая строка в блоке кода check представляет собой условие выражения. Если условие выполняется, проверка проходит успешно. Если нет, то проверка завершается неудачно. После условия можно указать сообщение об ошибке, которое будет отображаться при неудачной проверке.
Пример:
import regex
schema Sample:
foo: str # Обязательное свойство, не может быть None/Undefined, тип должен быть str
bar: int # Обязательное свойство, не может быть None/Undefined, тип должен быть int
fooList: [int] # Обязательное свойство, не может быть None/Undefined, тип должен быть списком int
color: "Red" | "Yellow" | "Blue" # Обязательное свойство, должно быть одним из значений "Red", "Yellow", "Blue", перечисление
id?: int # Необязательное свойство, может быть пустым, тип должен быть int
check:
bar >= 0 # bar должно быть больше или равно 0
bar < 100 # bar должно быть меньше 100
len(fooList) > 0 # fooList не может быть None/Undefined, длина должна быть больше 0
len(fooList) < 100 # fooList не может быть None/Undefined, длина должна быть меньше 100
regex.match(foo, "^The.*Foo$") # регулярное выражение, foo должно соответствовать регулярному выражению
bar in range(100) # диапазон, значение bar должно находиться в диапазоне от 1 до 99
bar in [2, 4, 6, 8] # перечисление, значение bar может быть только 2, 4, 6 или 8
bar % 2 == 0 # значение bar должно делиться на 2 без остатка
all foo in fooList {
foo > 1
} # Все элементы в списке fooList должны быть больше 1
any foo in fooList {
foo > 10
} # хотя бы один элемент в списке fooList должен быть больше 10
abs(id) > 10 if id # условие if, если id не пустое, абсолютное значение id должно быть больше 10
Также в этом примере можно использовать сокращённую запись для сравнительных выражений:
0 <= bar < 100
0 < len(fooList) < 100
Таким образом, в KCL поддерживаются следующие типы проверок:
Тип проверки | Метод использования |
---|---|
Проверка диапазона | Использование операторов сравнения < , > и т. д. |
Регулярная проверка | Использование методов match из библиотеки regex
|
Проверка длины | Использование функции len для переменных типа list , dict или str
|
Перечисление | Использование типов с перечислением |
Непустая проверка | Использование обязательных и необязательных свойств |
Условная проверка | Использование условных выражений if в блоке check Вот перевод текста на русский язык: |
Sub-schema Son of the schema Person.
Examples
--------
person = Person {
name = "Alice"
age = 18
}
"""
name: str
age: int
person = Person { name = "Alice" age = 18 }
При создании экземпляра схемы можно использовать оператор распаковки **
для развёртывания общей конфигурации.
schema Boy:
name: str
age: int
hc: int
schema Girl:
name: str
age: int
hc: int
config = {
age = 18
hc = 10
}
boy = Boy {
**config
name = "Bob"
}
girl = Girl {
**config
name = "Alice"
}
Вывод YAML:
config:
age: 18
hc: 10
boy:
name: Bob
age: 18
hc: 10
girl:
name: Alice
age: 18
hc: 10
После определения схемы можно создать соответствующую конфигурацию, используя схему в качестве имени, и использовать оператор :
для объединения значений по умолчанию, а также оператор =
для их переопределения. Для свойств типа int/float/bool/str схемы объединение и переопределение имеют одинаковый эффект; для свойств типа list/dict/schema схемы объединение и переопределение могут иметь разный эффект.
schema Meta:
labels: {str:str} = {"key1" = "value1"}
annotations: {str:str} = {"key1" = "value1"}
meta = Meta {
labels: {"key2": "value2"}
annotations = {"key2" = "value2"}
}
Вывод YAML:
meta:
labels:
key1: value1
key2: value2
annotations:
key2: value2
В определении схемы можно указать, от какой схемы она наследуется:
# A person has a first name, a last name and an age.
schema Person:
firstName: str
lastName: str
# The default value of age is 0
age: int = 0
# An employee **is** a person, and has some additional information.
schema Employee(Person):
bankCard: int
nationality: str
employee = Employee {
firstName = "Bob"
lastName = "Green"
age = 18
bankCard = 123456
nationality = "China"
}
Вывод YAML:
employee:
firstName: Bob
lastName: Green
age: 18
bankCard: 123456
nationality: China
Обратите внимание: KCL допускает только одиночное наследование схемы.
Можно использовать mixin схемы KCL для повторного использования логики схемы, mixin обычно используется для разделения внутренних атрибутов схемы и функций, таких как сопоставление данных, что делает код KCL более модульным и декларативным. Обратите внимание, что не рекомендуется определять зависимости между различными mixin, так как это усложнит использование mixin, обычно один mixin содержит не более трёх свойств.
schema Person:
mixin [FullNameMixin, UpperMixin]
firstName: str
lastName: str
fullName: str
upper: str
schema FullNameMixin:
fullName = "{} {}".format(firstName, lastName)
schema UpperMixin:
upper = fullName.upper()
person = Person {
firstName = "John"
lastName = "Doe"
}
Вывод YAML:
person:
firstName: John
lastName: Doe
fullName: John Doe
upper: JOHN DOE
Другие файлы KCL можно импортировать с помощью ключевого слова import. Файлы конфигурации KCL организованы в виде модулей. Один файл KCL считается модулем, каталог считается пакетом и рассматривается как особый модуль. Импорт поддерживает два способа импорта: относительный путь и абсолютный путь.
Например, для следующей структуры каталогов:
.
└── root
├── kcl.mod
├── model
│ ├── model1.k
| ├── model2.k
│ └── main.k
├── service
│ │── service1.k
│ └── service2.k
└── mixin
└── mixin1.k
Для main.k
относительный и абсолютный пути импорта могут быть выражены следующим образом:
import service # Абсолютный путь импорта, корень каталога — это путь, где находится kcl.mod.
import mixin # Абсолютный путь импорта, корень каталога — это путь, где находится kcl.mod.
import .model1 # Относительный путь импорта, текущий каталог.
import ..service # Относительный путь импорта, родительский каталог.
import ...root # Относительный путь импорта, родительский каталог родительского каталога.
Обратите внимание, что для файла KCL входа main.k
, он не может импортировать папку, в которой он находится, иначе возникнет ошибка циклического импорта.
import model # Ошибка: рекурсивная загрузка.
За исключением основного пакета в main, файлы в одном каталоге могут ссылаться друг на друга без импорта, например, для следующей структуры каталогов:
.
└── root
├── kcl.mod
├── model
│ ├── model1.k
| ├── model2.k
│ └── main.k
├── service
│ │── service1.k
│ └── service2.k
└── mixin
└── mixin1.k
Когда main.k
используется в качестве входного файла команды KCL, переменные в файлах model в папке model нельзя ссылаться друг на друга, необходимо импортировать их, но переменные в service в папке service можно ссылаться друг на друга и игнорировать импорт.
service1.k:
schema BaseService:
name: str
namespace: str
service2.k:
schema Service(BaseService):
id: str
В KCL вы можете использовать символ продолжения строки \
для переноса строк, и вы также можете использовать \
в строках для обозначения продолжения строки.
Пример соединения длинных строк:
longString = "Too long expression " + \
"Too long expression " + \
"Too long expression "
Пример выражения продолжения:
data = [1, **2, 3, 4]
dataNew = [
d + 2 \
for d in data \
if d % 2 == 0
]**
**Если выражение с продолжением строки:**
```python
condition = 1
data1 = 1 \
if condition \
else 2
data2 = 2 \
if condition \
else 1
Строка в тройных кавычках с продолжением:
longString = """\
The first line\
The continue second line\
"""
Примечание: при использовании символа продолжения строки \
необходимо сохранять отступ, как показано ниже:
Неправильный пример использования:
data1 = [
1, 2,
3, 4 \
] # Error, необходимо сохранить отступ для правой скобки ]
data2 = [
1, 2,
3, 4
] # Ошибка, необходимо сделать отступ для цифр 1 и 3
Правильный пример использования:
data1 = [
1, 2,
3, 4
] # Правильно, список с отступом
data2 = [ \
1, 2, \
3, 4 \
] # Верно, использование символа продолжения строки для определения списка, фактически это однострочный список
data3 = [ \
1, 2, \
3, 4 \
] # Верно, использование символа продолжения строки для определения списка, нет необходимости сохранять отступ, фактически это однострочный список
**
, *
при появлении вне dict/list обозначают операторы возведения в степень и умножения соответственно:data1 = 2 ** 4 # 2 в степени 4 равно 16
data2 = 2 * 3 # 2 умножить на 3 равно 6
**
, *
при появлении внутри dict/list означают оператор распаковки, часто используется для объединения и распаковки списков и словарей, аналогично использованию оператора распаковки в Python:Распаковка словаря:
data = {"key1" = "value1"}
dataUnpack = {**data, "key2" = "value2"} # Распаковать data и объединить с dataUnpack, {"key1": "value1", "key2": "value2"}
Распаковка списка:
data = [1, 2, 3]
dataUnpack = [*data, 4, 5, 6] # Распаковать данные и объединить с dataUnpack, [1, 2, 3, 4, 5, 6]
В KCL можно использовать выражения выбора или подстрочные выражения для получения элементов списка/словаря/схемы:
— Для типа list можно использовать []
для извлечения элемента или нескольких элементов из массива:
data = [1, 2, 3] # Определение целочисленного массива
theFirstItem = data[0] # Первый элемент 1
theSecondItem = data[1] # Второй элемент 2
Обратите внимание: индексы не должны выходить за пределы длины списка, иначе произойдёт ошибка. Можно использовать функцию len
для получения длины массива:
data = [1, 2, 3]
item = data[3] # Произойдёт ошибка выхода индекса за границы массива
Кроме того, можно использовать отрицательные индексы для обратного порядка элементов в списке:
data = [1, 2, 3]
item1 = data[-1] # Последний элемент 3
item2 = data[-2] # Предпоследний элемент 2
Таким образом, диапазон индексов списка составляет [-len, len - 1]
Когда нужно получить часть списка, можно использовать срез в []
, его конкретная грамматика: [<начальный индекс>:<конечный индекс>:<шаг среза>]
, обратите внимание, что начало и конец диапазона индексов определяются как левый закрытый, правый открытый [<начальный индекс>, <конечный индекс>]
, а три параметра могут быть опущены:
data = [1, 2, 3, 4, 5]
dataSlice0 = data[1:2] # Элементы от 1 до 2 [2]
dataSlice1 = data[1:3] # Элементы от 1 до 3 [2, 3]
dataSlice2 = data[1:] # Элементы от 1 до конца [2, 3, 4, 5]
dataSlice3 = data[:3] # Элементы от начала до 3 [1, 2, 3]
dataSlice4 = data[::2] # Элементы от начала до конца (шаг 2) [1, 3, 5]
dataSlice5 = data[::-1] # Реверс списка [5, 4, 3, 2, 1]
dataSlice6 = data[2:1] # Когда начальный и конечный индексы не соответствуют условиям, возвращается пустой список []
— Для типов dict/schema можно использовать []
и .
для доступа к элементам dict/schema:
data = {key1: "value1", key2: "value2"}
data1 = data["key1"] # "value1"
data2 = data.key1 # "value1"
data3 = data["key2"] # "value2"
data4 = data.key2 # "value2"
schema Person:
name: str = "Alice"
age: int = 18
person = Person {}
name1 = person.name # "Alice"
name2 = person["name"] # "Alice"
age1 = person.age # 18
age2 = person.age # 18
Если ключ не существует в dict, будет возвращено неопределённое значение Undefined
:
data = {key1 = "value1", key2 = "value2"}
data1 = data["not_exist_key"] # Undefined
data2 = data.not_exist_key # Undefined
Можно использовать ключевое слово in
для проверки существования ключа в dict/schema:
data = {key1 = "value1", key2 = "value2"}
exist1 = "key1" in data # True
exist2 = "not_exist_key" in data # False
При наличии точки в ключе или необходимости динамического получения значения ключа во время выполнения, можно использовать только []
, если нет особых обстоятельств, используйте .
:
name = "key1"
data = {key1 = "value1", key2 = "value2", "contains.dot" = "value3"}
data1 = data[name] # "value1"
data2 = data["contains.dot"] # "value3"
# Обратите внимание, что data3 = data.contains.dot неверно
Обратите внимание, что указанные выше операторы доступа к элементам нельзя применять к значениям, отличным от list/dict/schema, таким как целые числа и пустые значения.
data = 1
data1 = 1[0] # error
data = None
data1 = None[0] # error
Поэтому при получении элементов коллекции обычно необходимо проверять наличие или длину:
data = []
item = data[0] if data else None
Оператор ?
можно добавить перед []
или .
, чтобы выполнить проверку наличия перед доступом, и вернуть None
, когда условие не выполняется, например, предыдущий код можно упростить следующим образом:
data = []
item1 = data?[0] # Если data пуста, вернуть None
item2 = data?[0] or 1 # Если data пуста, вернуть None, если вы не хотите возвращать None, вы также можете использовать or для возврата других значений по умолчанию
Использование ?
позволяет выполнять рекурсивные вызовы, избегая сложных проверок на пустоту:
data = {key1.key2.key3 = []}
item = data?.key1?.key2?.key3?[0]
Встроенная функция KCL typeof может использоваться для немедленного возврата типа переменной (в виде строки) для утверждения типа во время её выполнения. ## B.31. Как решить проблему конфликта ключевых слов и переменных KCL?
Для идентификаторов, конфликтующих с ключевыми словами, можно использовать префикс $
для определения идентификатора ключевого слова, как показано в следующем коде, где if
, else
и другие ключевые слова используются в качестве идентификаторов и дают соответствующий вывод YAML:
$if = 1
$else = "s"
schema Person:
$filter: str = "filter"
data = Person {}
Вывод:
data:
filter: filter
if: 1
else: s
Обратите внимание, что использование префикса $
перед неключевыми идентификаторами имеет тот же эффект, что и без него:
_a = 1
$_a = 2 # эквивалентно _a = 2
Встроенные типы KCL включают int
, float
, bool
и str
, которые не являются ключевыми словами KCL и могут использоваться для определения переменных, например:
int = 1
str = 2
Вывод:
int: 1
str: 2
Примечание: без особых требований не рекомендуется использовать эти встроенные типы в качестве имён переменных, поскольку в некоторых языках они являются ключевыми словами.
Есть два способа реализовать перечисление в KCL:
schema Person:
name: str
gender: "Male" | "Female"
person = Person {
name = "Alice"
gender = "Male" # gender может быть только "Male" или "Female"
}
Пример более сложного случая:
schema Config:
colors: ["Red" | "Yellow" | "Blue"] # colors — это массив перечислений
config = Config {
colors = [
"Red"
"Blue"
]
}
schema Person:
name: str
gender: "Male" | "Female"
check:
gender in ["Male", "Female"]
person = Person {
name = "Alice"
gender = "Male" # гендер может быть только «Male» или «Female»
}
В KCL вы можете использовать функцию len
для непосредственного нахождения длины словаря:
len1 = len({k1: "v1"}) # 1
len2 = len({k1: "v1", k2: "v2"}) # 2
varDict = {k1 = 1, k2 = 2, k3 = 3}
len3 = len(varDict) # 3
Кроме того, функция len
также может использоваться для нахождения длины строк и списков.
len1 = len("hello") # 5
len2 = len([1, 2, 3]) # 3
Помимо поддержки выражений if-elif-else
в операторах верхнего уровня, KCL также поддерживает выражения условий в сложных структурах (списки/словари/схемы), что позволяет писать конфигурации с условиями.
x = 1
# Список структур, содержащих if условия
dataList = [
if x == 1:
]
# Словарь структур, содержащих условия if
dataDict = {
if x == 1: key1 = "value1" # можно записать в одной строке
elif x == 2:
key2 = "value2" # можно написать на разных строках
}
# Схема структуры, содержащая условия if
schema Config:
id?: int
env = "prod"
dataSchema = Config {
if env == "prod":
id = 1
elif env == "pre":
id = 2
elif env == "test":
id = 3
}
Обратите внимание: чтобы уменьшить сложность написания конфигураций и повысить удобочитаемость, в сложных структурах KCL в настоящее время не поддерживается вложение условий if-elif-else
.
==
выполнять глубокое сравнение в KCL?Оператор сравнения ==
в KCL выполняет следующие действия:
int
, float
, bool
, str
он непосредственно сравнивает их значения на равенство.list
, dict
, schema
он выполняет глубокое рекурсивное сравнение их внутренних элементов на равенство. Для типа list
это включает в себя сравнение значений каждого индекса и длины. Для типов dict
/schema
это включает сравнение значений каждого атрибута (независимо от порядка появления).print([1, 2] == [1, 2]) # True
print([[0, 1], 1] == [[0, 1], 1]) # True
print({k1 = 1, k2 = 2} == {k2 = 2, k1 = 1}) # True
print([1, 2] == [1, 2, 3]) # False
print({k1 = 1, k2 = 2, k3 = 3} == {k2 = 2, k1 = 1}) # False
В KCL существуют три оператора свойств =
, +=
и :
, которые можно использовать для изменения существующего блока конфигурации. Также можно использовать оператор распаковки **
для «наследования» всех свойств и значений блока конфигурации.
=
представляет собой переопределение, которое позволяет переопределить или удалить свойство с приоритетом (если используется Undefined
для переопределения, это означает удаление).+=
представляет добавление и обычно используется для добавления дочерних элементов к списку свойств. Тип операнда после +=
также должен быть списком.:
представляет собой равнозначное объединение, которое вызывает ошибку при конфликте значений, но не вызывает ошибки при отсутствии конфликта. ### B. Объединение атрибутов:Объединение атрибутов означает объединение различных конфигурационных блоков одного и того же атрибута с помощью операции слияния. Если значения объединяемых блоков конфликтуют, то происходит ошибка. Обычно используется для объединения сложных конфигураций.
data = {
labels: {key1: "value1"} # Определяем labels как словарь с одним элементом {"key1": "value1"}
labels: {key2: "value2"} # Объединяем два блока конфигурации labels
} # Конечное значение data равно {"labels": {"key1": "value1", "key2": "value2"}}
Операция объединения атрибутов является операцией слияния и не зависит от порядка записи блоков конфигурации. В примере выше блоки конфигурации можно поменять местами без изменения конечного результата.
data = { # Порядок блоков конфигурации не влияет на конечный результат
labels: {key2: "value2"} # Определяем labels как словарь с одним элементом {"key2": "value2"}
labels: {key1: "value1"} # Объединяем два блока конфигурации labels
} # Конечное значение data равно {"labels": {"key1": "value1", "key2": "value2"}}
Важно отметить, что операция объединения атрибутов проверяет наличие конфликтов между значениями объединяемых блоков. Если есть конфликт, то возникает ошибка.
data = {
a: 1 # Значение a равно 1
a: 2 # Ошибка: невозможно объединить значения 2 и 1, так как они конфликтуют
}
Также операция объединения атрибутов не позволяет объединять конфликтующие значения в одном блоке конфигурации.
data = {
labels: {key: "value"}
labels: {key: "override_value"} # Ошибка: конфликтующие значения "value" и "override_value" не могут быть объединены
}
Использование операции объединения атрибутов зависит от типа данных.
data = {
a: 1
a: 1 # Всё в порядке
a: 2 # Ошибка
}
Для списков операция объединения работает следующим образом:
data = {
args: ["kcl"]
args: ["-Y", "settings.yaml"] # Ошибка: длины двух блоков конфигурации args не совпадают
env: [{key1: "value1"}]
env: [{key2: "value2"}] # Всё в порядке: конечное значение env равно [{"key1": "value1"}, {"key2": "value2"}]
}
Для словарей и схем операция объединения выполняется рекурсивно по ключам.
data = {
labels: {key1: "value1"}
labels: {key2: "value2"}
labels: {key3: "value3"}
} # Конечное значение data равно {"labels": {"key1": "value1", "key2": "value2", "key3": "value3"}}
Значения None и Undefined при объединении с любым атрибутом дают исходное значение этого атрибута.
data = {
args: ["kcl"]
args: None # Всё в порядке
args: Undefined # Всё в порядке
} # Конечное значение data равно {"args": ["kcl"]}
Можно использовать операцию объединения для переменных верхнего уровня, используя :
для объявления и объединения. Это аналогично объявлению переменной с использованием Config {}.
schema Config:
id: int
value: str
config: Config {
id: 1
}
config: Config {
value: "1"
}
"""
Здесь определены два блока конфигурации Config, которые можно объединить с помощью операции объединения (`:`). Эквивалентный код выглядит так:
config: Config {
id: 1
value: "1"
}
"""
В целом, операция объединения атрибутов используется для объединения сложных структур данных, таких как списки, словари и схемы. Если нет особых требований, рекомендуется использовать операции =
и +=
для базовых типов данных и операцию :
для сложных структур.
=
.=
и +=
(используйте =
для полного перезаписывания списка, используйте +=
для добавления элементов в список).:
.Если уже существует конфигурация, можно использовать распаковку (**
), чтобы получить все поля этой конфигурации и изменить их с помощью различных операций атрибутов. Это позволяет создать новую конфигурацию.
configBase = {
intKey = 1 # Атрибут типа int
floatKey = 1.0 # Атрибут типа float
listKey = [0] # Атрибут типа list
dictKey = {key1: "value1"} # Атрибут типа dict
}
configNew = {
**configBase # Распаковка configBase в configNew
intKey = 0 # Перезапись intKey значением 0
floatKey = Undefined # Удаление floatKey
listKey += [1] # Добавление элемента 1 в конец listKey
dictKey: {key2: "value2"} # Расширение dictKey ключом-значением key2: "value2"
}
Результат в YAML:
configBase:
intKey: 1
floatKey: 1.0
listKey:
- 0
dictKey:
key1: value1
configNew:
intKey: 0
listKey:
- 0
- 1
dictKey:
key1: value1
key2: value2
Или можно использовать |
для объединения двух конфигураций:
configBase = {
intKey = 1 # Атрибут типа int
floatKey = 1.0 # Атрибут типа float
listKey = [0] # Атрибут типа list
dictKey = {key1: "value1"} # Атрибут типа dict
}
configNew = configBase | { # Объединение с помощью |
intKey = 0 # Перезапись intKey значением 0
floatKey = Undefined # Удаление floatKey
listKey += [1] # Добавление элемента 1 в конец listKey
dictKey: {key2: "value2"} # Расширение dictKey ключом-значением key2: "value2"
}
Результат в YAML такой же, как и в предыдущем примере. В запросе скорее всего текст технической направленности из области разработки и тестирования программного обеспечения. Основной язык текста запроса — язык KCL.
Когда в KCL возникает ошибка, подобная конфликту значений атрибута «attr» между {value1} и {value2}, это обычно связано с проблемой использования оператора слияния атрибутов :
. Это указывает на то, что при объединении конфигураций value1 и value2 произошла ошибка конфликта в атрибуте «attr». В этом случае обычно можно изменить атрибут «attr» value2 на другой оператор атрибута, например, использовать =
для перезаписи или +=
для добавления.
Например, для следующего кода:
data = {k: 1} | {k: 2} # Error: conflicting values on the attribute 'k' between {'k': 1} and {'k': 2}
Можно использовать оператор =
для изменения формы следующим образом:
data = {k: 1} | {k = 2} # Ok: the value 2 will override the value 1 through the `=` operator
В KCL можно использовать выражение for для перебора нескольких элементов.
dimension1 = [1, 2, 3] # dimension1 список имеет длину 3
dimension2 = [1, 2, 3] # dimension2 список имеет длину 3
matrix = [x + y for x in dimension1 for y in dimension2] # длина списка matrix равна 9 = 3 * 3
Вывод:
dimension1:
- 1
- 2
- 3
dimension2:
- 1
- 2
- 3
matrix:
- 2
- 3
- 4
- 3
- 4
- 5
- 4
- 5
- 6
dimension1 = [1, 2, 3] # список dimension1 имеет длину 3
dimension2 = [1, 2, 3] # список dimension2 имеет длину 3
dimension3 = [d[0] + d[1] for d in zip(dimension1, dimension2)] # список dimension3 имеет длину 3
Вывод:
dimension1:
- 1
- 2
- 3
dimension2:
- 1
- 2
- 3
dimension3:
- 2
- 4
- 6
В KCL, когда значение опции равно None/Undefined (пусто), можно напрямую указать значение по умолчанию, используя логический оператор or.
value = option("key") or "default_value" # Если key существует, взять значение option("key"), иначе взять "default_value"
Или используйте параметр default функции option.
value = option("key", default="default_value") # Если ключ существует, взять значение option("key"), иначе взять "default_value"
В KCL для проверки того, что один атрибут не может быть пустым, можно использовать маркер атрибута non-null.
schema Person:
name: str # required. name не может быть пустым
age: int # required. age не может быть пустым
id?: int # optional. id можно оставить пустым
Для проверки того, что атрибуты схемы не могут быть одновременно пустыми или только один из них может быть пустым (не может существовать или не быть пустым одновременно), необходимо использовать выражение schema check. Ниже приведён пример с двумя атрибутами a и b схемы Config.
— Атрибуты a и b в схеме Config не могут быть одновременно пустыми.
schema Config:
a?: str
b?: str
check:
a or b, "атрибуты a и b не могут быть одновременно пустыми"
— Атрибуты a и b в схеме Config могут быть либо пустыми, либо оба пустыми (не могут существовать одновременно или быть непустыми).
schema Config:
a?: str
b?: str
check:
a or b, "атрибуты a и b не должны быть заполнены одновременно"
Возможно, это связано с тем, что при импорте был импортирован только этот файл папки, а в KCL импорт поддерживает импорт всей папки файлов, а также импорт одного файла или папки. Например, для следующей структуры каталогов:
.
├── kcl.mod
├── main.k
└── pkg
├── pkg1.k
├── pkg2.k
└── pkg3.k
В корневом каталоге есть входной файл main.k, в котором можно написать следующий код для импорта всей папки pkg, и в этом случае определения схем всех файлов pkg будут видны друг другу.
import pkg
Также можно написать следующий код, чтобы импортировать отдельный файл pkg/pkg1.k. В этом случае определение схемы pkg1.k не сможет найти другие файлы, такие как pkg2.k и pkg3.k.
import pkg.pkg1
В KCL при появлении двоеточия (:), квадратных скобок ([]) или фигурных скобок ({}) обычно требуется использовать перенос строки и отступ, при этом количество пробелов для одного уровня отступа должно быть одинаковым, и обычно используется четыре пробела для представления одного уровня отступа.
— Двоеточие (:), за которым следует перенос строки и отступ.
"""if оператор, содержащий отступ"""
_a = 1
_b = 1
if _a >= 1: # После двоеточия следует перенос строки+отступ
if _a > 8:
_b = 2
elif a > 6:
_b = 3
"""определение схемы с отступом"""
schema Person: # После двоеточия следует перенос строки+отступ
name: str
age: int
— Квадратные скобки ([]) следуют за переносом строки и отступом.
data = [ # После левой квадратной скобки следует перенос строки+отступ
1
2
3
] # Перед правой квадратной скобкой отступ снимается
data = [ # После левой квадратной скобки следует перенос строки+отступ
i * 2 for i in range(5)
] # Перед правой квадратной скобкой отступ снимается
— Фигурные скобки ({}) следуют за переносом строки и отступом.
data = { # После левой фигурной скобки следует перенос строки+отступ
k1 = "v1"
k2 = "v2"
} # Перед правой фигурной скобкой отступ снимается
data = { # После левой фигурной скобки следует перенос строки+отступ
str(i): i * 2 for i in range(5)
} # Перед правой фигурной скобкой отступ снимается
Текущая версия KCL ещё не поддерживает внутреннюю отладку программы, но можно использовать утверждения assert и функцию print для реализации утверждений данных и просмотра вывода.
a = 1
print("Значение a равно", a)
assert a == 1
Кроме того, вы можете использовать инструмент тестирования kcl-test для написания тестовых примеров внутри KCL.
Предположим, у вас есть файл hello.k со следующим кодом:
schema Person:
name: str = "kcl"
age: int = 1
hello = Person {
name = "hello kcl"
age = 102
}
Создайте файл теста hello_test.k со следующим содержимым:
schema TestPerson:
a = Person{}
assert a.name == 'kcl'
schema TestPerson_age:
a = Person{}
assert a.age == 1
schema TestPerson_ok:
a = Person{}
assert a.name == "kcl"
assert a.age == 1
Затем выполните команду kcl-test в каталоге:
$ kcl-test
ok /pkg/to/app [365.154142ms]
$
Определение функции или метода в KCL аналогично определению схемы. Структура в определённой степени выполняет функции функции, и эта функция имеет несколько входных параметров и несколько выходных параметров, например, следующий код может реализовать функцию ряда Фибоначчи:
schema Fib:
n: int
value: int = 1 if n <= 2 else (Fib {n: n - 1}).value + (Fib {n: n - 2}).value
fib8 = (Fib {n: 8}).value
Вывод:
fib8: 21
Схема слияния списка в словарь.
schema UnionAll[data, n]:
_?: [] = data
value?: {:} = ((UnionAll(data=data, n=n - 1) {}).value | data[n] if n > 0 else data[0]) if data else {}
schema MergeList[data]:
"""Объединяет все элементы списка и возвращает объединённый словарь.
[{"key1": "value1"}, {"key2": "value2"}, {"key3": "value3"}] -> {"key1": "value1", "key2": "value2", "key3": "value3"}
"""
_?: [] = data
value?: {:} = (UnionAll(data=data, n=len(data) - 1) {}).value if data else {}
Кроме того, KCL поддерживает использование ключевого слова lambda
для определения функции:
func = lambda x: int, y: int -> int {
x + y
}
a = func(1, 1) # 2
Лямбда-функция имеет следующие характеристики:
— Лямбда-функция возвращает значение последнего выражения в качестве значения функции, а пустая функция тела возвращает None. — Тип возвращаемого значения можно опустить, тип возвращаемого значения будет таким же, как у последнего выражения. — В теле функции нет характеристик, не зависящих от порядка выполнения, все выражения выполняются последовательно.
_func = lambda x: int, y: int -> int {
x + y
} # Define a function using the lambda expression
_func = lambda x: int, y: int -> int {
x - y
} # Ok
_func = lambda x: int, y: int -> str {
str(x + y)
} # Error (int, int) -> str can't be assigned to (int, int) -> int
Объект лямбда-функции нельзя использовать в любых вычислениях, его можно использовать только в операторах присваивания и вызова.
func = lambda x: int, y: int -> int {
x + y
}
x = func + 1 # Error: unsupported operand type(s) for +: 'function' and 'int(1)'
a = 1
func = lambda x: int {
x + a
}
funcOther = lambda f, para: int {
f(para)
}
r = funcOther(func, 1) # 2
Вывод:
a: 1
r: 2
Можно определить анонимную функцию и вызвать её напрямую:
result = (lambda x, y {
z = 2 * x
z + y
})(1, 1) # 3
В цикле for можно использовать анонимные функции:
result = [(lambda x, y {
x + y
})(x, y) for x in [1, 2] for y in [1, 2]] # [2, 3, 3, 4]
Также можно определить и использовать функции в схеме KCL:
_funcOutOfSchema = lambda x: int, y: int {
x + y
}
schema Data:
_funcInSchema = lambda x: int, y: int {
x + y
}
id0: int = _funcOutOfSchema(1, 1)
id1: int = _funcInSchema(1, 1)
id2: int = (lambda x: int, y: int {
x + y
})(1, 1)
Вывод YAML:
data:
id0: 2
id1: 2
id2: 2
В KCL атрибуты, определённые как типы объединения, могут принимать только одно значение — литерал или переменную того же типа объединения — при присвоении значений. Например, следующий код является правильным:
schema Data:
color: "Red" | "Yellow" | "Blue"
data = Data {
color = "Red" # Ok, можно присвоить "Red", "Yellow" или "Blue"
}
Однако следующий код вызовет ошибку:
schema Data:
color: "Red" | "Yellow" | "Blue"
_color = "Red"
data = Data {
color = _color # Ошибка: ожидается str("Red") | str("Yellow") | str("Blue"), получено str
}
Это связано с тем, что переменная _color
не была объявлена с определённым типом, поэтому компилятор KCL выводит её как тип str
. При попытке присвоить значение типа str
типу объединения "Red" | "Yellow" | "Blue"
возникает ошибка. Один из способов решения проблемы — объявить тип для переменной _color
:
schema Data:
color: "Red" | "Yellow" | "Blue"
_color: "Red" | "Yellow" | "Blue" = "Red"
data = Data {
color = _color # Правильно
}
Более того, мы можем использовать псевдонимы типов для упрощения записи типов объединений:
type Color = "Red" | "Yellow" | "Blue" # Определяем псевдоним типа, который можно повторно использовать в разных местах, уменьшая количество написанного кода
schema Data:
color: Color
_color: Color = "Red"
data = Data {
color = _color # Правильно
}
Плагины KCL находятся в каталоге plugins в KCLVM ($HOME/.kusion/kclvm/plugins) или через переменную среды $KCL_PLUGINS_ROOT (переменная среды имеет более высокий приоритет). Для разработчиков плагинов плагины управляются системой контроля версий Git, и репозитории плагинов можно клонировать в этот каталог для разработки.
KCL предоставляет инструмент kcl-plugin для помощи пользователям в написании плагинов на Python для расширения возможностей языка KCL, таких как доступ к сети, ввод-вывод, запросы CMDB и шифрование/дешифрование.
usage: kcl-plugin [-h] {list,init,info,gendoc,test} ...
positional arguments:
{list,init,info,gendoc,test}
kcl plugin sub commands
list list all plugins
init init a new plugin
info show plugin document
gendoc gen all plugins document
test test plugin
optional arguments:
-h, --help show this help message and exit
Например, чтобы разработать плагин с именем io, можно использовать следующую команду для успешного создания нового плагина io:
kcl-plugin init io
Затем можно использовать следующую команду, чтобы получить корневой путь плагина и перейти в соответствующий каталог плагина io для разработки:
kcl-plugin info
Например, если вы хотите разработать функцию для чтения файла: read_file, можно в $plugin_root/io
в plugin.py
написать код на Python:
# Copyright 2020 The KCL Authors. All rights reserved.
import pathlib
INFO = {
'name': 'io',
'describe': 'my io plugin description test',
'long_describe': 'my io plugin long description test',
'version': '0.0.1',
}
def read_file(file: str) -> str:
"""Прочитать строку из файла."""
return pathlib.Path(file).read_text()
Также можно написать соответствующую тестовую функцию в plugin_test.py
, или напрямую написать файл KCL для тестирования:
import kcl_plugin.io
text = io.read_file('test.txt')
Можно также использовать команду info для просмотра информации об io плагине:
kcl-plugin info io
{
"name": "io",
"describe": "my io plugin description test",
"long_describe": "my io plugin long description test",
"version": "0.0.1",
"method": {
"read_file": "Прочитать строку из файла"
}
}
После завершения написания тестов на плагин, его можно отправить в репозиторий kcl_plugins
через MR (Merge Request).
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )