Repository: scikit-hep/vector

Issue:
feat: new constructor
Placeholder issue for the constructor work, main feature of 0.9.

Discussion:
codecov-commenter: # [Codecov](https://codecov.io/gh/scikit-hep/vector/pull/89?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep) Report
> Merging [#89](https://codecov.io/gh/scikit-hep/vector/pull/89?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep) (4a7b1cf) into [main](https://codecov.io/gh/scikit-hep/vector/commit/ba4e8b45fe561881c4cb6b97e8532819f297b30c?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep) (ba4e8b4) will **decrease** coverage by `0.37%`.
> The diff coverage is `60.00%`.

```diff
@@            Coverage Diff             @@
##             main      #89      +/-   ##
==========================================
- Coverage   82.45%   82.07%   -0.38%     
==========================================
  Files          96       96              
  Lines       10388    10251     -137     
==========================================
- Hits         8565     8414     -151     
- Misses       1823     1837      +14     
```


| [Impacted Files](https://codecov.io/gh/scikit-hep/vector/pull/89?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep) | Coverage Δ | |
|---|---|---|
| [src/vector/backends/numpy.py](https://codecov.io/gh/scikit-hep/vector/pull/89/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep#diff-c3JjL3ZlY3Rvci9iYWNrZW5kcy9udW1weS5weQ==) | `77.50% <ø> (ø)` | |
| [src/vector/backends/object.py](https://codecov.io/gh/scikit-hep/vector/pull/89/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep#diff-c3JjL3ZlY3Rvci9iYWNrZW5kcy9vYmplY3QucHk=) | `72.73% <47.05%> (-0.34%)` | :arrow_down: |
| [src/vector/\_methods.py](https://codecov.io/gh/scikit-hep/vector/pull/89/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep#diff-c3JjL3ZlY3Rvci9fbWV0aG9kcy5weQ==) | `78.53% <72.22%> (-0.15%)` | :arrow_down: |
| [src/vector/backends/\_numba\_object.py](https://codecov.io/gh/scikit-hep/vector/pull/89/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep#diff-c3JjL3ZlY3Rvci9iYWNrZW5kcy9fbnVtYmFfb2JqZWN0LnB5) | `66.16% <0.00%> (-3.01%)` | :arrow_down: |
| [src/vector/\_\_init\_\_.py](https://codecov.io/gh/scikit-hep/vector/pull/89/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep#diff-c3JjL3ZlY3Rvci9fX2luaXRfXy5weQ==) | `79.48% <0.00%> (-2.57%)` | :arrow_down: |
| [src/vector/\_typeutils.py](https://codecov.io/gh/scikit-hep/vector/pull/89/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep#diff-c3JjL3ZlY3Rvci9fdHlwZXV0aWxzLnB5) | `93.18% <0.00%> (-2.28%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/scikit-hep/vector/pull/89?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/scikit-hep/vector/pull/89?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep). Last update [ba4e8b4...4a7b1cf](https://codecov.io/gh/scikit-hep/vector/pull/89?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=scikit-hep).

Saransh-cpp: I think this PR should work now, given that we are fine with passing generic coordinates into `MomentumObject` classes. The same is under discussion for the shortcut constructors (`obj` here), which can be handled in another PR. 
henryiii: Feel free to turn of the unreachable check for mypy if it triggers too much, and 1-2 type ignores need `[misc]` added.
Saransh-cpp: Done! I've also refactored out the type safety code from the `obj` constructor.
henryiii: I can't approve "my own" (according to GitHub) PR, so verbally approving here!

Patch:
diff --git a/docs/changelog.md b/docs/changelog.md
index 93ac38d..06f664f 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -14,6 +14,7 @@
 - Pull request template [#271][]
 - docs: add a developer guide [#233][]
 - ci: Test notebooks on PRs [#272][]
+- feat: add constructors for `VectorObject2D` and `MomentumObject2D` [#89][]
 
 [#256]: https://github.com/scikit-hep/vector/pull/256
 [#254]: https://github.com/scikit-hep/vector/pull/254
@@ -27,6 +28,7 @@
 [#271]: https://github.com/scikit-hep/vector/pull/271
 [#233]: https://github.com/scikit-hep/vector/pull/233
 [#272]: https://github.com/scikit-hep/vector/pull/272
+[#89]: https://github.com/scikit-hep/vector/pull/89
 
 ## Version 0.10
 
diff --git a/pyproject.toml b/pyproject.toml
index f79fcbb..616cc69 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -175,6 +175,8 @@ messages_control.disable = [
   "useless-option-value",
   "cast_python_value",
   "unknown-option-value",
+  "no-else-raise",
+  "unidiomatic-typecheck",
 ]
 
 [tool.mypy]
@@ -185,7 +187,6 @@ python_version = "3.8"
 strict = true
 warn_return_any = false
 show_error_codes = true
-warn_unreachable = true
 enable_error_code = [
   "ignore-without-code",
   "truthy-bool",
diff --git a/src/vector/_methods.py b/src/vector/_methods.py
index 448efc3..e6008f7 100644
--- a/src/vector/_methods.py
+++ b/src/vector/_methods.py
@@ -8,6 +8,7 @@ from __future__ import annotations
 import typing
 from contextlib import suppress
 
+import vector
 from vector._typeutils import (
     BoolCollection,
     ScalarCollection,
@@ -144,7 +145,9 @@ class VectorProtocol:
             vectors without momentum-synonyms.
     """
 
-    lib: Module
+    @property
+    def lib(self) -> Module:
+        ...
 
     def _wrap_result(
         self,
@@ -1348,6 +1351,40 @@ class MomentumProtocolLorentz(VectorProtocolLorentz, MomentumProtocolSpatial):
 
 
 class Vector(VectorProtocol):
+    @typing.overload
+    def __new__(cls, *, x: float, y: float) -> vector.VectorObject2D:
+        ...
+
+    @typing.overload
+    def __new__(cls, *, rho: float, phi: float) -> vector.VectorObject2D:
+        ...
+
+    @typing.overload
+    def __new__(cls, *, px: float, py: float) -> vector.MomentumObject2D:
+        ...
+
+    @typing.overload
+    def __new__(cls, *, x: float, py: float) -> vector.MomentumObject2D:
+        ...
+
+    @typing.overload
+    def __new__(cls, *, px: float, y: float) -> vector.MomentumObject2D:
+        ...
+
+    @typing.overload
+    def __new__(cls, *, pt: float, phi: float) -> vector.MomentumObject2D:
+        ...
+
+    @typing.overload
+    def __new__(cls, __azumthal: Azimuthal) -> Vector:
+        ...
+
+    def __new__(cls, *args: Azimuthal, **kwargs: float) -> Vector:
+        if cls is not Vector:
+            return super().__new__(cls)
+
+        return vector.obj(*args, **kwargs)
+
     def to_xy(self) -> VectorProtocolPlanar:
         from vector._compute import planar
 
@@ -2671,10 +2708,11 @@ def _lib_of(*objects: VectorProtocol) -> Module:  # NumPy-like module
     for obj in objects:
         if isinstance(obj, Vector):
             if lib is None:
-                lib = obj.lib
-            elif lib is not obj.lib:
+                # Not sure why mypy complains about member not being assignable here
+                lib = obj.lib  # type: ignore[misc]
+            elif lib is not obj.lib:  # type: ignore[misc]
                 raise TypeError(
-                    f"cannot use {lib} and {obj.lib} in the same calculation"
+                    f"cannot use {lib} and {obj.lib} in the same calculation"  # type: ignore[misc]
                 )
 
     assert lib is not None
diff --git a/src/vector/backends/awkward.py b/src/vector/backends/awkward.py
index 6ca8c27..5c62e91 100644
--- a/src/vector/backends/awkward.py
+++ b/src/vector/backends/awkward.py
@@ -576,12 +576,12 @@ class TemporalAwkwardTau(TemporalAwkward, TemporalTau):
 
 def _class_to_name(cls: type[VectorProtocol]) -> str:
     if issubclass(cls, Momentum):
-        if issubclass(cls, Vector2D):  # type: ignore[unreachable]
-            return "Momentum2D"  # type: ignore[unreachable]
-        elif issubclass(cls, Vector3D):  # type: ignore[unreachable]
-            return "Momentum3D"  # type: ignore[unreachable]
-        elif issubclass(cls, Vector4D):  # type: ignore[unreachable]
-            return "Momentum4D"  # type: ignore[unreachable]
+        if issubclass(cls, Vector2D):
+            return "Momentum2D"
+        elif issubclass(cls, Vector3D):
+            return "Momentum3D"
+        elif issubclass(cls, Vector4D):
+            return "Momentum4D"
     else:
         if issubclass(cls, Vector2D):
             return "Vector2D"
diff --git a/src/vector/backends/numpy.py b/src/vector/backends/numpy.py
index d639584..a516f8a 100644
--- a/src/vector/backends/numpy.py
+++ b/src/vector/backends/numpy.py
@@ -1899,7 +1899,6 @@ def array(*args: typing.Any, **kwargs: typing.Any) -> VectorNumpy:
     else:
         cls = MomentumNumpy2D if is_momentum else VectorNumpy2D
 
-    # VectorNumpy has no constructor, so mypy flags this line
     return cls(*args, **kwargs)
 
 
diff --git a/src/vector/backends/object.py b/src/vector/backends/object.py
index b746209..78b8208 100644
--- a/src/vector/backends/object.py
+++ b/src/vector/backends/object.py
@@ -3,23 +3,27 @@
 # Distributed under the 3-clause BSD license, see accompanying file LICENSE
 # or https://github.com/scikit-hep/vector for details.
 """
-Defines behaviors for Object vectors. New vectors created with the
+Defines behaviors for Object vectors. New vectors created with the respective classes
 
 .. code-block:: python
 
-    vector.obj(...)
-
+    vector.VectorObject2D(x=..., y=...)
 
-function will have these behaviors built in (and will pass them to any derived
-objects).
+will have these behaviors built in (and will pass them to any derived objects).
 
-Additionally, the class methods can also be used to construct object type
-vectors -
+The class methods can also be used to construct object type vectors -
 
 .. code-block:: python
 
     vec = vector.VectorObject2D.from_xy(1, 2)
 
+Additionally, object type vectors can also be constructed using -
+
+.. code-block:: python
+
+    vector.obj(...)
+
+function.
 """
 from __future__ import annotations
 
@@ -56,6 +60,7 @@ from vector._methods import (
     _handler_of,
     _ltype,
     _repr_generic_to_momentum,
+    _repr_momentum_to_generic,
     _ttype,
 )
 from vector._typeutils import FloatArray
@@ -106,7 +111,7 @@ class AzimuthalObjectXY(AzimuthalObject, AzimuthalXY, TupleXY):
 
         Examples:
             >>> import vector
-            >>> v = vector.obj(x=1, y=2)
+            >>> v = vector.VectorObject2D(x=1, y=2)
             >>> az = v.azimuthal
             >>> az.elements
             (1, 2)
@@ -135,7 +140,7 @@ class AzimuthalObjectRhoPhi(AzimuthalObject, AzimuthalRhoPhi, TupleRhoPhi):
 
         Examples:
             >>> import vector
-            >>> v = vector.obj(rho=1, phi=2)
+            >>> v = vector.VectorObject2D(rho=1, phi=2)
             >>> az = v.azimuthal
             >>> az.elements
             (1, 2)
@@ -598,12 +603,27 @@ class VectorObject(Vector):
 class VectorObject2D(VectorObject, Planar, Vector2D):
     """
     Two dimensional vector class for the object backend.
-    Use the class methods -
+
+    Examples:
+        >>> import vector
+        >>> vec = vector.VectorObject2D(x=1, y=2)
+        >>> vec.x, vec.y
+        (1, 2)
+        >>> vec = vector.VectorObject2D(rho=1, phi=2)
+        >>> vec.rho, vec.phi
+        (1, 2)
+        >>> vec = vector.VectorObject2D(azimuthal=vector.backends.object.AzimuthalObjectXY(1, 2))
+        >>> vec.x, vec.y
+        (1, 2)
+
+    The following class methods can also be used to
+    construct 2D object type vectors -
 
     - :meth:`VectorObject2D.from_xy`
     - :meth:`VectorObject2D.from_rhophi`
 
-    to construct 2D Vector objects.
+    Additionally, the :func:`vector.obj` function can
+    also be used to construct 2D object type vectors.
 
     For two dimensional momentum vector objects, see
     :class:`vector.backends.object.MomentumObject2D`.
@@ -625,9 +645,9 @@ class VectorObject2D(VectorObject, Planar, Vector2D):
             >>> import vector
             >>> vec = vector.VectorObject2D.from_xy(1, 2)
             >>> vec
-            vector.obj(x=1, y=2)
+            VectorObject2D(x=1, y=2)
         """
-        return cls(AzimuthalObjectXY(x, y))
+        return cls(azimuthal=AzimuthalObjectXY(x, y))
 
     @classmethod
     def from_rhophi(cls, rho: float, phi: float) -> VectorObject2D:
@@ -641,17 +661,45 @@ class VectorObject2D(VectorObject, Planar, Vector2D):
             >>> import vector
             >>> vec = vector.VectorObject2D.from_rhophi(1, 2)
             >>> vec
-            vector.obj(rho=1, phi=2)
+            VectorObject2D(rho=1, phi=2)
         """
-        return cls(AzimuthalObjectRhoPhi(rho, phi))
+        return cls(azimuthal=AzimuthalObjectRhoPhi(rho, phi))
 
-    def __init__(self, azimuthal: AzimuthalObject) -> None:
-        self.azimuthal = azimuthal
+    def __init__(
+        self, azimuthal: AzimuthalObject | None = None, **kwargs: float
+    ) -> None:
+
+        if not _is_type_safe(kwargs):
+            raise TypeError("a coordinate must be of the type int or float")
+
+        for k, v in kwargs.copy().items():
+            kwargs.pop(k)
+            kwargs[_repr_momentum_to_generic.get(k, k)] = v
+
+        if not kwargs and azimuthal is not None:
+            self.azimuthal = azimuthal
+        elif kwargs and azimuthal is None:
+            if set(kwargs) == {"x", "y"}:
+                self.azimuthal = AzimuthalObjectXY(kwargs["x"], kwargs["y"])
+            elif set(kwargs) == {"rho", "phi"}:
+                self.azimuthal = AzimuthalObjectRhoPhi(kwargs["rho"], kwargs["phi"])
+            else:
+                complaint = """unrecognized combination of coordinates, allowed combinations are:\n
+                    x= y=
+                    rho= phi=""".replace(
+                    "                    ", "    "
+                )
+                if type(self) == VectorObject2D:
+                    raise TypeError(complaint)
+                else:
+                    raise TypeError(f"{complaint}\n\nor their momentum equivalents")
+        else:
+            raise TypeError("must give Azimuthal if not giving keyword arguments")
 
     def __repr__(self) -> str:
         aznames = _coordinate_class_to_names[_aztype(self)]
         out = [f"{x}={getattr(self.azimuthal, x)}" for x in aznames]
-        return "vector.obj(" + ", ".join(out) + ")"
+        return "VectorObject2D(" + ", ".join(out) + ")"
 
     def __array__(self) -> FloatArray:
         from vector.backends.numpy import VectorNumpy2D
@@ -690,7 +738,7 @@ class VectorObject2D(VectorObject, Planar, Vector2D):
             and issubclass(returns[0], Azimuthal)
         ):
             azcoords = _coord_object_type[returns[0]](result[0], result[1])
-            return cls.ProjectionClass2D(azcoords)
+            return cls.ProjectionClass2D(azimuthal=azcoords)
 
         elif (
             len(returns) == 2
@@ -699,7 +747,7 @@ class VectorObject2D(VectorObject, Planar, Vector2D):
             and returns[1] is None
         ):
             azcoords = _coord_object_type[returns[0]](result[0], result[1])
-            return cls.ProjectionClass2D(azcoords)
+            return cls.ProjectionClass2D(azimuthal=azcoords)
 
         elif (
             len(returns) == 2
@@ -778,6 +826,21 @@ class MomentumObject2D(PlanarMomentum, VectorObject2D):
     """
     Two dimensional momentum vector class for the object backend.
 
+    Examples:
+        >>> import vector
+        >>> vec = vector.MomentumObject2D(px=1, py=2)
+        >>> vec.px, vec.py
+        (1, 2)
+        >>> vec = vector.MomentumObject2D(pt=1, phi=2)
+        >>> vec.pt, vec.phi
+        (1, 2)
+        >>> vec = vector.MomentumObject2D(azimuthal=vector.backends.object.AzimuthalObjectXY(1, 2))
+        >>> vec.px, vec.py
+        (1, 2)
+
+    The :func:`vector.obj` function can also be
+    used to construct 2D momentum object type vectors.
+
     For two dimensional vector objects, see
     :class:`vector.backends.object.VectorObject2D`.
     """
@@ -788,7 +851,7 @@ class MomentumObject2D(PlanarMomentum, VectorObject2D):
         for x in aznames:
             y = _repr_generic_to_momentum.get(x, x)
             out.append(f"{y}={getattr(self.azimuthal, x)}")
-        return "vector.obj(" + ", ".join(out) + ")"
+        return "MomentumObject2D(" + ", ".join(out) + ")"
 
     def __array__(self) -> FloatArray:
         from vector.backends.numpy import MomentumNumpy2D
@@ -1011,7 +1074,7 @@ class VectorObject3D(VectorObject, Spatial, Vector3D):
             and returns[1] is None
         ):
             azcoords = _coord_object_type[returns[0]](result[0], result[1])
-            return cls.ProjectionClass2D(azcoords)
+            return cls.ProjectionClass2D(azimuthal=azcoords)
 
         elif (
             len(returns) == 2
@@ -1587,7 +1650,7 @@ class VectorObject4D(VectorObject, Lorentz, Vector4D):
             and returns[1] is None
         ):
             azcoords = _coord_object_type[returns[0]](result[0], result[1])
-            return cls.ProjectionClass2D(azcoords)
+            return cls.ProjectionClass2D(azimuthal=azcoords)
 
         elif (
             len(returns) == 2
@@ -1822,6 +1885,13 @@ class MomentumObject4D(LorentzMomentum, VectorObject4D):
         self.temporal = TemporalObjectTau(mass)
 
 
+def _is_type_safe(coordinates: dict[str, typing.Any]) -> bool:
+    for _, value in coordinates.items():
+        if not issubclass(type(value), numbers.Real) or isinstance(value, bool):
+            return False
+    return True
+
+
 def _gather_coordinates(
     planar_class: type[VectorObject2D],
     spatial_class: type[VectorObject3D],
@@ -1875,7 +1945,7 @@ def _gather_coordinates(
 
     if not coordinates:
         if azimuthal is not None and longitudinal is None and temporal is None:
-            return planar_class(azimuthal)
+            return planar_class(azimuthal=azimuthal)
         if azimuthal is not None and longitudinal is not None and temporal is None:
             return spatial_class(azimuthal, longitudinal)
         if azimuthal is not None and longitudinal is not None and temporal is not None:
@@ -3090,9 +3160,8 @@ def obj(**coordinates: float) -> VectorObject:
     is_momentum = False
     generic_coordinates = {}
 
-    for _, value in coordinates.items():
-        if not issubclass(type(value), numbers.Real) or isinstance(value, bool):
-            raise TypeError("a coordinate must be of the type int or float")
+    if not _is_type_safe(coordinates):
+        raise TypeError("a coordinate must be of the type int or float")
 
     if "px" in coordinates:
         is_momentum = True


Test patch:
diff --git a/tests/backends/test_object.py b/tests/backends/test_object.py
index 7789c5c..4b7b8f7 100644
--- a/tests/backends/test_object.py
+++ b/tests/backends/test_object.py
@@ -11,6 +11,66 @@ import pytest
 import vector
 
 
+def test_constructors_2D():
+    vec = vector.VectorObject2D(x=1, y=2)
+    assert vec.x == 1
+    assert vec.y == 2
+
+    vec = vector.VectorObject2D(rho=1, phi=2)
+    assert vec.rho == 1
+    assert vec.phi == 2
+
+    vec = vector.VectorObject2D(
+        azimuthal=vector.backends.object.AzimuthalObjectXY(1, 2)
+    )
+    assert vec.x == 1
+    assert vec.y == 2
+
+    with pytest.raises(TypeError):
+        vector.VectorObject2D(rho=1, wow=2)
+
+    with pytest.raises(TypeError):
+        vector.VectorObject2D(rho=complex(1, 2), wow=2)
+
+    with pytest.raises(TypeError):
+        vector.VectorObject2D()
+
+    vec = vector.MomentumObject2D(px=1, py=2)
+    assert vec.px == 1
+    assert vec.py == 2
+    assert vec.x == 1
+    assert vec.y == 2
+
+    vec = vector.MomentumObject2D(x=1, py=2)
+    assert vec.px == 1
+    assert vec.py == 2
+    assert vec.x == 1
+    assert vec.y == 2
+
+    vec = vector.MomentumObject2D(pt=1, phi=2)
+    assert vec.pt == 1
+    assert vec.phi == 2
+    assert vec.rho == 1
+    assert vec.phi == 2
+
+    vec = vector.MomentumObject2D(
+        azimuthal=vector.backends.object.AzimuthalObjectXY(1, 2)
+    )
+    assert vec.px == 1
+    assert vec.py == 2
+    assert vec.x == 1
+    assert vec.y == 2
+
+    with pytest.raises(TypeError):
+        vector.MomentumObject2D(rho=1, wow=2)
+
+    with pytest.raises(TypeError):
+        vector.MomentumObject2D(rho=False, wow=2)
+
+    with pytest.raises(TypeError):
+        vector.MomentumObject2D()
+
+
 def test_array_casting():
     obj = vector.obj(x=1, y=1)
     assert isinstance(obj, vector.VectorObject2D)
diff --git a/tests/compute/planar/test_deltaphi.py b/tests/compute/planar/test_deltaphi.py
index 6a78f6e..de9002d 100644
--- a/tests/compute/planar/test_deltaphi.py
+++ b/tests/compute/planar/test_deltaphi.py
@@ -14,39 +14,39 @@ import vector.backends.object
 
 def test_xy_xy():
     v1 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(1, 2)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(1, 2)
     )
     v2 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(3, 4)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(3, 4)
     )
     assert v1.deltaphi(v2) == pytest.approx(math.atan2(2, 1) - math.atan2(4, 3))
 
 
 def test_xy_rhophi():
     v1 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(1, 2)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(1, 2)
     )
     v2 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectRhoPhi(3, 4)
+        azimuthal=vector.backends.object.AzimuthalObjectRhoPhi(3, 4)
     )
     assert v1.deltaphi(v2) == pytest.approx(math.atan2(2, 1) - 4)
 
 
 def test_rhophi_xy():
     v1 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectRhoPhi(1, 2)
+        azimuthal=vector.backends.object.AzimuthalObjectRhoPhi(1, 2)
     )
     v2 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(3, 4)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(3, 4)
     )
     assert v1.deltaphi(v2) == pytest.approx(2 - math.atan2(4, 3))
 
 
 def test_rhophi_rhophi():
     v1 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectRhoPhi(1, 2)
+        azimuthal=vector.backends.object.AzimuthalObjectRhoPhi(1, 2)
     )
     v2 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectRhoPhi(3, 4)
+        azimuthal=vector.backends.object.AzimuthalObjectRhoPhi(3, 4)
     )
     assert v1.deltaphi(v2) == pytest.approx(2 - 4)
diff --git a/tests/compute/planar/test_phi.py b/tests/compute/planar/test_phi.py
index 6ae5cbc..253270e 100644
--- a/tests/compute/planar/test_phi.py
+++ b/tests/compute/planar/test_phi.py
@@ -14,13 +14,13 @@ import vector.backends.object
 
 def test_xy():
     vec = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(3, 4)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(3, 4)
     )
     assert vec.phi == pytest.approx(math.atan2(4, 3))
 
 
 def test_rhophi():
     vec = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectRhoPhi(5, math.atan2(4, 3))
+        azimuthal=vector.backends.object.AzimuthalObjectRhoPhi(5, math.atan2(4, 3))
     )
     assert vec.phi == pytest.approx(math.atan2(4, 3))
diff --git a/tests/compute/planar/test_rho.py b/tests/compute/planar/test_rho.py
index 352f547..0884927 100644
--- a/tests/compute/planar/test_rho.py
+++ b/tests/compute/planar/test_rho.py
@@ -14,13 +14,13 @@ import vector.backends.object
 
 def test_xy():
     vec = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(3, 4)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(3, 4)
     )
     assert vec.rho == pytest.approx(5)
 
 
 def test_rhophi():
     vec = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectRhoPhi(5, math.atan2(4, 3))
+        azimuthal=vector.backends.object.AzimuthalObjectRhoPhi(5, math.atan2(4, 3))
     )
     assert vec.rho == pytest.approx(5)
diff --git a/tests/compute/planar/test_rho2.py b/tests/compute/planar/test_rho2.py
index 731b419..bb89461 100644
--- a/tests/compute/planar/test_rho2.py
+++ b/tests/compute/planar/test_rho2.py
@@ -14,13 +14,13 @@ import vector.backends.object
 
 def test_xy():
     vec = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(3, 4)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(3, 4)
     )
     assert vec.rho2 == pytest.approx(25)
 
 
 def test_rhophi():
     vec = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectRhoPhi(5, math.atan2(4, 3))
+        azimuthal=vector.backends.object.AzimuthalObjectRhoPhi(5, math.atan2(4, 3))
     )
     assert vec.rho2 == pytest.approx(25)
diff --git a/tests/compute/planar/test_rotateZ.py b/tests/compute/planar/test_rotateZ.py
index c13f1e7..3d8511d 100644
--- a/tests/compute/planar/test_rotateZ.py
+++ b/tests/compute/planar/test_rotateZ.py
@@ -14,7 +14,7 @@ import vector.backends.object
 
 def test_xy():
     vec = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(1, 0)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(1, 0)
     )
     assert vec.rotateZ(0.1).x == pytest.approx(0.9950041652780258)
     assert vec.rotateZ(0.1).y == pytest.approx(0.09983341664682815)
@@ -31,7 +31,7 @@ def test_xy():
 
 def test_rhophi():
     vec = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectRhoPhi(1, 0)
+        azimuthal=vector.backends.object.AzimuthalObjectRhoPhi(1, 0)
     )
     assert vec.rotateZ(0.1).rho == pytest.approx(1)
     assert vec.rotateZ(0.1).phi == pytest.approx(0.1)
diff --git a/tests/compute/planar/test_x.py b/tests/compute/planar/test_x.py
index 7879d5e..4832f49 100644
--- a/tests/compute/planar/test_x.py
+++ b/tests/compute/planar/test_x.py
@@ -14,13 +14,13 @@ import vector.backends.object
 
 def test_xy():
     vec = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(3, 4)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(3, 4)
     )
     assert vec.x == pytest.approx(3)
 
 
 def test_rhophi():
     vec = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectRhoPhi(5, math.atan2(4, 3))
+        azimuthal=vector.backends.object.AzimuthalObjectRhoPhi(5, math.atan2(4, 3))
     )
     assert vec.x == pytest.approx(3)
diff --git a/tests/compute/planar/test_y.py b/tests/compute/planar/test_y.py
index 5d570a6..92f543c 100644
--- a/tests/compute/planar/test_y.py
+++ b/tests/compute/planar/test_y.py
@@ -14,13 +14,13 @@ import vector.backends.object
 
 def test_xy():
     vec = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(3, 4)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(3, 4)
     )
     assert vec.y == pytest.approx(4)
 
 
 def test_rhophi():
     vec = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectRhoPhi(5, math.atan2(4, 3))
+        azimuthal=vector.backends.object.AzimuthalObjectRhoPhi(5, math.atan2(4, 3))
     )
     assert vec.y == pytest.approx(4)
diff --git a/tests/compute/test_add.py b/tests/compute/test_add.py
index 3bcc8c3..140cf51 100644
--- a/tests/compute/test_add.py
+++ b/tests/compute/test_add.py
@@ -14,10 +14,10 @@ import vector.backends.object
 
 def test_planar_object():
     v1 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(3, 4)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(3, 4)
     )
     v2 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(5, 12)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(5, 12)
     )
     out = v1.add(v2)
     assert out.x == pytest.approx(8)
diff --git a/tests/compute/test_dot.py b/tests/compute/test_dot.py
index 5e08c67..9fca87b 100644
--- a/tests/compute/test_dot.py
+++ b/tests/compute/test_dot.py
@@ -14,10 +14,10 @@ import vector.backends.object
 
 def test_planar_object():
     v1 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(0.1, 0.2)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(0.1, 0.2)
     )
     v2 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(0.3, 0.4)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(0.3, 0.4)
     )
     assert v1.dot(v2) == pytest.approx(0.11)
 
diff --git a/tests/compute/test_equal.py b/tests/compute/test_equal.py
index 9c0801d..2d491e5 100644
--- a/tests/compute/test_equal.py
+++ b/tests/compute/test_equal.py
@@ -13,10 +13,10 @@ import vector.backends.object
 
 def test_planar_object():
     v1 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(0.1, 0.2)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(0.1, 0.2)
     )
     v2 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(0.1, 0.2)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(0.1, 0.2)
     )
     assert v1.equal(v2)
 
diff --git a/tests/compute/test_isclose.py b/tests/compute/test_isclose.py
index 3678cef..954aced 100644
--- a/tests/compute/test_isclose.py
+++ b/tests/compute/test_isclose.py
@@ -13,10 +13,10 @@ import vector.backends.object
 
 def test_planar_object():
     v1 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(0.1, 0.2)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(0.1, 0.2)
     )
     v2 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(0.1, 0.2)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(0.1, 0.2)
     )
     assert v1.isclose(v2)
 
diff --git a/tests/compute/test_not_equal.py b/tests/compute/test_not_equal.py
index 06f18d8..b4b82ad 100644
--- a/tests/compute/test_not_equal.py
+++ b/tests/compute/test_not_equal.py
@@ -13,10 +13,10 @@ import vector.backends.object
 
 def test_planar_object():
     v1 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(0.1, 0.2)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(0.1, 0.2)
     )
     v2 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(0.1, 0.2)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(0.1, 0.2)
     )
     assert not v1.not_equal(v2)
 
diff --git a/tests/compute/test_scale.py b/tests/compute/test_scale.py
index 311c8a9..4b379b9 100644
--- a/tests/compute/test_scale.py
+++ b/tests/compute/test_scale.py
@@ -13,7 +13,7 @@ import vector.backends.object
 
 def test_planar_posfactor():
     vec = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(1, 2),
+        azimuthal=vector.backends.object.AzimuthalObjectXY(1, 2),
     )
     out = vec.scale(1.75)
     assert type(out.azimuthal) == type(vec.azimuthal)  # noqa: E721
@@ -30,7 +30,7 @@ def test_planar_posfactor():
 
 def test_planar_negfactor():
     vec = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(1, 2),
+        azimuthal=vector.backends.object.AzimuthalObjectXY(1, 2),
     )
     out = vec.scale(-1.75)
     assert type(out.azimuthal) == type(vec.azimuthal)  # noqa: E721
diff --git a/tests/compute/test_subtract.py b/tests/compute/test_subtract.py
index 96a3312..d29abcd 100644
--- a/tests/compute/test_subtract.py
+++ b/tests/compute/test_subtract.py
@@ -14,10 +14,10 @@ import vector.backends.object
 
 def test_planar_object():
     v1 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(3, 4)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(3, 4)
     )
     v2 = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(5, 12)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(5, 12)
     )
     out = v1.subtract(v2)
     assert out.x == pytest.approx(-2)
diff --git a/tests/compute/test_unit.py b/tests/compute/test_unit.py
index a98fc08..29992b3 100644
--- a/tests/compute/test_unit.py
+++ b/tests/compute/test_unit.py
@@ -14,7 +14,7 @@ import vector.backends.object
 
 def test_planar_object():
     v = vector.backends.object.VectorObject2D(
-        vector.backends.object.AzimuthalObjectXY(0.1, 0.2)
+        azimuthal=vector.backends.object.AzimuthalObjectXY(0.1, 0.2)
     )
     u = v.unit()
     assert type(u) is type(v)