12

I'm quite new to Python and I need to make declare my own data structure, I'm a bit confused on how to do this though. I currently have:

class Particle:

    def __init__(self, mass, position, velocity, force):

        self.mass = mass
        self.position, self.velocity, self.force = position, velocity, force

    def __getitem__(self, mass):
        return self.mass

    def __getitem__(self, position):
        return self.position

    def __getitem__(self, velocity):
        return self.velocity

    def __getitem__(self, force):
        return self.force

This isn't working, however, when I try to define an instance of the class with:

 p1 = Particle(mass, position, velocity, force)

Every value just ends up as a (0.0, 0.0) (which is the value for velocity and force).

Could someone explain where I'm going wrong, all I need from the data structure is to be able to pull the data out of it, nothing else. (edit: actually, sorry, I will have to change them a bit later on)

Thanks

smci
  • 32,567
  • 20
  • 113
  • 146
djcmm476
  • 1,723
  • 6
  • 24
  • 46
  • 2
    this works for me as is. – askewchan Mar 07 '13 at 21:08
  • 3
    All of your `__getitem__` definitions are the same method. You need to check which item is actually being retrieved. – Waleed Khan Mar 07 '13 at 21:08
  • 3
    @askewchan It shouldn't. – Ismail Badawi Mar 07 '13 at 21:09
  • 2
    Aside: on dimensional consistency grounds, I'd store acceleration rather than force. – DSM Mar 07 '13 at 21:10
  • Why not just use `particle.mass` and so on to get the values? – Waleed Khan Mar 07 '13 at 21:10
  • 1
    How do try to access? The way you defined it, `p1[]` returns `self.force`. I'm curious how you imagine to access the different attributes... –  Mar 07 '13 at 21:10
  • I used `p1.mass` to get the values, didn't use any of the `__getitem__`s – askewchan Mar 07 '13 at 21:10
  • 1
    @askewchan Well, duh! That part isn't the problem ;-) The `__getitem__` "overloads" are wrong, for several reasons. –  Mar 07 '13 at 21:11
  • askewchan: Then please edit that missing information in; the question this seems to be asking at first glance is a red herring: you declare four conflicting overridden versions of `__getitem__`, then proceed to use none of them! and bypass them by referencing the attribute directly. And please show the exact code you complain about in `Every value just ends up as a (0.0, 0.0)`. I checked the attribute values are fine, and you could verify that by inspecting `p1.mass, p1. position` etc. So, what is the actual question here? The Pythonic way to access object attributes? or what? – smci Jan 07 '19 at 09:19
  • So I renamed this *"What's the Pythonic way to initialize, set and get my custom object's attributes, by name?"* because that's what you seem to be asking. But please correct that if not. – smci Jan 07 '19 at 09:22
  • Related Meta discussion: [Organizing all the Python questions and meanings of 'dot-notation', as applied to Python](https://meta.stackoverflow.com/questions/378660/organizing-all-the-python-questions-and-meanings-of-dot-notation-as-applied-t) – smci Jan 08 '19 at 04:50

6 Answers6

25

First off, you should understand that __getitem__ is syntactic sugar. It's nice to have, but if you don't need it, don't use it. __getitem__ and __setitem__ are basically if you want to be able to access items from your object using bracket notation like:

p= Particle(foo)
bar = p[0]

if you don't need to this, don't worry about it.

Now, onto everything else. It looks like you've got the main characteristics you want your object to carry around in your __init__ definition, which is fine. Now you need to actually bind those values onto your object using self:

class Particle:
    def __init__(self, mass, position, velocity, force):
        self.mass = mass
        self.position = position
        self.velocity = velocity
        self.force = force

That's really it. You can now access these values using dot notation, like so:

mass,pos,vel,f = 0,0,0,0 # just for readability
p = Particle(mass,pos,vel,f)
print p.mass, p.position, p.velocity, p.force

One of the nice things we get out of this is that if we ask python what p is, it will tell you that it is an instance of the Particle type, like so:

in [1]: p
out[1]: <__main__.Particle instance at 0x03E1fE68>

In theory, when you work with objects like this you want there to be a "layer of abstraction" between the user and the data such that they don't access or manipulate the data directly. To do this, you create functions (like you tried to do with __getitem__) to mediate interactions between the user and the data through class methods. This is nice, but often not necessary.

In your simpler case, to update the values of these attributes, you can just do it directly the same way we accessed them, with dot notation:

in [2]: p.mass
out[2]: 0

in [3]: p.mass = 2 
in [4]: p.mass
out[4]: 2

You might have figured this out already, but there's nothing magical about the __init__ function, or even the class definition (where you would/should generally be defining most of your class's attributes and methods). Certain kinds of objects are pretty permissive about allowing you to add attributes whenever/wherever you want. This can be convenient, but it's generally very hacky and not good practice. I'm not suggesting that you do this, just showing you that it's possible.

in [5]: p.newattr ='foobar!'
in [6]: p.newattr
out[6]: 'foobar!'

Weird right? If this makes your skin crawl... well, maybe it should. But it is possible, and who am I to say what you can and can't do. So that's a taste of how classes work.

David Marx
  • 8,172
  • 3
  • 45
  • 66
  • That's great, thanks! Quick follow up question though, how do I change the values after creating an instance of Particle? – djcmm476 Mar 07 '13 at 21:18
  • @Incredidave Assignment, as with variables (there are some subtle differences, but most people get along for months without understanding them, so you should be fine ^^): `p.mass = new value` –  Mar 07 '13 at 21:25
  • @Incredidave Updated the post to demo assignment and show you a little black magic – David Marx Mar 07 '13 at 21:34
8
class Particle:
    def __init__(self, mass, position, velocity, force):
        self.mass = mass
        self.position = position
        self.velocity = velocity
        self.force = force

particle = Particle(1, 2, 3, 4)
print(particle.mass)  # 1

If you want to pretend your class has properties, you can use the @property decorator:

class Particle:
    def __init__(self, mass, position, velocity, force):
        self.mass = mass
        self.position = position
        self.velocity = velocity
        self.force = force

    @property
    def acceleration(self):
        return self.force / self.mass

particle = Particle(2, 3, 3, 8)
print(particle.acceleration)  # 4.0
Waleed Khan
  • 11,426
  • 6
  • 39
  • 70
  • This is great, thanks. I'm quite new to Python, and I was struggling to find anything decent to describe this. As usual with Python, the answer is simpler than I thought. – djcmm476 Mar 07 '13 at 21:15
  • Out of interest, why *pretend* to have properties, when actually having properties is so easy? –  Mar 07 '13 at 21:19
  • @delnan Suppose a property is defined by a formula relying on two other properties. If you change one of them (like mass) you'd also have to update acceleration. That probably means buggy or messy code. – Waleed Khan Mar 07 '13 at 21:19
  • 1
    @WaleedKhan I am a Python veteran. When I say "property", I mean the `property` builtin, preferably used as decorator. Which, as you may or may not know, do exactly that -- just in a better and prettier way. –  Mar 07 '13 at 21:21
  • @delnan How would I go about making, for example, the above acceleration property using the method you're talking about? – djcmm476 Mar 07 '13 at 21:22
  • @delnan That's true. I'm still not used to using `@property`. I'll update the answer. – Waleed Khan Mar 07 '13 at 21:23
4

Seems like collections.namedtuple is what you're after:

from collections import namedtuple

Particle = namedtuple('Particle', 'mass position velocity force')
p = Particle(1, 2, 3, 4)
print p.velocity
Jon Clements
  • 138,671
  • 33
  • 247
  • 280
  • 1
    Only if the attributes don't have to change ever during an object's lifetime, and you're fine with it being a sequence. The neatness of namedtuples also suffers a blow when you need to add methods, or something constructor-like (they're still useful, but require more careful handling and ugly code to make it work well). –  Mar 07 '13 at 21:16
  • 1
    @delnan True, but I'm going by the OP's statement of `all I need from the data structure is to be able to pull the data out of it, nothing else.` – Jon Clements Mar 07 '13 at 21:17
  • Yeah, and your answer is alright. It's just that I almost never see those limitations even hinted at, even when important, so I made a habit of mentioning them. –  Mar 07 '13 at 21:18
  • 1
    Ah, crap, I'll change that. I will need to edit them a bit afterwards (my bad!). – djcmm476 Mar 07 '13 at 21:20
1

you can just put this class definition ahead before you use it. If you want to declare it, check this site: http://www.diveintopython.net/getting_to_know_python/declaring_functions.html

By the way, your question is similar to this post: Is it possible to forward-declare a function in Python? and also this post: Is it possible to use functions before declaring their body in python?

Community
  • 1
  • 1
tqjustc
  • 3,624
  • 6
  • 27
  • 42
0

If you just need to store some attribute values (similar to a C-language struct), you can just do:

class myContainer(object):
    pass  # Do nothing

myContainerObj = myContainer()
myContainerObj.storedAttrib = 5
print myContainerObj.storedAttrib
neilr8133
  • 142
  • 6
  • Nah, that's just an awful way to write a dictionary. I am opposed to using the ability to add and remove attributes to objects without very strong reasons. It makes errors harder to spot (sometimes this thing has an attribute, sometimes it hasn't, depending on control flow). –  Mar 07 '13 at 21:23
  • @delnan I agree in general, but for someone new to Python it's a simple solution and doesn't require an understanding of dictionaries, hashing, and the `get()` method. (It's also in the Python tutorial docs: http://docs.python.org/3/tutorial/classes.html#odds-and-ends) – neilr8133 Mar 07 '13 at 21:37
  • edit: (_insert_: "`get()` method to specify a value if the attribute doesn't exist, or some other method to probe if it's been defined"). Personally, I'd rather see a full class with `__init__` and proper get/set methods for all the properties, but in my (possibly incorrect) opinion, this met the OP's needs.) – neilr8133 Mar 07 '13 at 21:43
0

In Python 3.7+ there is the data class library. This library will allow you to create your own class to hold data quickly using a decorator, @dataclass.

The @dataclass decorator allows you to quickly define and add functionality to a class you intend to mostly be used to hold data.

A data class for your problem might be implemented as below. I've included type hints and default values which you might also find helpful.

from dataclasses import dataclass

@dataclass
class Particle:
   mass: float
   position: float
   velocity: float = 0.0
   force: float = 0.0

Here is a useful article which explains how to use data classes in Python 3.7+ and some other features.

Thomas Dickson
  • 313
  • 3
  • 9