Caffe2 - C++ API
A deep learning, cross platform ML framework
python_arg_parser.h
1 #pragma once
2 
3 // Parse arguments to Python functions implemented in C++
4 // This is similar to PyArg_ParseTupleAndKeywords(), but specifically handles
5 // the types relevant to PyTorch and distinguishes between overloaded function
6 // signatures.
7 //
8 // Example:
9 //
10 // static PythonArgParser parser({
11 // "norm(Scalar p, int64_t dim, bool keepdim=False)",
12 // "norm(Scalar p=2)",
13 // });
14 // ParsedArgs<3> parsed_args;
15 // auto r = parser.parse(args, kwargs, parsed_args);
16 // if (r.idx == 0) {
17 // norm(r.scalar(0), r.int64(1), r.bool(0));
18 // } else {
19 // norm(r.scalar(0));
20 // }
21 //
22 // We auto-generate most uses of PythonArgParser; the generated files
23 // are torch/csrc/autograd/generated/python_*.cpp
24 //
25 // Some gotchas that you should watch out for:
26 //
27 // - Note [Order of overloads matters]
28 // Order of overloads matters. A set of input arguments may
29 // bind to multiple argument specs; we will always pick the
30 // first one in PythonArgParser. However, when you are writing
31 // overloads in, e.g., native_functions.yaml, you don't have to
32 // worry about what order you write them, because the code
33 // generation logic always gives the overloads a canonical
34 // order, where Tensor overloads come first, before Scalar overloads.
35 // This logic is in sort_declarations in
36 // tools/autograd/gen_python_functions.py
37 //
38 // - Zero-dim tensors (e.g., torch.tensor(2)) bind to both
39 // Scalar and Tensor, UNLESS they require grad (in which case
40 // they only bind to Tensor).
41 
42 
43 #include <torch/csrc/python_headers.h>
44 
45 #include <torch/csrc/Device.h>
46 #include <torch/csrc/Dtype.h>
47 #include <torch/csrc/DynamicTypes.h>
48 #include <torch/csrc/Exceptions.h>
49 #include <torch/csrc/Generator.h>
50 #include <torch/csrc/autograd/generated/VariableType.h>
51 #include <torch/csrc/autograd/python_variable.h>
52 #include <torch/csrc/jit/tracer.h>
53 #include <torch/csrc/tensor/python_tensor.h>
54 #include <torch/csrc/utils/numpy_stub.h>
55 #include <torch/csrc/utils/object_ptr.h>
56 #include <torch/csrc/utils/python_numbers.h>
57 #include <torch/csrc/utils/python_strings.h>
58 #include <torch/csrc/utils/six.h>
59 #include <torch/csrc/autograd/variable.h>
60 
61 #include <ATen/ATen.h>
62 
63 #include <array>
64 #include <cstddef>
65 #include <memory>
66 #include <sstream>
67 #include <string>
68 #include <vector>
69 
70 namespace torch {
71 
72 enum class ParameterType {
73  TENSOR, SCALAR, INT64, DOUBLE, TENSOR_LIST, INT_LIST, GENERATOR,
74  BOOL, STORAGE, PYOBJECT, SCALARTYPE, LAYOUT, DEVICE, STRING
75 };
76 
77 struct FunctionParameter;
78 struct FunctionSignature;
79 struct PythonArgs;
80 
81 // Contains bound Python arguments in declaration order
82 template<int N>
83 struct ParsedArgs {
84  ParsedArgs() : args() { }
85  PyObject* args[N];
86 };
87 
89  explicit PythonArgParser(std::vector<std::string> fmts, bool traceable=false);
90 
91  template<int N>
92  inline PythonArgs parse(PyObject* args, PyObject* kwargs, ParsedArgs<N>& dst);
93 
94 private:
95  [[noreturn]]
96  void print_error(PyObject* args, PyObject* kwargs, PyObject* parsed_args[]);
97  PythonArgs raw_parse(PyObject* args, PyObject* kwargs, PyObject* parsed_args[]);
98 
99  std::vector<FunctionSignature> signatures_;
100  std::string function_name;
101  ssize_t max_args;
102  bool traceable;
103 };
104 
105 struct PythonArgs {
106  PythonArgs(int idx, bool traceable, const FunctionSignature& signature, PyObject** args)
107  : idx(idx)
108  , traceable(traceable)
109  , signature(signature)
110  , args(args) {}
111 
112  int idx;
113  bool traceable;
114  const FunctionSignature& signature;
115  PyObject** args;
116 
117  inline at::Tensor tensor(int i);
118  inline at::Scalar scalar(int i);
119  inline at::Scalar scalarWithDefault(int i, at::Scalar default_scalar);
120  inline std::vector<at::Tensor> tensorlist(int i);
121  template<int N>
122  inline std::array<at::Tensor, N> tensorlist_n(int i);
123  inline std::vector<int64_t> intlist(int i);
124  inline std::vector<int64_t> intlistWithDefault(int i, std::vector<int64_t> default_intlist);
125  inline at::Generator* generator(int i);
126  inline at::Storage storage(int i);
127  inline at::ScalarType scalartype(int i);
128  inline at::ScalarType scalartypeWithDefault(int i, at::ScalarType default_scalartype);
129  inline c10::optional<at::ScalarType> scalartypeOptional(int i);
130  inline c10::optional<at::Scalar> scalarOptional(int i);
131  inline c10::optional<int64_t> toInt64Optional(int i);
132  inline const THPLayout& layout(int i);
133  inline const THPLayout& layoutWithDefault(int i, const THPLayout& default_layout);
134  inline at::Device device(int i);
135  inline at::Device deviceWithDefault(int i, const at::Device& default_device);
136  inline c10::optional<at::Device> deviceOptional(int i);
137  inline std::string string(int i);
138  inline PyObject* pyobject(int i);
139  inline int64_t toInt64(int i);
140  inline int64_t toInt64WithDefault(int i, int64_t default_int);
141  inline double toDouble(int i);
142  inline double toDoubleWithDefault(int i, double default_double);
143  inline bool toBool(int i);
144  inline bool toBoolWithDefault(int i, bool default_bool);
145  inline bool isNone(int i);
146 };
147 
149  explicit FunctionSignature(const std::string& fmt);
150 
151  bool parse(PyObject* args, PyObject* kwargs, PyObject* dst[], bool raise_exception);
152  std::string toString() const;
153 
154  std::string name;
155  std::vector<FunctionParameter> params;
156  ssize_t min_args;
157  ssize_t max_args;
158  ssize_t max_pos_args;
159  bool hidden;
160  bool deprecated;
161 };
162 
164  FunctionParameter(const std::string& fmt, bool keyword_only);
165 
166  bool check(PyObject* obj);
167  void set_default_str(const std::string& str);
168  std::string type_name() const;
169 
170  ParameterType type_;
171  bool optional;
172  bool allow_none;
173  bool keyword_only;
174  bool allow_numbers_as_tensors = false;
175  int size;
176  std::string name;
177  // having this as a raw PyObject * will presumably leak it, but these are only held by static objects
178  // anyway, and Py_Finalize can already be called when this is destructed.
179  PyObject *python_name;
180  at::Scalar default_scalar;
181  std::vector<int64_t> default_intlist;
182  union {
183  bool default_bool;
184  int64_t default_int;
185  double default_double;
186  at::ScalarType default_scalartype;
187  THPLayout* default_layout;
188  };
189 };
190 
191 template<int N>
192 inline PythonArgs PythonArgParser::parse(PyObject* args, PyObject* kwargs, ParsedArgs<N>& dst) {
193  if (N < max_args) {
194  throw ValueError("PythonArgParser: dst ParsedArgs buffer does not have enough capacity, expected %d (got %d)",
195  (int)max_args, N);
196  }
197  return raw_parse(args, kwargs, dst.args);
198 }
199 
200 inline at::Tensor PythonArgs::tensor(int i) {
201  PyObject* obj = args[i];
202  if (!obj) return at::Tensor();
203  if (!THPVariable_Check(obj)) {
204  at::Scalar scalar;
205  if (THPUtils_checkLong(obj)) {
206  scalar = at::Scalar(THPUtils_unpackLong(obj));
207  } else if (THPUtils_checkDouble(obj)) {
208  scalar = at::Scalar(THPUtils_unpackDouble(obj));
209  } else {
210  // NB: Are you here because you passed None to a Variable method,
211  // and you expected an undefined tensor to be returned? Don't add
212  // a test for Py_None here; instead, you need to mark the argument
213  // as *allowing none*; you can do this by writing 'Tensor?' instead
214  // of 'Tensor' in the ATen metadata.
215  throw TypeError("expected Tensor as argument %d, but got %s", i,
216  Py_TYPE(obj)->tp_name);
217  }
218  auto tensor = scalar_to_tensor(scalar);
219  tensor.unsafeGetTensorImpl()->set_wrapped_number(true);
220  return autograd::make_variable(tensor);
221  }
222  return reinterpret_cast<THPVariable*>(obj)->cdata;
223 }
224 
225 inline at::Scalar PythonArgs::scalar(int i) {
226  return scalarWithDefault(i, signature.params[i].default_scalar);
227 }
228 
229 inline at::Scalar PythonArgs::scalarWithDefault(int i, at::Scalar default_scalar) {
230  if (!args[i]) return default_scalar;
231  // Zero-dim tensors are converted to Scalars as-is. Note this doesn't currently
232  // handle most NumPy scalar types except np.float64.
233  if (THPVariable_Check(args[i])) {
234  return ((THPVariable*)args[i])->cdata.item();
235  }
236  if (THPUtils_checkLong(args[i])) {
237  return at::Scalar(static_cast<int64_t>(THPUtils_unpackLong(args[i])));
238  }
239 
240  if (PyComplex_Check(args[i])) {
241  return at::Scalar(THPUtils_unpackComplexDouble(args[i]));
242  }
243  return at::Scalar(THPUtils_unpackDouble(args[i]));
244 }
245 
246 inline c10::optional<at::Scalar> PythonArgs::scalarOptional(int i) {
247  if (!args[i]) return c10::nullopt;
248  return scalar(i);
249 }
250 
251 inline std::vector<at::Tensor> PythonArgs::tensorlist(int i) {
252  if (!args[i]) return std::vector<at::Tensor>();
253  auto tuple = six::isTuple(args[i]);
254  THPObjectPtr arg = six::maybeAsTuple(args[i]);
255  auto size = tuple ? PyTuple_GET_SIZE(arg.get()) : PyList_GET_SIZE(arg.get());
256  std::vector<at::Tensor> res(size);
257  for (int idx = 0; idx < size; idx++) {
258  PyObject* obj = tuple ? PyTuple_GET_ITEM(arg.get(), idx) : PyList_GET_ITEM(arg.get(), idx);
259  if (!THPVariable_Check(obj)) {
260  throw TypeError("expected Tensor as element %d in argument %d, but got %s",
261  idx, i, Py_TYPE(obj)->tp_name);
262  }
263  res[idx] = reinterpret_cast<THPVariable*>(obj)->cdata;
264  }
265  return res;
266 }
267 
268 template<int N>
269 inline std::array<at::Tensor, N> PythonArgs::tensorlist_n(int i) {
270  auto res = std::array<at::Tensor, N>();
271  if (!args[i]) return res;
272  auto tuple = six::isTuple(args[i]);
273  THPObjectPtr arg = six::maybeAsTuple(args[i]);
274  auto size = tuple ? PyTuple_GET_SIZE(arg.get()) : PyList_GET_SIZE(arg.get());
275  if (size != N) {
276  throw TypeError("expected tuple of %d elements but got %d", N, (int)size);
277  }
278  for (int idx = 0; idx < size; idx++) {
279  PyObject* obj = tuple ? PyTuple_GET_ITEM(arg.get(), idx) : PyList_GET_ITEM(arg.get(), idx);
280  if (!THPVariable_Check(obj)) {
281  throw TypeError("expected Tensor as element %d in argument %d, but got %s",
282  idx, i, Py_TYPE(obj)->tp_name);
283  }
284  res[idx] = reinterpret_cast<THPVariable*>(obj)->cdata;
285  }
286  return res;
287 }
288 
289 inline std::vector<int64_t> PythonArgs::intlist(int i) {
290  return intlistWithDefault(i, signature.params[i].default_intlist);
291 }
292 
293 inline std::vector<int64_t> PythonArgs::intlistWithDefault(int i, std::vector<int64_t> default_intlist) {
294  if (!args[i]) return default_intlist;
295  PyObject* arg = args[i];
296  auto size = signature.params[i].size;
297  if (size > 0 && THPUtils_checkLong(arg)) {
298  return std::vector<int64_t>(size, THPUtils_unpackIndex(arg));
299  }
300  auto tuple = PyTuple_Check(arg);
301  size = tuple ? PyTuple_GET_SIZE(arg) : PyList_GET_SIZE(arg);
302  std::vector<int64_t> res(size);
303  for (int idx = 0; idx < size; idx++) {
304  PyObject* obj = tuple ? PyTuple_GET_ITEM(arg, idx) : PyList_GET_ITEM(arg, idx);
305  try {
306  // Elements of torch.Size are tensors during tracing, and we need to record extra
307  // information before they are turned into an IntArrayRef
308  if (traceable && jit::tracer::isTracing() && THPVariable_Check(obj)) {
309  auto & var = THPVariable_Unpack(obj);
310  jit::tracer::ArgumentStash::stashIntArrayRefElem(
311  signature.params[i].name, size, idx, var);
312  res[idx] = var.item<int64_t>();
313  continue;
314  } else {
315  res[idx] = THPUtils_unpackIndex(obj);
316  }
317  } catch (const std::exception &e) {
318  throw TypeError("%s(): argument '%s' must be %s, but found element of type %s at pos %d",
319  signature.name.c_str(), signature.params[i].name.c_str(),
320  signature.params[i].type_name().c_str(), Py_TYPE(obj)->tp_name, idx + 1);
321  }
322  }
323  return res;
324 }
325 
326 inline at::ScalarType PythonArgs::scalartypeWithDefault(int i, at::ScalarType default_scalartype) {
327  if (!args[i]) return default_scalartype;
328  return scalartype(i);
329 }
330 
331 inline at::ScalarType PythonArgs::scalartype(int i) {
332  if (!args[i]) {
333  auto scalartype = signature.params[i].default_scalartype;
334  return (scalartype == at::ScalarType::Undefined) ?
335  torch::tensors::get_default_tensor_type().scalarType() : scalartype;
336  }
337  return reinterpret_cast<THPDtype*>(args[i])->scalar_type;
338 }
339 
340 inline c10::optional<at::ScalarType> PythonArgs::scalartypeOptional(int i) {
341  if (!args[i])
342  return c10::nullopt;
343  return scalartype(i);
344 }
345 
346 inline const THPLayout& PythonArgs::layout(int i) {
347  if (!args[i]) return *signature.params[i].default_layout;
348  return *reinterpret_cast<THPLayout*>(args[i]);
349 }
350 
351 inline const THPLayout& PythonArgs::layoutWithDefault(int i, const THPLayout& default_layout) {
352  if (!args[i]) return default_layout;
353  return layout(i);
354 }
355 
356 static std::string cuda_str = "cuda";
357 static std::string cpu_str = "cpu";
358 static std::string cuda_prefix = "cuda:";
359 static std::string cpu_prefix = "cpu:";
360 
361 inline at::Device PythonArgs::device(int i) {
362  if (!args[i]) {
363  const auto& default_tensor_type = torch::tensors::get_default_tensor_type();
364  return at::Device(default_tensor_type.device_type());
365  }
366  if (THPDevice_Check(args[i])) {
367  const auto device = reinterpret_cast<THPDevice*>(args[i]);
368  return device->device;
369  }
370  if (THPUtils_checkLong(args[i])) {
371  const auto device_index = THPUtils_unpackLong(args[i]);
372  AT_CHECK(device_index >= 0, "Device index must not be negative");
373  return at::Device(at::DeviceType::CUDA, device_index);
374  }
375  const std::string &device_str = THPUtils_unpackString(args[i]);
376  return at::Device(device_str);
377 }
378 
379 inline at::Device PythonArgs::deviceWithDefault(int i, const at::Device& default_device) {
380  if (!args[i]) return default_device;
381  return device(i);
382 }
383 
384 inline c10::optional<at::Device> PythonArgs::deviceOptional(int i) {
385  if (!args[i])
386  return c10::nullopt;
387  return device(i);
388 }
389 
390 inline std::string PythonArgs::string(int i) {
391  if (!args[i]) return "";
392  return THPUtils_unpackString(args[i]);
393 }
394 
395 inline int64_t PythonArgs::toInt64(int i) {
396  if (!args[i]) return signature.params[i].default_int;
397  if (traceable && jit::tracer::isTracing() && THPVariable_Check(args[i])) {
398  auto & var = THPVariable_Unpack(args[i]);
399  jit::tracer::ArgumentStash::stashValue(
400  signature.params[i].name, idx, var, jit::IntType::get());
401  }
402  return THPUtils_unpackLong(args[i]);
403 }
404 
405 inline int64_t PythonArgs::toInt64WithDefault(int i, int64_t default_int) {
406  if (!args[i]) return default_int;
407  return toInt64(i);
408 }
409 
410 inline c10::optional<int64_t> PythonArgs::toInt64Optional(int i) {
411  if (!args[i])
412  return c10::nullopt;
413  return toInt64(i);
414 }
415 
416 inline double PythonArgs::toDouble(int i) {
417  if (!args[i]) return signature.params[i].default_double;
418  return THPUtils_unpackDouble(args[i]);
419 }
420 
421 inline double PythonArgs::toDoubleWithDefault(int i, double default_double) {
422  if (!args[i]) return default_double;
423  return toDouble(i);
424 }
425 
426 inline bool PythonArgs::toBool(int i) {
427  if (!args[i]) return signature.params[i].default_bool;
428  return args[i] == Py_True;
429 }
430 
431 inline bool PythonArgs::toBoolWithDefault(int i, bool default_bool) {
432  if (!args[i]) return default_bool;
433  return toBool(i);
434 }
435 
436 inline bool PythonArgs::isNone(int i) {
437  return args[i] == nullptr;
438 }
439 
440 inline at::Generator* PythonArgs::generator(int i) {
441  if (!args[i]) return nullptr;
442  return reinterpret_cast<THPGenerator*>(args[i])->cdata;
443 }
444 
445 inline at::Storage PythonArgs::storage(int i) {
446  if (!args[i]) return at::Storage();
447  return createStorage(args[i]);
448 }
449 
450 inline PyObject* PythonArgs::pyobject(int i) {
451  if (!args[i]) return Py_None;
452  return args[i];
453 }
454 
455 } // namespace torch
Scalar represents a 0-dimensional tensor which contains a single element.
Definition: Scalar.h:22
Represents a a compute device on which a tensor is located.
Definition: Device.h:30
Definition: Dtype.h:9
Definition: jit_type.h:17