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)
3 Answers 3
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.
Comments
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?
3 Comments
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)
1 Comment
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...
movement_functioncould just be an instance method, but the class attributeimageseems like a good idea.namedtupleor classes that define__slots__.__slots__in this video, but I don't have a timestamp handy.