9

Hopefully the title isn't too misleading, I'm not sure the best way to phrase my question.

I'm trying to create a (X, Y) coordinate data type in Python. Is there a way to create a "custom data type" so that I have an object with a value, but also some supporting attributes?

So far I've made this simple class:

class Point: def __init__(self, x, y): self.x = x self.y = y self.tuple = (x, y) 

Ideally, I'd like to be able to do something like this:

>>> p = Point(4, 5) >>> >>> my_x = p.x # can access the `x` attribute with "dot syntax" >>> >>> my_tuple = p # or can access the tuple value directly # without needing to do `.tuple`, as if the `tuple` # attribute is the "default" attribute for the object 

NOTE I'm not trying to simply display the tuple, I know I can do that with the __repr__ method

In a way, I'm trying to create a very simplified numpy.ndarray, because the ndarrays are a datatype that have their own attributes. I tried looking thru the numpy source to see how this is done, but it was way over my head, haha.

Any tips would be appreciated!

5
  • Is it working for you? If all you want to do is access x and y, ..., you're there. Its uncommon to also set the tuple attribute because then you have to worry about keeping data in sync if x, y or the tuple itself change. Do you want to perform operations on the class as a whole, or just have a convenient place to park x and y? Are these mutable or immutable? As stands, the class already does what you want. We need to know what problem you are having. Commented Feb 18, 2022 at 4:45
  • 1
    Regarding my_tuple = p accessing "tuple directly without needing to do .tuple", I can't think of a way this is possible. The statement my_tuple = p defines a new reference to an object, which could be any python object in principle. Commented Feb 18, 2022 at 4:45
  • collections.namedtuple may be what you want. Review it, and let us know how it doesn't work for you. Commented Feb 18, 2022 at 4:47
  • @hilberts_drinking_problem - namedtuple does it. You can access its values as p.x or p[0]. It even works with isinstance(p, tuple). You don't need the my_tuple = p part, though. As you say, all that does is add a ref count to the existing object. Commented Feb 18, 2022 at 4:51
  • 1
    @tdelaney good point about the syncing issues, I hadn’t considered that. The Point was a somewhat simplified example of what I’m dealing with, but overall I think I’m over-engineering this piece in attempt to make my code cleaner (which has backfired haha). I think I’ll try to use the namedtuple as suggest by @Thomas Commented Feb 18, 2022 at 4:54

4 Answers 4

21

Python 3.12+

PEP 695 introduces a new, more compact and explicit way to create generic classes and functions. In addition, the PEP introduces a new way to declare type aliases using the type statement, which creates an instance of TypeAliasType. As a result, you can do this:

type Point = tuple[float, float] # or type Point[T] = tuple[T, T] # more generic 

For example, you can use it like this:

type Point = tuple[int, int] p: Point = (0,0) print(p) # (0,0) p = (4,5) # re-assign Point p print(p[0]) # 4, your "x" print(p[1]) # 5, your "y" 

Depending on your use case, this answer may not be relevant.

Sign up to request clarification or add additional context in comments.

2 Comments

All this does at runtime is make ordinary tuples. It doesn't provide any functionality beyond what ordinary tuples already do. The OP specifically wanted to be able to access elements with attribute access, and this doesn't do that.
If there are some needs to use the type aliases in older Python version <3.12, you may able to mark a type alias as TypeAlias type to distinguish from plain variable: a: TypeAlias = int. need typing_extensions to work on <3.10, and is marked deprecated in Python 3.12. docs.python.org/3.12/library/typing.html#typing.TypeAlias
9

I am not sure what you want to do with the tuple. p will always be an instance of Point. What you intend to do there won't work.

If you just want to use the dot notation, you could use a namedtuple or a dataclass instead of a class. Then cast their instances to a tuple using tuple() and astuple().


Using a namedtuple and tuple():

from collections import namedtuple Point = namedtuple("Point", ["x", "y"]) p = Point(4, 5) x = p.x y = p.y xy = p # xy = tuple(p) not necessary since namedtuple is already a tuple 

Note: namedtuple is immutable, i.e. you can't change x and y.


Using a dataclasses.dataclass and dataclasses.astuple():

from dataclasses import dataclass, astuple @dataclass class Point: x: int y: int p = Point(4, 5) x = p.x y = p.y xy = astuple(p) 

1 Comment

Great answer but you don't need xy = tuple(p) with namedtuple. It already is a tuple (or at least a subclass there-of).
0

You have to specify __iter__

class MyDataType: def __init__(self, x, y): self.x = x self.y = y def __iter__(self); return iter((self.x, self.y)) 

This will let it know what you mean when you do tuple(instanceOfMyDataType)

It will not return a tuple if you just do var = instanceOfMyDataType but this is OK.

Comments

0

A better way is to use inheritance to make Point a subtype of tuple, so it will have all of the functionality of a tuple, but you can extend its functionality, like this:

class Point(tuple): def __new__(cls, x, y): return tuple.__new__(cls, [x,y]) def __init__(self, x, y): tuple.__init__([x,y]) self.x = x self.y = y 

We make Point a child class of tuple by declaring it as Point(tuple). We need to use __new__ (docs) in addition to __init__ because tuples are immutable. __new__ creates the object and __init__ initializes it, and since immutables can't be changed after creation, you must overwrite __new__. Then we initialize just like a normal child class, and declare the x and y attributes for the extra functionality.

You can use the class like this:

p = Point(0,1) my_x = p.x my_y = p.y my_tuple = p print(my_x) #0 print(my_y) #1 print(my_tuple) #(0, 1) 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.