4

I have a couple of questions about classes in Python.

There is the Bullet class, which represents an abstract projectile in a computer game and there will be a hell a lot of it's instances (you see, I'm trying to make a "bullet hell" game :) Instances would have the same image and movement function while having different positions. In the code below I've tried to put common stuff to class attributes and load their values with the class_init class method. The goal is to deduplicate data and save some memory.

Q1: Is that class_init method a right thing to do? Maybe there is a more elegant way.

Q2: Will it actually improve memory usage?

SOLVED: Thank you for your tips, everyone! I guess I was not entirely wrong about my code, though it could be improved. Gotta read about metaclases, slots and profiling in Python. Still has a long way to go, sigh. Anyway, thanks!

class Bullet(object):
    @classmethod
    def class_init(cls, image, movement_function):
        cls.image = image
        cls.movement_function = movement_function

    def __init__(self, pos):
        super(Bullet, self).__init__()
        self.pos = pos


if __name__ == '__main__':
    Bullet.class_init('sprite.png', lambda x: x + 1)
    b1 = Bullet((10,10))
    b2 = Bullet((20,20))
    print 'pos', id(b1.pos) == id(b2.pos)
    print 'image', id(b1.image) == id(b2.image)
    print 'movement_function', id(b1.movement_function) == id(b2.movement_function)
Anton Melnikov
  • 1,048
  • 7
  • 21
  • The `movement_function` could just be an instance method, but the class attribute `image` seems like a good idea. – jonrsharpe May 19 '14 at 13:14
  • 3
    If you're looking for memory efficient, light weight objects, look at [`namedtuple`](http://docs.python.org/2/library/collections.html#collections.namedtuple) or classes that define [`__slots__`](http://stackoverflow.com/questions/472000/python-slots). – Lukas Graf May 19 '14 at 13:20
  • Wow, that looks interesting, thanks, Lukas. – Anton Melnikov May 19 '14 at 13:31
  • 1
    @AntonMelnikov you're welcome. See [this video](http://pyvideo.org/video/367/pycon-2011--fun-with-python--39-s-newer-tools) for details on namedtuples (11:35) by Raymond Hettinger. I believe he also talks about `__slots__` in [this video](http://pyvideo.org/video/1779/pythons-class-development-toolkit), but I don't have a timestamp handy. – Lukas Graf May 19 '14 at 13:33

3 Answers3

1

Q1: Is that class_init method a right thing to do? Maybe there is a more elegant way.

It's not wrong. Perhaps the more idiomatic way to do it would be as the __init__ on a metaclass, but either way is fine.

Q2: Will it actually improve memory usage?

Probably not by much, because the image and function are not copied - only the reference to those objects is copied.

The way to learn if something will improve speed or memory usage is to profile it. Until you identify the problem, don't do tricks that might optimize something.

cls.movement_function = movement_function

Why isn't movement_function just a method?

Marcin
  • 48,559
  • 18
  • 128
  • 201
  • You are right, it would be better to measure it than guessing, gotta read about that. – Anton Melnikov May 19 '14 at 13:21
  • Also, could you please give an example about metaclasses? About movement_function - well, I was thinking to pass functions for different types of movement, like linear and circular, while being able to hotswap it if needed. I dunno, maybe it' stupid. – Anton Melnikov May 19 '14 at 13:30
  • @AntonMelnikov It's unusual to do that, because you're creating it on the class. If you have lots of different functions, it probably makes more sense than inheritance, if the function is per-object. Also, you might want to provide a default method, and just override it by assignment when the time comes. Google python metaclasses. – Marcin May 19 '14 at 13:32
1

Q1: Is that class_init method a right thing to do? Maybe there is a more elegant way.

whether this is a right thing or not is hard to tell. But it's definitely not a bad way to do this. You're using a class method to define class attributes, it makes sense and looks good. If you think OOP-wise, you're encapsulating the class data within a class method.

Though, the movement_function is a behavior working on an instance member. So it should better be defined as an instance method, and have it changed using class inheritance in case you need to have different movements.

Q2: Will it actually improve memory usage?

For the image, it really depends on how you initialize it. Imagine the following:

class Bullet2:
    def __init__(self, image):
        self.image = image

if you do:

for _ in range(1,10000):
    with open('/path/to/image.png', 'r') as f:
        Bullet2(YourImage(f.read()))

then it'll take 10000 instances of the image. That'll eat a lot of memory.

Though if you do:

with open('/path/to/image.png', 'r') as f:
    img = YourImage(f.read())
    for _ in range(1,10000):
        Bullet2(img)

then you'll have 10000 references to the same instance. Which would take the same amount of memory as yours:

with open('/path/to/image.png', 'r') as f:
    img = YourImage(f.read())
    Bullet.class_init(img)
    for _ in range(1,10000):
        Bullet()

If your solution has some elegancy, it's whether you want to change the image of all instances using a single method or not. Though I'd better use a method called setup_image(), instead of the too generic class_init() which does not hint about what it does.

If you want to change the image of each instance at a time, then you'd better use a reference within Bullet, like I'm doing with Bullet2:

class Bullet2:
    def __init__(self, image):
        self.image = image
    def change_image(self, img):
        self.image = img

About setting a callback to a function as a class member, or creating a method for your class, it will be equivalent memory-wise, though the second option will be better, because it'll be more readable, and it'll be defining the behavior of your class. If you then want to change what does the method, then you can use class inheritance.

zmo
  • 24,463
  • 4
  • 54
  • 90
1

The more idiomatic way to customize a class is to define a factory function which will return a subclass of Bullet. If you aren't concerned with the name of the resulting subclass, you can skip making cls_name a parameter and have make_bullet use a hard-coded name instead.

class Bullet(object):
    def __init__(self, pos):
        super(Bullet, self).__init__()
        self.pos = pos

def make_bullet(cls_name, image, movement_function):
    return type(cls_name, (Bullet,), {'image': image,
                                      'movement_function': movement_function })

if __name__ == '__main__':
    MyBullet = make_bullet('MyBullet', 'sprite.png', lambda x: x+1)
    b1 = MyBullet((10,10))
    b2 = MyBullet((20,20))
    print 'pos', id(b1.pos) == id(b2.pos)
    print 'image', id(b1.image) == id(b2.image)
    print 'movement_function', id(b1.movement_function) == id(b2.movement_function)
chepner
  • 497,756
  • 71
  • 530
  • 681
  • 1
    well, first it's an useless use of a factory here, and second it's not modern python at all! Instead of using `type()`, passing explicitly the name of the class as a string parameter… You'd better use a `classmethod` to make your factory. But then, I'm not sure the OP wants a factory… – zmo May 19 '14 at 13:34