Я считаю, что соглашение является правильным подходом здесь, и бит, который вам не хватает, просто предоставляет правильный метод расширения для регистрации вашей библиотеки в MVC.
Начните с создания соглашения, которое добавит префикс для всех контроллеров, которые передают определенный селектор.
AttributeRouteModel
, либо добавит новый, если ни один не найден. Это было бы примером такого соглашения:
public class ApiPrefixConvention: IApplicationModelConvention
{
private readonly string prefix;
private readonly Func controllerSelector;
private readonly AttributeRouteModel onlyPrefixRoute;
private readonly AttributeRouteModel fullRoute;
public ApiPrefixConvention(string prefix, Func controllerSelector)
{
this.prefix = prefix;
this.controllerSelector = controllerSelector;
// Prepare AttributeRouteModel local instances, ready to be added to the controllers
// This one is meant to be combined with existing route attributes
onlyPrefixRoute = new AttributeRouteModel(new RouteAttribute(prefix));
// This one is meant to be added as the route for api controllers that do not specify any route attribute
fullRoute = new AttributeRouteModel(
new RouteAttribute("api/[controller]"));
}
public void Apply(ApplicationModel application)
{
// Loop through any controller matching our selector
foreach (var controller in application.Controllers.Where(controllerSelector))
{
// Either update existing route attributes or add a new one
if (controller.Selectors.Any(x => x.AttributeRouteModel != null))
{
AddPrefixesToExistingRoutes(controller);
}
else
{
AddNewRoute(controller);
}
}
}
private void AddPrefixesToExistingRoutes(ControllerModel controller)
{
foreach (var selectorModel in controller.Selectors.Where(x => x.AttributeRouteModel != null).ToList())
{
// Merge existing route models with the api prefix
var originalAttributeRoute = selectorModel.AttributeRouteModel;
selectorModel.AttributeRouteModel =
AttributeRouteModel.CombineAttributeRouteModel(onlyPrefixRoute, originalAttributeRoute);
}
}
private void AddNewRoute(ControllerModel controller)
{
// The controller has no route attributes, lets add a default api convention
var defaultSelector = controller.Selectors.First(s => s.AttributeRouteModel == null);
defaultSelector.AttributeRouteModel = fullRoute;
}
}
Теперь, если это было частью приложения, которое вы пишете вместо библиотеки, вы просто зарегистрируете его как:
services.AddMvc(opts =>
{
var prefixConvention = new ApiPrefixConvention("api/", (c) => c.ControllerType.Namespace == "WebApplication2.Controllers.Api");
opts.Conventions.Insert(0, prefixConvention);
});
Однако, поскольку вы предоставляете библиотека, то, что вы хотите, это предоставить метод расширения, такой как AddMyLibrary("some/prefix")
, который позаботится о добавлении этого соглашения и любой другой настройки, такой как регистрация необходимых сервисов.
Таким образом, вы можете написать метод расширения для IMvcBuilder
и обновите MvcOptions
внутри tha т. Самое приятное, что поскольку это расширение IMvcBuilder
, оно всегда будет вызываться после по умолчанию AddMvc()
:
public static IMvcBuilder AddMyLibrary(this IMvcBuilder builder, string prefix = "api/")
{
// instantiate the convention with the right selector for your library.
// Check for namespace, marker attribute, name pattern, whatever your prefer
var prefixConvention = new ApiPrefixConvention(prefix, (c) => c.ControllerType.Namespace == "WebApplication2.Controllers.Api");
// Insert the convention within the MVC options
builder.Services.Configure(opts => opts.Conventions.Insert(0, prefixConvention));
// perform any extra setup required by your library, like registering services
// return builder so it can be chained
return builder;
}
Затем вы попросите пользователей вашей библиотеки включить его в свое приложение как в:
services.AddMvc().AddMyLibrary("my/api/prefix/");
Вы можете это выяснить, но результат будет представлен в терминах ops и ядер, которые не отображаются точно на функции Python более высокого уровня. В случае, если вы не знакомы с архитектурой TensorFlow, она построена вокруг концепции «операций», которые являются просто формальным описанием операции с тензорами (например, операция «Добавить» принимает два значения и выводит третье значение). Граф вычисления TensorFlow состоит из взаимосвязанных операционных узлов. Операции сами по себе не реализуют никакой логики, они просто указывают имя и атрибуты операции, включая типы данных, к которым она может быть применена. Реализация ops дается ядрами, которые являются фактическими частями кода, которые выполняют работу. У одной операции может быть много зарегистрированных ядер, которые работают с разными типами данных и / или разными устройствами (CPU, GPU).
TensorFlow хранит «реестры» со всей этой информацией, хранимой в виде различных сообщений Protocol Buffers . Хотя он не является частью общедоступного API, вы можете запросить эти реестры, чтобы получить список операций или ядер, которые соответствуют определенным критериям. Например, вот как вы можете получить все операции, которые работают с некоторым сложным типом:
import tensorflow as tf
def get_ops_with_dtypes(dtypes):
from tensorflow.python.framework import ops
valid_ops = []
dtype_enums = set(dtype.as_datatype_enum for dtype in dtypes)
reg_ops = ops.op_def_registry.get_registered_ops()
for op in reg_ops.values():
for attr in op.attr:
if (attr.type == 'type' and
any(t in dtype_enums for t in attr.allowed_values.list.type)):
valid_ops.append(op)
break
# Sort by name for convenience
return sorted(valid_ops, key=lambda op: op.name)
complex_dtypes = [tf.complex64, tf.complex128]
complex_ops = get_ops_with_dtypes(complex_dtypes)
# Print one op
print(complex_ops[0])
# name: "AccumulateNV2"
# input_arg {
# name: "inputs"
# type_attr: "T"
# number_attr: "N"
# }
# output_arg {
# name: "sum"
# type_attr: "T"
# }
# attr {
# name: "N"
# type: "int"
# has_minimum: true
# minimum: 1
# }
# attr {
# name: "T"
# type: "type"
# allowed_values {
# list {
# type: DT_FLOAT
# type: DT_DOUBLE
# type: DT_INT32
# type: DT_UINT8
# type: DT_INT16
# type: DT_INT8
# type: DT_COMPLEX64
# type: DT_INT64
# type: DT_QINT8
# type: DT_QUINT8
# type: DT_QINT32
# type: DT_BFLOAT16
# type: DT_UINT16
# type: DT_COMPLEX128
# type: DT_HALF
# type: DT_UINT32
# type: DT_UINT64
# }
# }
# }
# attr {
# name: "shape"
# type: "shape"
# }
# is_aggregate: true
# is_commutative: true
# Print op names
print(*(op.name for op in complex_ops), sep='\n')
# AccumulateNV2
# AccumulatorApplyGradient
# AccumulatorTakeGradient
# Acos
# Acosh
# Add
# AddN
# AddV2
# Angle
# ApplyAdaMax
# ...
Здесь элементы в complex_ops
являются OpDef
сообщениями, которые вы можете проверить чтобы узнать точную структуру оп. В этом случае get_ops_with_dtypes
просто возвращает каждую операцию, которая имеет один из заданных типов данных среди своих атрибутов type
, поэтому комплексное значение может применяться к одному из входов или выходов.
Другой альтернативой может быть непосредственный поиск ядер, работающих с интересующими вас типами данных. Ядра хранятся в виде сообщений KernelDef
, которые не содержат всей информации об операциях, но например, у них есть информация об устройстве, на котором они могут работать, поэтому вы также можете запросить ядра, поддерживающие определенное устройство.
import tensorflow as tf
def get_kernels_with_dtypes(dtypes, device_type=None):
from tensorflow.python.framework import kernels
valid_kernels = []
dtype_enums = set(dtype.as_datatype_enum for dtype in dtypes)
reg_kernels = kernels.get_all_registered_kernels()
for kernel in reg_kernels.kernel:
if device_type and kernel.device_type != device_type:
continue
for const in kernel.constraint:
if any(t in dtype_enums for t in const.allowed_values.list.type):
valid_kernels.append(kernel)
break
# Sort by name for convenience
return sorted(valid_kernels, key=lambda kernel: kernel.op)
complex_dtypes = [tf.complex64, tf.complex128]
complex_gpu_kernels = get_kernels_with_dtypes(complex_dtypes, device_type='GPU')
# Print one kernel
print(complex_gpu_kernels[0])
# op: "Add"
# device_type: "GPU"
# constraint {
# name: "T"
# allowed_values {
# list {
# type: DT_COMPLEX64
# }
# }
# }
# Print kernel op names
print(*(kernel.op for kernel in complex_gpu_kernels), sep='\n')
# Add
# Add
# AddN
# AddN
# AddV2
# AddV2
# Assign
# Assign
# AssignVariableOp
# AssignVariableOp
# ...
Проблема в том, что вы никогда не используете ops или ядра напрямую, когда программируете с TensorFlow на Python. Функции Python принимают аргументы, которые вы им даете, проверяете их и производите один или несколько новых операций на графике, обычно возвращая вам выходные значения последнего из них. Поэтому, в конце концов, выяснение того, какие ops / ядра вам подходят, требует небольшой проверки. Например, рассмотрим следующие примеры:
import tensorflow as tf
with tf.Graph().as_default():
# Matrix multiplication: (2, 3) x (3, 4)
tf.matmul([[1, 2, 3], [4, 5, 6]], [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
# Print all op names and types
all_ops = tf.get_default_graph().get_operations()
print(*(f'Op name: {op.name}, Op type: {op.type}' for op in all_ops), sep='\n')
# Op name: MatMul/a, Op type: Const
# Op name: MatMul/b, Op type: Const
# Op name: MatMul, Op type: MatMul
with tf.Graph().as_default():
# Matrix multiplication: (1, 2, 3) x (1, 3, 4)
tf.matmul([[[1, 2, 3], [4, 5, 6]]], [[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]])
# Print all op names and types
all_ops = tf.get_default_graph().get_operations()
print(*(f'Op name: {op.name}, Op type: {op.type}' for op in all_ops), sep='\n')
# Op name: MatMul/a, Op type: Const
# Op name: MatMul/b, Op type: Const
# Op name: MatMul, Op type: BatchMatMul
Здесь та же самая функция Python tf.matmul
выдает типы операций в каждом случае. Первые два операции - Const
в обоих случаях, что является результатом преобразования данных списков в тензоры TensorFlow, но третий - MatMul
в одном случае и BatchedMatMul
в другом, потому что во втором случае вход имеет одно дополнительное начальное измерение.
В любом случае, если вы можете объединить описанные выше подходы, чтобы узнать всю информацию о ядрах и операциях об одном интересующем вас имени операции:
def get_op_info(op_name):
from tensorflow.python.framework import ops
from tensorflow.python.framework import kernels
reg_ops = ops.op_def_registry.get_registered_ops()
op_def = reg_ops[op_name]
op_kernels = list(kernels.get_registered_kernels_for_op(op_name).kernel)
return op_def, op_kernels
# Get MatMul information
matmul_def, matmul_kernels = get_op_info('MatMul')
# Print op definition
print(matmul_def)
# name: "MatMul"
# input_arg {
# name: "a"
# type_attr: "T"
# }
# input_arg {
# name: "b"
# type_attr: "T"
# }
# output_arg {
# name: "product"
# type_attr: "T"
# }
# attr {
# name: "transpose_a"
# type: "bool"
# default_value {
# b: false
# }
# }
# attr {
# name: "transpose_b"
# type: "bool"
# default_value {
# b: false
# }
# }
# attr {
# name: "T"
# type: "type"
# allowed_values {
# list {
# type: DT_BFLOAT16
# type: DT_HALF
# type: DT_FLOAT
# type: DT_DOUBLE
# type: DT_INT32
# type: DT_COMPLEX64
# type: DT_COMPLEX128
# }
# }
# }
# Total number of matrix multiplication kernels
print(len(matmul_kernels))
# 24
# Print one kernel definition
print(matmul_kernels[0])
# op: "MatMul"
# device_type: "CPU"
# constraint {
# name: "T"
# allowed_values {
# list {
# type: DT_FLOAT
# }
# }
# }