1

I came across this class Vector2 which was constructed like this

class Vector2:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y

        if hasattr(x, "__getitem__"):
            x, y = x
            self._v = [float(x), float(y)]
        else:
            self._v = [float(x), float(y)]

    def __getitem__(self, index):
        return self._v[index]

    def __setitem__(self, index, value):
        self._v[index] = 1.0 * value
        
    def __str__(self):
        return "(%s, %s)"%(self.x, self.y)

As I don't have much knowledge in Python, I couldn't understand the first line of the block if hasattr(x, "__getitem__"):

if hasattr(x, "__getitem__"):
            x, y = x # why can we do this?

I meant how could x be split into x and y because x is already itself (that is, x == x)?

Also, what is the objective/purpose of hasattr(x, "__getitem__")?

EDIT: If I instantiated a vector v1 = Vector2(100, 200) to represent the movement of an object in pygame, I found out that I couldn't use v1[0] to specify the location of the object on the x-axis for drawing (with pygame.blit()). Why was that, given v1[0] == v1.x?

EDIT 2: correction of the edit above. I was wrong, v1[0] (same as v1.x) can be used to specify the location of the object.

Nemo
  • 1,124
  • 2
  • 16
  • 39
  • 1
    When you use `x[i]`, this is shorthand for writing `x.__getitem__(i)` from a indexable structure. The line `x, y = x` is an argument expansion, which allows you to declare multiple values by extracting values out of an iterable (e.g. `a, b = [1, 2]`). So this code is asserts that if `x` is indexable, that you can construct the `_v` attribute by pulling items out of the container. – flakes May 31 '23 at 04:16
  • Compare `hasattr(1, '__getitem__')` and `hasattr([1], '__getitem__')`. – Mark Ransom May 31 '23 at 04:17
  • @MarkRansom, thanks for your response. But what is the purpose of the comparison? – Nemo May 31 '23 at 04:22
  • 1
    @Nemo this is to show you the difference of structures which support `__getitem__` and those which do not. – flakes May 31 '23 at 04:23
  • My advice is to remember where you came across this class, and avoid using that source forever. This code is horrible - `self._v` is sort-of a list made from float(x) and float(y), unless x happened to have a `__getitem__` method, in which case it's not. And once there is a call to `__setitem__` the original relationship between x, y and _v is no longer valid. Just awful. – Paul Cornelius May 31 '23 at 05:04
  • And the line `self._v[index] = 1.0 * value` is actually rather amusing, since `1.0*value == value` for all values of `value`. – Paul Cornelius May 31 '23 at 05:07
  • 1
    @PaulCornelius I think that assessment is a bit harsh. This code is certainly not as polished as it could be, but we also don't have the history for why these changes have been made. It's very possible that the code is used by external clients and needs to maintain old behavior while enabling newer design choices. `1.0 * value` is probably more succinctly written as `float(value)`, which definitely has potential to change functionality in terms of truncating operations. IMO you should choose the best tool for the job, which may or may not have its warts to consider in your tradeoffs. – flakes May 31 '23 at 05:21
  • 1
    I suspect that the original author is trying to detect either a list or tuple which, IMO, is better achieved with *isinstance(x, (list, tuple))* If x is either of these types there is an implicit assumption that the list/tuple has exactly 2 elements. Never make assumptions! The implementation of \_\_str__ is interesting. Surely it should report the normalised data - i.e., self._v[0] and self._v[1]. The way the returned string is formatted suggests that this code is very dated – DarkKnight May 31 '23 at 07:20
  • Thanks @PaulCornelius for pointing out those 'interesting' points in the code as I didn't notice them until now. – Nemo May 31 '23 at 07:40
  • @DarkKnight, you're right, the book was first published in 2015. Interesting that the version I read was 2nd edition but it seemed the code was not updated to make it more , how should I say, professional. – Nemo May 31 '23 at 07:44
  • @Nemo If you have an instance of this class, say V1, then V1.x may not be exactly the same as V1[0] as they could be of different types. V1.x could even be string! – DarkKnight May 31 '23 at 07:51
  • Geez, how can that happen, @DarkKnight? I thought `__setitem__(self, index, value)` would ensure `v1.x` or `v1.y` is *float*? – Nemo May 31 '23 at 07:58
  • 1
    @Nemo \_\_setitem__ only affects self._v It does not affect self.x – DarkKnight May 31 '23 at 07:59
  • 1
    The purpose of the comparison was to show you which kind of Python objects have `__getitem__` and which don't. – Mark Ransom May 31 '23 at 12:13

1 Answers1

1

I believe the code's author intended to support passing tuples as initial parameters for creating a new object from the class, Vector2.

The condition if hasattr(x, "__getitem__"): serves as a loose check to verify if the argument passed is a tuple.

For example if you pass (5, 2) when initializing an object of Vector2 class,

point = Vector2((5, 2))

the code block, x, y = x will split it and initialize the co-ordinates as x = 5 and y = 2

Read more about __getitem __

ybl
  • 1,510
  • 10
  • 16
  • Thank you for explaining the purpose of `if hasattr(x, "__getitem__"):`. Would you please expand your response with my new edit of the OP? – Nemo May 31 '23 at 04:26
  • What do you mean by "I found out that I couldn't use v1[0]", because using the Vector2 from your code, v1[0] should be a `float` of the first argument you provide and it works as expected. – ybl May 31 '23 at 04:32
  • Sorry, I think I was confused myself. I think you are right, the problem wasn't due to `v1[0]` as I just found out (please see this post https://stackoverflow.com/questions/76370428/preventing-an-object-disappears-from-the-screen-in-pygame – Nemo May 31 '23 at 05:38