Mutable 2d Vector classs
Clash Royale CLAN TAG#URR8PPP
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;
up vote
8
down vote
favorite
I create a 2dVector class to use a position coordinates in a pygame application and maybe in the future as part of a physics engine. Does this implementation has any huge downsides? Is the type hinting ok?
import math
import random
from numbers import Real
from typing import Union, Sequence, cast
VectorType = Sequence[Real]
class Vector(VectorType):
x: Real
y: Real
rotation: Real
magnitude: Real
def __init__(self, x: Union[VectorType, Real], y: Real = None) -> None:
if y is None:
x, y = cast(VectorType, x)
self.x, self.y = cast(Real, x), y
def __repr__(self):
return f"self.__class__.__name__self.x, self.y"
def __len__(self) -> int:
return 2
def __getitem__(self, i: int) -> Real:
return (self.x, self.y)[i]
def __add__(self, other: VectorType):
return Vector(self.x + other[0], self.y + other[1])
def __radd__(self, other):
return Vector(self.x + other[0], self.y + other[1])
def __iadd__(self, other: VectorType):
self.x += other[0]
self.y += other[1]
return self
def __sub__(self, other: VectorType):
return Vector(self.x - other[0], self.y - other[1])
def __rsub__(self, other: VectorType):
return Vector(other[0] - self.x, other[1] - self.y)
def __isub__(self, other: VectorType):
self.x -= other[0]
self.y -= other[1]
return self
def __mul__(self, other: Real) -> Vector:
return Vector(self.x * other, self.y * other)
def __rmul__(self, other: Real):
return Vector(self.x * other, self.y * other)
def __matmul__(self, other: VectorType):
return Vector(self.x * other[0], self.y * other[1])
def __rmatmul__(self, other):
return Vector(other[0] * self.x, other[1] * self.y)
def __truediv__(self, other: Union[VectorType, Real]) -> Vector:
if isinstance(other, Sequence):
return Vector(self.x / other[0], self.y / other[1])
return Vector(self.x / other, self.y / other)
def __rtruediv__(self, other: VectorType) -> Vector:
self.x /= other
self.y /= other
return self
def __round__(self, n=None) -> Vector:
return Vector(round(self.x, n), round(self.x, n))
def change_rotation(self, rotation: float) -> Vector:
v = Vector(self.x, self.y)
v.rotation += rotation
return v
@classmethod
def random(cls, x_range=(0, 1), y_range=(0, 1)):
return cls(random.uniform(*x_range), random.uniform(*y_range))
@classmethod
def from_polar(cls, rotation: Real, magnitude: Real):
return Vector(math.cos(rotation) * magnitude, math.sin(rotation) * magnitude)
def normalize(self):
f = self.magnitude
self.x *= f
self.y *= f
return self
@property
def magnitude(self) -> Real:
return (self.x * self.x + self.y * self.y) ** 0.5
@magnitude.setter
def magnitude(self, value: Real):
f = value / self.magnitude
self.x *= f
self.y *= f
@property
def rotation(self):
return math.atan2(self.x, self.y)
@rotation.setter
def rotation(self, value):
m = self.magnitude
self.x = math.sin(value)
self.y = math.cos(value)
self.magnitude = m
@property
def rounded(self):
return round(self.x), round(self.y)
Here is the code on github (with a few additions)
python python-3.x coordinate-system
 |Â
show 2 more comments
up vote
8
down vote
favorite
I create a 2dVector class to use a position coordinates in a pygame application and maybe in the future as part of a physics engine. Does this implementation has any huge downsides? Is the type hinting ok?
import math
import random
from numbers import Real
from typing import Union, Sequence, cast
VectorType = Sequence[Real]
class Vector(VectorType):
x: Real
y: Real
rotation: Real
magnitude: Real
def __init__(self, x: Union[VectorType, Real], y: Real = None) -> None:
if y is None:
x, y = cast(VectorType, x)
self.x, self.y = cast(Real, x), y
def __repr__(self):
return f"self.__class__.__name__self.x, self.y"
def __len__(self) -> int:
return 2
def __getitem__(self, i: int) -> Real:
return (self.x, self.y)[i]
def __add__(self, other: VectorType):
return Vector(self.x + other[0], self.y + other[1])
def __radd__(self, other):
return Vector(self.x + other[0], self.y + other[1])
def __iadd__(self, other: VectorType):
self.x += other[0]
self.y += other[1]
return self
def __sub__(self, other: VectorType):
return Vector(self.x - other[0], self.y - other[1])
def __rsub__(self, other: VectorType):
return Vector(other[0] - self.x, other[1] - self.y)
def __isub__(self, other: VectorType):
self.x -= other[0]
self.y -= other[1]
return self
def __mul__(self, other: Real) -> Vector:
return Vector(self.x * other, self.y * other)
def __rmul__(self, other: Real):
return Vector(self.x * other, self.y * other)
def __matmul__(self, other: VectorType):
return Vector(self.x * other[0], self.y * other[1])
def __rmatmul__(self, other):
return Vector(other[0] * self.x, other[1] * self.y)
def __truediv__(self, other: Union[VectorType, Real]) -> Vector:
if isinstance(other, Sequence):
return Vector(self.x / other[0], self.y / other[1])
return Vector(self.x / other, self.y / other)
def __rtruediv__(self, other: VectorType) -> Vector:
self.x /= other
self.y /= other
return self
def __round__(self, n=None) -> Vector:
return Vector(round(self.x, n), round(self.x, n))
def change_rotation(self, rotation: float) -> Vector:
v = Vector(self.x, self.y)
v.rotation += rotation
return v
@classmethod
def random(cls, x_range=(0, 1), y_range=(0, 1)):
return cls(random.uniform(*x_range), random.uniform(*y_range))
@classmethod
def from_polar(cls, rotation: Real, magnitude: Real):
return Vector(math.cos(rotation) * magnitude, math.sin(rotation) * magnitude)
def normalize(self):
f = self.magnitude
self.x *= f
self.y *= f
return self
@property
def magnitude(self) -> Real:
return (self.x * self.x + self.y * self.y) ** 0.5
@magnitude.setter
def magnitude(self, value: Real):
f = value / self.magnitude
self.x *= f
self.y *= f
@property
def rotation(self):
return math.atan2(self.x, self.y)
@rotation.setter
def rotation(self, value):
m = self.magnitude
self.x = math.sin(value)
self.y = math.cos(value)
self.magnitude = m
@property
def rounded(self):
return round(self.x), round(self.y)
Here is the code on github (with a few additions)
python python-3.x coordinate-system
Vector(round(self.x, n), round(self.x, n))
typo forVector(round(self.x, n), round(self.y, n))
?
â Jean-François Fabre
Apr 30 at 20:07
@Jean-FrançoisFabre Yes, glad you pointed that out. Would have been a pretty hard to catch bug.
â MegaIng
Apr 30 at 20:08
also inrotation
you're assigning m to the magnitude, then assign it the other way round: it achieves nothing
â Jean-François Fabre
Apr 30 at 20:08
1
pretty nice code, that said
â Jean-François Fabre
Apr 30 at 20:10
@Jean-FrançoisFabre yes it does. It will setself.x
andself.y
to the correct values. (Multiply them with magnitude) , becauseself.magnitude = m
invokes the property setter.
â MegaIng
Apr 30 at 20:11
 |Â
show 2 more comments
up vote
8
down vote
favorite
up vote
8
down vote
favorite
I create a 2dVector class to use a position coordinates in a pygame application and maybe in the future as part of a physics engine. Does this implementation has any huge downsides? Is the type hinting ok?
import math
import random
from numbers import Real
from typing import Union, Sequence, cast
VectorType = Sequence[Real]
class Vector(VectorType):
x: Real
y: Real
rotation: Real
magnitude: Real
def __init__(self, x: Union[VectorType, Real], y: Real = None) -> None:
if y is None:
x, y = cast(VectorType, x)
self.x, self.y = cast(Real, x), y
def __repr__(self):
return f"self.__class__.__name__self.x, self.y"
def __len__(self) -> int:
return 2
def __getitem__(self, i: int) -> Real:
return (self.x, self.y)[i]
def __add__(self, other: VectorType):
return Vector(self.x + other[0], self.y + other[1])
def __radd__(self, other):
return Vector(self.x + other[0], self.y + other[1])
def __iadd__(self, other: VectorType):
self.x += other[0]
self.y += other[1]
return self
def __sub__(self, other: VectorType):
return Vector(self.x - other[0], self.y - other[1])
def __rsub__(self, other: VectorType):
return Vector(other[0] - self.x, other[1] - self.y)
def __isub__(self, other: VectorType):
self.x -= other[0]
self.y -= other[1]
return self
def __mul__(self, other: Real) -> Vector:
return Vector(self.x * other, self.y * other)
def __rmul__(self, other: Real):
return Vector(self.x * other, self.y * other)
def __matmul__(self, other: VectorType):
return Vector(self.x * other[0], self.y * other[1])
def __rmatmul__(self, other):
return Vector(other[0] * self.x, other[1] * self.y)
def __truediv__(self, other: Union[VectorType, Real]) -> Vector:
if isinstance(other, Sequence):
return Vector(self.x / other[0], self.y / other[1])
return Vector(self.x / other, self.y / other)
def __rtruediv__(self, other: VectorType) -> Vector:
self.x /= other
self.y /= other
return self
def __round__(self, n=None) -> Vector:
return Vector(round(self.x, n), round(self.x, n))
def change_rotation(self, rotation: float) -> Vector:
v = Vector(self.x, self.y)
v.rotation += rotation
return v
@classmethod
def random(cls, x_range=(0, 1), y_range=(0, 1)):
return cls(random.uniform(*x_range), random.uniform(*y_range))
@classmethod
def from_polar(cls, rotation: Real, magnitude: Real):
return Vector(math.cos(rotation) * magnitude, math.sin(rotation) * magnitude)
def normalize(self):
f = self.magnitude
self.x *= f
self.y *= f
return self
@property
def magnitude(self) -> Real:
return (self.x * self.x + self.y * self.y) ** 0.5
@magnitude.setter
def magnitude(self, value: Real):
f = value / self.magnitude
self.x *= f
self.y *= f
@property
def rotation(self):
return math.atan2(self.x, self.y)
@rotation.setter
def rotation(self, value):
m = self.magnitude
self.x = math.sin(value)
self.y = math.cos(value)
self.magnitude = m
@property
def rounded(self):
return round(self.x), round(self.y)
Here is the code on github (with a few additions)
python python-3.x coordinate-system
I create a 2dVector class to use a position coordinates in a pygame application and maybe in the future as part of a physics engine. Does this implementation has any huge downsides? Is the type hinting ok?
import math
import random
from numbers import Real
from typing import Union, Sequence, cast
VectorType = Sequence[Real]
class Vector(VectorType):
x: Real
y: Real
rotation: Real
magnitude: Real
def __init__(self, x: Union[VectorType, Real], y: Real = None) -> None:
if y is None:
x, y = cast(VectorType, x)
self.x, self.y = cast(Real, x), y
def __repr__(self):
return f"self.__class__.__name__self.x, self.y"
def __len__(self) -> int:
return 2
def __getitem__(self, i: int) -> Real:
return (self.x, self.y)[i]
def __add__(self, other: VectorType):
return Vector(self.x + other[0], self.y + other[1])
def __radd__(self, other):
return Vector(self.x + other[0], self.y + other[1])
def __iadd__(self, other: VectorType):
self.x += other[0]
self.y += other[1]
return self
def __sub__(self, other: VectorType):
return Vector(self.x - other[0], self.y - other[1])
def __rsub__(self, other: VectorType):
return Vector(other[0] - self.x, other[1] - self.y)
def __isub__(self, other: VectorType):
self.x -= other[0]
self.y -= other[1]
return self
def __mul__(self, other: Real) -> Vector:
return Vector(self.x * other, self.y * other)
def __rmul__(self, other: Real):
return Vector(self.x * other, self.y * other)
def __matmul__(self, other: VectorType):
return Vector(self.x * other[0], self.y * other[1])
def __rmatmul__(self, other):
return Vector(other[0] * self.x, other[1] * self.y)
def __truediv__(self, other: Union[VectorType, Real]) -> Vector:
if isinstance(other, Sequence):
return Vector(self.x / other[0], self.y / other[1])
return Vector(self.x / other, self.y / other)
def __rtruediv__(self, other: VectorType) -> Vector:
self.x /= other
self.y /= other
return self
def __round__(self, n=None) -> Vector:
return Vector(round(self.x, n), round(self.x, n))
def change_rotation(self, rotation: float) -> Vector:
v = Vector(self.x, self.y)
v.rotation += rotation
return v
@classmethod
def random(cls, x_range=(0, 1), y_range=(0, 1)):
return cls(random.uniform(*x_range), random.uniform(*y_range))
@classmethod
def from_polar(cls, rotation: Real, magnitude: Real):
return Vector(math.cos(rotation) * magnitude, math.sin(rotation) * magnitude)
def normalize(self):
f = self.magnitude
self.x *= f
self.y *= f
return self
@property
def magnitude(self) -> Real:
return (self.x * self.x + self.y * self.y) ** 0.5
@magnitude.setter
def magnitude(self, value: Real):
f = value / self.magnitude
self.x *= f
self.y *= f
@property
def rotation(self):
return math.atan2(self.x, self.y)
@rotation.setter
def rotation(self, value):
m = self.magnitude
self.x = math.sin(value)
self.y = math.cos(value)
self.magnitude = m
@property
def rounded(self):
return round(self.x), round(self.y)
Here is the code on github (with a few additions)
python python-3.x coordinate-system
asked Apr 30 at 19:55
MegaIng
2189
2189
Vector(round(self.x, n), round(self.x, n))
typo forVector(round(self.x, n), round(self.y, n))
?
â Jean-François Fabre
Apr 30 at 20:07
@Jean-FrançoisFabre Yes, glad you pointed that out. Would have been a pretty hard to catch bug.
â MegaIng
Apr 30 at 20:08
also inrotation
you're assigning m to the magnitude, then assign it the other way round: it achieves nothing
â Jean-François Fabre
Apr 30 at 20:08
1
pretty nice code, that said
â Jean-François Fabre
Apr 30 at 20:10
@Jean-FrançoisFabre yes it does. It will setself.x
andself.y
to the correct values. (Multiply them with magnitude) , becauseself.magnitude = m
invokes the property setter.
â MegaIng
Apr 30 at 20:11
 |Â
show 2 more comments
Vector(round(self.x, n), round(self.x, n))
typo forVector(round(self.x, n), round(self.y, n))
?
â Jean-François Fabre
Apr 30 at 20:07
@Jean-FrançoisFabre Yes, glad you pointed that out. Would have been a pretty hard to catch bug.
â MegaIng
Apr 30 at 20:08
also inrotation
you're assigning m to the magnitude, then assign it the other way round: it achieves nothing
â Jean-François Fabre
Apr 30 at 20:08
1
pretty nice code, that said
â Jean-François Fabre
Apr 30 at 20:10
@Jean-FrançoisFabre yes it does. It will setself.x
andself.y
to the correct values. (Multiply them with magnitude) , becauseself.magnitude = m
invokes the property setter.
â MegaIng
Apr 30 at 20:11
Vector(round(self.x, n), round(self.x, n))
typo for Vector(round(self.x, n), round(self.y, n))
?â Jean-François Fabre
Apr 30 at 20:07
Vector(round(self.x, n), round(self.x, n))
typo for Vector(round(self.x, n), round(self.y, n))
?â Jean-François Fabre
Apr 30 at 20:07
@Jean-FrançoisFabre Yes, glad you pointed that out. Would have been a pretty hard to catch bug.
â MegaIng
Apr 30 at 20:08
@Jean-FrançoisFabre Yes, glad you pointed that out. Would have been a pretty hard to catch bug.
â MegaIng
Apr 30 at 20:08
also in
rotation
you're assigning m to the magnitude, then assign it the other way round: it achieves nothingâ Jean-François Fabre
Apr 30 at 20:08
also in
rotation
you're assigning m to the magnitude, then assign it the other way round: it achieves nothingâ Jean-François Fabre
Apr 30 at 20:08
1
1
pretty nice code, that said
â Jean-François Fabre
Apr 30 at 20:10
pretty nice code, that said
â Jean-François Fabre
Apr 30 at 20:10
@Jean-FrançoisFabre yes it does. It will set
self.x
and self.y
to the correct values. (Multiply them with magnitude) , because self.magnitude = m
invokes the property setter.â MegaIng
Apr 30 at 20:11
@Jean-FrançoisFabre yes it does. It will set
self.x
and self.y
to the correct values. (Multiply them with magnitude) , because self.magnitude = m
invokes the property setter.â MegaIng
Apr 30 at 20:11
 |Â
show 2 more comments
1 Answer
1
active
oldest
votes
up vote
2
down vote
accepted
There are no docstrings.
I would suggest reconsidering the mutability of the vectors. The advantages of mutability are:
a. You get to reuse the original storage for the original vector when you make a change, avoiding a couple of memory management operations.
b. You save a couple of characters when writing some update operations, for example you can write
v.normalize()
instead of, say,v = v.normalized()
.But the disadvantage of mutability is that it is harder to reason about operations. If you see a function that takes some vectors, let's say
def collide(p: VectorType, q: VectorType, r: Real) -> Bool:
"Return True if p and q are separated by no more than r."is it safe to pass
p=sprite.position
, or do you need to make a copy? With mutability, then for all you know, the function is implemented like this:p -= q
return p.magnitude <= rSo in a world where vectors are mutable, you have to worry about this possibility, and perhaps document for each function which arguments get mutated and which don't. If vectors are immutable, then all this is unnecessary and reasoning about the behaviour of code is easier.
Immutability would avoid the need to implement the
__iadd__
and__isub__
methods. Also, you could inherit fromtuple
and save some memory. See this vector class on github for some implementation ideas.The
__truediv__
method has different behaviour based on the type of the argument. This is inconsistent with the behaviour of__mul__
and__rmul__
and__rtruediv__
, none of which have this behaviour. Also, dispatching based on the type makes it harder to read and reason about code. If you know thatv
is a vector and then you read the expressionv / a
, you can't immediately deduce thata
must be a scalar as you could in the case ofv * a
.I guess that you added the variant operation because of some use case. But nonetheless I would recommend giving the variant operation a different method name, for example
scaled
.In
__radd__
the operations should be reversed, with the other values on the left (just in case they have unusual implementations of__add__
method):def __radd__(self, other):
return Vector(other[0] + self.x, other[1] + self.y)Similarly for
__rmul__
.There's no checking that the other vector has the right length. There's nothing stopping me from writing:
Vector(1, 2) + [3, 4, 5]
which ought to be an error.
In
__repr__
I suggest writingtype(self)
rather thanself.__class__
, just as you would writelen(s)
rather thans.__len__()
.In
from_polar
you should callcls
rather thanVector
, to support subclasses.normalize
has the operations the wrong way round: you need to divide by the magnitude, not multiply. But it would be simpler to writeself.magnitude = 1
.In
magnitude
it would be clearer to call the argumentlength
ormagnitude
rather thanvalue
.In
magnitude
, consider writingmath.hypot(self.x, self.y)
.In
rotation
it would be clearer to call the argumenttheta
orangle
rather thanvalue
.
Do you think I need to add docstrings to all methods? Even__add__
or__getitem__
?
â MegaIng
May 1 at 14:24
I wouldn't write docstrings for methods that are already adequately documented in the Python language reference. Unless there's something special or surprising about your implementation.
â Gareth Rees
May 1 at 14:29
Added suggestion 1 and 4-11 to github. To 2: I want to keep the mutability. It is quite useful. To 3: I want to keep the simple division by a scalar or a vector. I no it is a mess, I already thoughtabout overwriting__divmod__
and returningresult, None
, but that is better in my opinion. Any ideas?
â MegaIng
May 1 at 14:47
I mean overwriting__divmod__
isn't better
â MegaIng
May 1 at 14:58
The specification for__divmod__
says: "The__divmod__
method should be the equivalent to using__floordiv__
and__mod__
; it should not be related to__truediv__
." So overriding this method with something related to__truediv__
has the potential to lead to confusion and error. I suggest using another method name, for examplescaled
orscaled_down
.
â Gareth Rees
May 3 at 13:15
add a comment |Â
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
2
down vote
accepted
There are no docstrings.
I would suggest reconsidering the mutability of the vectors. The advantages of mutability are:
a. You get to reuse the original storage for the original vector when you make a change, avoiding a couple of memory management operations.
b. You save a couple of characters when writing some update operations, for example you can write
v.normalize()
instead of, say,v = v.normalized()
.But the disadvantage of mutability is that it is harder to reason about operations. If you see a function that takes some vectors, let's say
def collide(p: VectorType, q: VectorType, r: Real) -> Bool:
"Return True if p and q are separated by no more than r."is it safe to pass
p=sprite.position
, or do you need to make a copy? With mutability, then for all you know, the function is implemented like this:p -= q
return p.magnitude <= rSo in a world where vectors are mutable, you have to worry about this possibility, and perhaps document for each function which arguments get mutated and which don't. If vectors are immutable, then all this is unnecessary and reasoning about the behaviour of code is easier.
Immutability would avoid the need to implement the
__iadd__
and__isub__
methods. Also, you could inherit fromtuple
and save some memory. See this vector class on github for some implementation ideas.The
__truediv__
method has different behaviour based on the type of the argument. This is inconsistent with the behaviour of__mul__
and__rmul__
and__rtruediv__
, none of which have this behaviour. Also, dispatching based on the type makes it harder to read and reason about code. If you know thatv
is a vector and then you read the expressionv / a
, you can't immediately deduce thata
must be a scalar as you could in the case ofv * a
.I guess that you added the variant operation because of some use case. But nonetheless I would recommend giving the variant operation a different method name, for example
scaled
.In
__radd__
the operations should be reversed, with the other values on the left (just in case they have unusual implementations of__add__
method):def __radd__(self, other):
return Vector(other[0] + self.x, other[1] + self.y)Similarly for
__rmul__
.There's no checking that the other vector has the right length. There's nothing stopping me from writing:
Vector(1, 2) + [3, 4, 5]
which ought to be an error.
In
__repr__
I suggest writingtype(self)
rather thanself.__class__
, just as you would writelen(s)
rather thans.__len__()
.In
from_polar
you should callcls
rather thanVector
, to support subclasses.normalize
has the operations the wrong way round: you need to divide by the magnitude, not multiply. But it would be simpler to writeself.magnitude = 1
.In
magnitude
it would be clearer to call the argumentlength
ormagnitude
rather thanvalue
.In
magnitude
, consider writingmath.hypot(self.x, self.y)
.In
rotation
it would be clearer to call the argumenttheta
orangle
rather thanvalue
.
Do you think I need to add docstrings to all methods? Even__add__
or__getitem__
?
â MegaIng
May 1 at 14:24
I wouldn't write docstrings for methods that are already adequately documented in the Python language reference. Unless there's something special or surprising about your implementation.
â Gareth Rees
May 1 at 14:29
Added suggestion 1 and 4-11 to github. To 2: I want to keep the mutability. It is quite useful. To 3: I want to keep the simple division by a scalar or a vector. I no it is a mess, I already thoughtabout overwriting__divmod__
and returningresult, None
, but that is better in my opinion. Any ideas?
â MegaIng
May 1 at 14:47
I mean overwriting__divmod__
isn't better
â MegaIng
May 1 at 14:58
The specification for__divmod__
says: "The__divmod__
method should be the equivalent to using__floordiv__
and__mod__
; it should not be related to__truediv__
." So overriding this method with something related to__truediv__
has the potential to lead to confusion and error. I suggest using another method name, for examplescaled
orscaled_down
.
â Gareth Rees
May 3 at 13:15
add a comment |Â
up vote
2
down vote
accepted
There are no docstrings.
I would suggest reconsidering the mutability of the vectors. The advantages of mutability are:
a. You get to reuse the original storage for the original vector when you make a change, avoiding a couple of memory management operations.
b. You save a couple of characters when writing some update operations, for example you can write
v.normalize()
instead of, say,v = v.normalized()
.But the disadvantage of mutability is that it is harder to reason about operations. If you see a function that takes some vectors, let's say
def collide(p: VectorType, q: VectorType, r: Real) -> Bool:
"Return True if p and q are separated by no more than r."is it safe to pass
p=sprite.position
, or do you need to make a copy? With mutability, then for all you know, the function is implemented like this:p -= q
return p.magnitude <= rSo in a world where vectors are mutable, you have to worry about this possibility, and perhaps document for each function which arguments get mutated and which don't. If vectors are immutable, then all this is unnecessary and reasoning about the behaviour of code is easier.
Immutability would avoid the need to implement the
__iadd__
and__isub__
methods. Also, you could inherit fromtuple
and save some memory. See this vector class on github for some implementation ideas.The
__truediv__
method has different behaviour based on the type of the argument. This is inconsistent with the behaviour of__mul__
and__rmul__
and__rtruediv__
, none of which have this behaviour. Also, dispatching based on the type makes it harder to read and reason about code. If you know thatv
is a vector and then you read the expressionv / a
, you can't immediately deduce thata
must be a scalar as you could in the case ofv * a
.I guess that you added the variant operation because of some use case. But nonetheless I would recommend giving the variant operation a different method name, for example
scaled
.In
__radd__
the operations should be reversed, with the other values on the left (just in case they have unusual implementations of__add__
method):def __radd__(self, other):
return Vector(other[0] + self.x, other[1] + self.y)Similarly for
__rmul__
.There's no checking that the other vector has the right length. There's nothing stopping me from writing:
Vector(1, 2) + [3, 4, 5]
which ought to be an error.
In
__repr__
I suggest writingtype(self)
rather thanself.__class__
, just as you would writelen(s)
rather thans.__len__()
.In
from_polar
you should callcls
rather thanVector
, to support subclasses.normalize
has the operations the wrong way round: you need to divide by the magnitude, not multiply. But it would be simpler to writeself.magnitude = 1
.In
magnitude
it would be clearer to call the argumentlength
ormagnitude
rather thanvalue
.In
magnitude
, consider writingmath.hypot(self.x, self.y)
.In
rotation
it would be clearer to call the argumenttheta
orangle
rather thanvalue
.
Do you think I need to add docstrings to all methods? Even__add__
or__getitem__
?
â MegaIng
May 1 at 14:24
I wouldn't write docstrings for methods that are already adequately documented in the Python language reference. Unless there's something special or surprising about your implementation.
â Gareth Rees
May 1 at 14:29
Added suggestion 1 and 4-11 to github. To 2: I want to keep the mutability. It is quite useful. To 3: I want to keep the simple division by a scalar or a vector. I no it is a mess, I already thoughtabout overwriting__divmod__
and returningresult, None
, but that is better in my opinion. Any ideas?
â MegaIng
May 1 at 14:47
I mean overwriting__divmod__
isn't better
â MegaIng
May 1 at 14:58
The specification for__divmod__
says: "The__divmod__
method should be the equivalent to using__floordiv__
and__mod__
; it should not be related to__truediv__
." So overriding this method with something related to__truediv__
has the potential to lead to confusion and error. I suggest using another method name, for examplescaled
orscaled_down
.
â Gareth Rees
May 3 at 13:15
add a comment |Â
up vote
2
down vote
accepted
up vote
2
down vote
accepted
There are no docstrings.
I would suggest reconsidering the mutability of the vectors. The advantages of mutability are:
a. You get to reuse the original storage for the original vector when you make a change, avoiding a couple of memory management operations.
b. You save a couple of characters when writing some update operations, for example you can write
v.normalize()
instead of, say,v = v.normalized()
.But the disadvantage of mutability is that it is harder to reason about operations. If you see a function that takes some vectors, let's say
def collide(p: VectorType, q: VectorType, r: Real) -> Bool:
"Return True if p and q are separated by no more than r."is it safe to pass
p=sprite.position
, or do you need to make a copy? With mutability, then for all you know, the function is implemented like this:p -= q
return p.magnitude <= rSo in a world where vectors are mutable, you have to worry about this possibility, and perhaps document for each function which arguments get mutated and which don't. If vectors are immutable, then all this is unnecessary and reasoning about the behaviour of code is easier.
Immutability would avoid the need to implement the
__iadd__
and__isub__
methods. Also, you could inherit fromtuple
and save some memory. See this vector class on github for some implementation ideas.The
__truediv__
method has different behaviour based on the type of the argument. This is inconsistent with the behaviour of__mul__
and__rmul__
and__rtruediv__
, none of which have this behaviour. Also, dispatching based on the type makes it harder to read and reason about code. If you know thatv
is a vector and then you read the expressionv / a
, you can't immediately deduce thata
must be a scalar as you could in the case ofv * a
.I guess that you added the variant operation because of some use case. But nonetheless I would recommend giving the variant operation a different method name, for example
scaled
.In
__radd__
the operations should be reversed, with the other values on the left (just in case they have unusual implementations of__add__
method):def __radd__(self, other):
return Vector(other[0] + self.x, other[1] + self.y)Similarly for
__rmul__
.There's no checking that the other vector has the right length. There's nothing stopping me from writing:
Vector(1, 2) + [3, 4, 5]
which ought to be an error.
In
__repr__
I suggest writingtype(self)
rather thanself.__class__
, just as you would writelen(s)
rather thans.__len__()
.In
from_polar
you should callcls
rather thanVector
, to support subclasses.normalize
has the operations the wrong way round: you need to divide by the magnitude, not multiply. But it would be simpler to writeself.magnitude = 1
.In
magnitude
it would be clearer to call the argumentlength
ormagnitude
rather thanvalue
.In
magnitude
, consider writingmath.hypot(self.x, self.y)
.In
rotation
it would be clearer to call the argumenttheta
orangle
rather thanvalue
.
There are no docstrings.
I would suggest reconsidering the mutability of the vectors. The advantages of mutability are:
a. You get to reuse the original storage for the original vector when you make a change, avoiding a couple of memory management operations.
b. You save a couple of characters when writing some update operations, for example you can write
v.normalize()
instead of, say,v = v.normalized()
.But the disadvantage of mutability is that it is harder to reason about operations. If you see a function that takes some vectors, let's say
def collide(p: VectorType, q: VectorType, r: Real) -> Bool:
"Return True if p and q are separated by no more than r."is it safe to pass
p=sprite.position
, or do you need to make a copy? With mutability, then for all you know, the function is implemented like this:p -= q
return p.magnitude <= rSo in a world where vectors are mutable, you have to worry about this possibility, and perhaps document for each function which arguments get mutated and which don't. If vectors are immutable, then all this is unnecessary and reasoning about the behaviour of code is easier.
Immutability would avoid the need to implement the
__iadd__
and__isub__
methods. Also, you could inherit fromtuple
and save some memory. See this vector class on github for some implementation ideas.The
__truediv__
method has different behaviour based on the type of the argument. This is inconsistent with the behaviour of__mul__
and__rmul__
and__rtruediv__
, none of which have this behaviour. Also, dispatching based on the type makes it harder to read and reason about code. If you know thatv
is a vector and then you read the expressionv / a
, you can't immediately deduce thata
must be a scalar as you could in the case ofv * a
.I guess that you added the variant operation because of some use case. But nonetheless I would recommend giving the variant operation a different method name, for example
scaled
.In
__radd__
the operations should be reversed, with the other values on the left (just in case they have unusual implementations of__add__
method):def __radd__(self, other):
return Vector(other[0] + self.x, other[1] + self.y)Similarly for
__rmul__
.There's no checking that the other vector has the right length. There's nothing stopping me from writing:
Vector(1, 2) + [3, 4, 5]
which ought to be an error.
In
__repr__
I suggest writingtype(self)
rather thanself.__class__
, just as you would writelen(s)
rather thans.__len__()
.In
from_polar
you should callcls
rather thanVector
, to support subclasses.normalize
has the operations the wrong way round: you need to divide by the magnitude, not multiply. But it would be simpler to writeself.magnitude = 1
.In
magnitude
it would be clearer to call the argumentlength
ormagnitude
rather thanvalue
.In
magnitude
, consider writingmath.hypot(self.x, self.y)
.In
rotation
it would be clearer to call the argumenttheta
orangle
rather thanvalue
.
answered May 1 at 13:52
Gareth Rees
41.1k394166
41.1k394166
Do you think I need to add docstrings to all methods? Even__add__
or__getitem__
?
â MegaIng
May 1 at 14:24
I wouldn't write docstrings for methods that are already adequately documented in the Python language reference. Unless there's something special or surprising about your implementation.
â Gareth Rees
May 1 at 14:29
Added suggestion 1 and 4-11 to github. To 2: I want to keep the mutability. It is quite useful. To 3: I want to keep the simple division by a scalar or a vector. I no it is a mess, I already thoughtabout overwriting__divmod__
and returningresult, None
, but that is better in my opinion. Any ideas?
â MegaIng
May 1 at 14:47
I mean overwriting__divmod__
isn't better
â MegaIng
May 1 at 14:58
The specification for__divmod__
says: "The__divmod__
method should be the equivalent to using__floordiv__
and__mod__
; it should not be related to__truediv__
." So overriding this method with something related to__truediv__
has the potential to lead to confusion and error. I suggest using another method name, for examplescaled
orscaled_down
.
â Gareth Rees
May 3 at 13:15
add a comment |Â
Do you think I need to add docstrings to all methods? Even__add__
or__getitem__
?
â MegaIng
May 1 at 14:24
I wouldn't write docstrings for methods that are already adequately documented in the Python language reference. Unless there's something special or surprising about your implementation.
â Gareth Rees
May 1 at 14:29
Added suggestion 1 and 4-11 to github. To 2: I want to keep the mutability. It is quite useful. To 3: I want to keep the simple division by a scalar or a vector. I no it is a mess, I already thoughtabout overwriting__divmod__
and returningresult, None
, but that is better in my opinion. Any ideas?
â MegaIng
May 1 at 14:47
I mean overwriting__divmod__
isn't better
â MegaIng
May 1 at 14:58
The specification for__divmod__
says: "The__divmod__
method should be the equivalent to using__floordiv__
and__mod__
; it should not be related to__truediv__
." So overriding this method with something related to__truediv__
has the potential to lead to confusion and error. I suggest using another method name, for examplescaled
orscaled_down
.
â Gareth Rees
May 3 at 13:15
Do you think I need to add docstrings to all methods? Even
__add__
or __getitem__
?â MegaIng
May 1 at 14:24
Do you think I need to add docstrings to all methods? Even
__add__
or __getitem__
?â MegaIng
May 1 at 14:24
I wouldn't write docstrings for methods that are already adequately documented in the Python language reference. Unless there's something special or surprising about your implementation.
â Gareth Rees
May 1 at 14:29
I wouldn't write docstrings for methods that are already adequately documented in the Python language reference. Unless there's something special or surprising about your implementation.
â Gareth Rees
May 1 at 14:29
Added suggestion 1 and 4-11 to github. To 2: I want to keep the mutability. It is quite useful. To 3: I want to keep the simple division by a scalar or a vector. I no it is a mess, I already thoughtabout overwriting
__divmod__
and returning result, None
, but that is better in my opinion. Any ideas?â MegaIng
May 1 at 14:47
Added suggestion 1 and 4-11 to github. To 2: I want to keep the mutability. It is quite useful. To 3: I want to keep the simple division by a scalar or a vector. I no it is a mess, I already thoughtabout overwriting
__divmod__
and returning result, None
, but that is better in my opinion. Any ideas?â MegaIng
May 1 at 14:47
I mean overwriting
__divmod__
isn't betterâ MegaIng
May 1 at 14:58
I mean overwriting
__divmod__
isn't betterâ MegaIng
May 1 at 14:58
The specification for
__divmod__
says: "The __divmod__
method should be the equivalent to using __floordiv__
and __mod__
; it should not be related to __truediv__
." So overriding this method with something related to __truediv__
has the potential to lead to confusion and error. I suggest using another method name, for example scaled
or scaled_down
.â Gareth Rees
May 3 at 13:15
The specification for
__divmod__
says: "The __divmod__
method should be the equivalent to using __floordiv__
and __mod__
; it should not be related to __truediv__
." So overriding this method with something related to __truediv__
has the potential to lead to confusion and error. I suggest using another method name, for example scaled
or scaled_down
.â Gareth Rees
May 3 at 13:15
add a comment |Â
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f193295%2fmutable-2d-vector-classs%23new-answer', 'question_page');
);
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Vector(round(self.x, n), round(self.x, n))
typo forVector(round(self.x, n), round(self.y, n))
?â Jean-François Fabre
Apr 30 at 20:07
@Jean-FrançoisFabre Yes, glad you pointed that out. Would have been a pretty hard to catch bug.
â MegaIng
Apr 30 at 20:08
also in
rotation
you're assigning m to the magnitude, then assign it the other way round: it achieves nothingâ Jean-François Fabre
Apr 30 at 20:08
1
pretty nice code, that said
â Jean-François Fabre
Apr 30 at 20:10
@Jean-FrançoisFabre yes it does. It will set
self.x
andself.y
to the correct values. (Multiply them with magnitude) , becauseself.magnitude = m
invokes the property setter.â MegaIng
Apr 30 at 20:11