waveform80 / picamera

A pure Python interface to the Raspberry Pi camera module
https://picamera.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
1.57k stars 357 forks source link

New attributes on runtime #425

Open birkanozer opened 7 years ago

birkanozer commented 7 years ago

Hi all,

On my application i build a middle API for both RPi camera and DSLR cameras. I have a class for Pi camera and another for DSLR cameras. Both system has their own API for control. My classes handles differences and provides same class method for both system. On DSLR cameras we van change aperture, but not in RPi camera. I want to add symbolic 'aperture' method to PiCamera class, so i can iterate same list or settings to get or set different type of camera systems.

Although i tried following solutions, i couldn't manage to define new attribute to the class.

from picamera import PiCamera

camera = PiCamera()
camera.aperture = '2.8'
print(camera.aperture)
from picamera import PiCamera

camera = PiCamera()
camera.aperture = lambda: None
setattr(camera.aperture, 'aperture', '2.8')
print(camera.aperture)

Is there any way to do that?

waveform80 commented 7 years ago

Sorry about that! This is because picamera uses Python's slightly obscure __slots__ mechanism to limit the parameters that are associated with the class. The reason for doing so is that a good portion of picamera's users are in education and Python's dynamic nature can cause serious issues for teachers attempting to debug things in a class-room situation. Consider if __slots__ weren't used and a teacher (who is capable, but exactly a guru, with Python) finds themselves in the middle of a class-room trying to figure out why little Jonny's script doesn't work:

...
camera = picamera.PiCamera()
camera.annotation = 'Hello world!'

No error appears, so there's no hint for the teacher to start looking near a particular line, but the script isn't doing what is expected. With __slots__ in place, the camera.annotation line immediately raises an exception and the teacher (or little Jonny) have a much better chance of figuring things out quickly. Unfortunately this does mean that people in your position (who wish to extend the class) need to jump through a few extra hoops.

The basic solution is: sub-class PiCamera. At its most trivial (an empty sub-class), this results in a sub-class without __slots__ (so it has a __dict__ instead) which means you can set anything you like on it:

import picamera

class MyCamera(picamera.PiCamera):
    pass

camera = MyCamera()
camera.aperture = 28

Alternatively, you can make things more complex. For example, if you wish to keep the __slots__ limits in place (to make the sub-class as close to PiCamera's behaviour as possible), but still add your own attributes and ensure they've got initial values:

import picamera

class MyCamera(picamera.PiCamera):
    __slots__ = ('aperture',)
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.aperture = 28 # initial value

camera = MyCamera()
camera.aperture = 29