Classes

A Cat Goes…

When you first begin exploring classes it is best to start small and work your way up. It is all about relationships. If you have a pet, take a look at him or her, and start thinking about their origins. Is a cat just a cat? No. Your cat is an animal. What do animals have in common? Let’s begin our new adventure with a quick introduction to base classes. In the following example we have defined the generic layout of an animal with only two attributes. An animal should have a name and make some kind of sound, right? Easy as pie.

Base Class Example

    class Animal(object):
        def __init__(self):
            self.name = ""
            self.sound = ""
        def get_name(self):
            return self.name
        def get_sound(self):
            return self.sound

A base class is the parent of a derived class. You’ll never remember that, instead, try to relate to it. For instance, your physical features were derived from both of your parents.

    class Mom(object):
        pass

    class Dad(object):
        pass

    class Child(Mom, Dad):
        pass

Breaking it Down

Naming Conventions

In the warped world of procedural programming people have gotten used to writing simple or condensed attribute names. To work with classes efficiently you must break the bonds of your procedural programming jail cell and start writing clear and concise names.

The Animal class should not be named A, nor should self.name be expressed as self.n. C is a programming language and n is a letter in the alphabet. Naming attributes in this way is asinine and tends to become confusing in larger applications. “I’m the only one that’s going to see this code!” exclaims the non-conformist. Stop. Nobody cares. Don’t give me the old, “But that’s what comments are for!” bit either. Your code is like a resume- Only the keywords stand out to the person trying to understand it. The rest is entirely aesthetic fluff. Comments are useful in code so don’t get all depressed, however my reason for telling you this is entirely valid. Writing a paragraph to explain the responsibility of every vague attribute is a total waste of valuable time.

Defining a Class

    class Animal(object):

In Python, all class definitions should begin with a capital letter. This allows for easy object assignment (i.e. ball = Ball()). Speaking of that, nearly everything in Python is an object. New-style classes need to inherit attributes from the internally defined object class. By doing this we are telling Python, “Hey! I’m creating a new class and I require the initial attributes of an object in order to function properly!”

The Initialization Method

    def __init__(self):

As with all other object-oriented programming languages, Python followed suit with an obligatory __init__ method as the entry point for their class infrastructure. Attributes defined with the built-in keyword self will be accessible throughout the rest of the class. Declaring an attribute without the self prefix will not propagate the attribute. An attribute without self is local to the scope of the function it is declared in. This is useful for temporary assignments, because self.name is not equivalent to name. For example, the following assignment is valid:

    bar = "foo"
    self.bar = bar

Although it is not mandatory for a class to define an initialization method, for this example it will be. You may be asking yourself, “Why are we passing this bizarre attribute named self into the method?”. Simple! Classes are self-contained structures that must be able to reference information stored within. The way Python (and many other object-oriented programming languages) achieve this feat is to define a pointer or reference back to the class itself. Try to think of a class as a brain and self as the consciousness. A brain with no concept of self is not a very useful brain at all, right? The self parameter permits a class to communicate with everything defined within, and by passing self into different methods, this allows for quick, stable and easy access to all of the its own attributes and methods. Even if it seems implicit for a brain to know that an arm or a leg exists, it won’t until you explicitly tell it. The __init__ method is where we define our default class attributes.

    self.name = ""
    self.sound = ""

Hey wait, why are these values empty? At the very least doesn’t an animal deserve to have a name? That’s up to you! These attributes can be defined however you wish, but remember, we’re writing a base class. With the flexibility classes provide we will be able to inherit these attributes from within another class without the need to redefine them. By doing it this way we can reduce the amount of redundant code in our program. Feel free to scoff, but in the end the overall readability of our code increases by several orders of magnitude.

Defining Methods

All class methods must start with self as the first argument. Python does not permit you to pick and choose with methods are not part of the class.

    def get_name(self):
        return self.namedef get_sound(self):
        return self.sound

Now we have two methods returning information stored in the class. Of course, defining a function to access a class’s attributes is not required, but doing so will provide you a clear and concise model for interacting with your data.

Test it out!

>>> animal = Animal()
>>> animal
<__main__.Animal at 0x2002810>
>>> vars(animal)
{"name": "", "sound": ""}

Congratulations, my friend, you have created your very first unbelievably useless base class!

Inheritance

Let’s make our Animal class a bit more useful by creating an animal based on Animal!

    class Cat(Animal):
        def __init__(self, name, sound):
            Animal.__init__(self)
            self.name = name
            self.sound = sound

The Cat class based on our Animal base class is complete! Seriously. I’m not kidding! Remember when I said we would reduce the amount of coding required to get the job done? Take a moment to unlearn all of the confusing mumbo-jumbo you’ve read about, because classes are not too hard or too confusing to work with. In their deconvolved form they’re quite simple to comprehend. As I stated earlier, everything is about relationships. If you are capable of interpreting an everyday Cat as an Animal and self as the conscious part of a class, then you’re already on the right track.

In order to utilize the attributes and methods stored within Animal we need to make sure Cat knows about them. In order to do that we must call the initialization method for the Animal class. Animal.__init__(self) literally activates the parent class so you may derive features from it. Cat’s initialization method defines two local parameters name and sound. In order to pass them back to the base class we need to redefine the default values for self.name and self.sound.

Test it out!

>>> cat = Cat("Gorda", "Meow")
>>> cat
<__main__.Cat at 0x20fe790>
>>> vars(cat)
{"name": "Gorda", "sound": "Meow"}
>>> print("I am a {}.  My name is {} and I say, {}".format(cat.__class__.__name__, cat.get_name(), cat.get_sound()))
I am a Cat.  My name is Gorda and I say, "Meow".

I must say, now we’re getting somewhere. We took a rather innocuous base class and turned it into something we can use repeatedly. Need a Dog? How about a Hamster? Not a problem.

    class Dog(Animal):
        def __init__(self, name, sound):
            Animal.__init__(self)
            self.name = name
            self.sound = sound

    class Hamster(Animal):
        def __init__(self, name, sound):
            Animal.__init__(self)
            self.name = name
            self.sound = sound

Lies! Right about now you’re ready to call me a liar. You’re thinking, “Classes are confusing and hard to work with! You may as well have used the Animal class instead of inheriting the values! This isn’t useful! I’m only coding more!”

You are wrong.

I told you classes were easier to understand when they are presented in their deconvolved form. Let’s increase the complexity a bit to illustrate why classes (using inheritance) are incredibly useful.

Every Animal Eats

Who is hungry? First, we need to define our food groups. In a perfect world our Animal could eat “stuff”, and it wouldn’t even matter what it was, but this isn’t a perfect world and our Animal will be quite picky. Being picky gets your point across (most times).

    class DietRequires(object):
        unknown = -1
        carnivore = 0
        herbivore = 1

    class FoodType(object):
        unknown = -1
        meat = 0
        plant = 1

Woah! What happened? Why don’t these classes have __init__ methods?

You’re not going crazy and neither am I. Classes have the ability to contain values without the need for them to be initialized (aka no __init__() method. Surprised yet? We’re going to take full advantage of this neat little feature!

    class Food(object):
        def __init__(self, name):
            self.name = name
            self.eaten = False
            self.edibleby = DietRequires.unknown
            self.set_food_type(FoodType.unknown)

        def is_plant(self):
            if self.get_food_type() is FoodType.plant:
                return True
        return False

        def is_meat(self):
            if self.get_food_type() is FoodType.meat:
                return True
            return False

        def is_eaten(self):
            if self.eaten:
                return True
            return False

        def was_eaten(self):
            self.eaten = True

        def get_food_type(self):
            return self.food_type

        def set_food_type(self, food_type):
            self.food_type = food_type
            if self.food_type is FoodType.plant:
                self.edibleby = DietRequires.herbivore
            elif self.food_type is FoodType.meat:
                self.edibleby = DietRequires.carnivore
            else:
                self.edibleby = DietRequires.unknown

In the Food base class there are many ease-of-use methods. Notice the way set_food_type automatically assigns self.edibleby based on what type of food it is. The reason this is helpful is, if we were to create a Carrot class, it would be trivial to tell the object it was comprised of plant matter rather than muscle tissue. By the way… Can you guess what animals are really good at? Turning into delicious food!

    class Animal(Food):
        def __init__(self):
            Food.__init__(self, self.__class__.__name__)
            self.name = ""
            self.sound = ""
            self.stomach = []
            self.diet = DietRequires.unknown
            self.set_food_type(FoodType.meat)

        def set_diet(self, diet):
            self.diet = dietdef eat(self, food):
            if food.edibleby is self.diet:
                food.was_eaten()
                self.stomach.append(food)
                return True
            return False

        def get_name(self):
            return self.name

        def get_sound(self):
            return self.sound

By using inheritance in a smart manner it took very little effort to mold our Animal base class into something a bit more palatable. Animals, in addition to being eaten (because they are tasty), are capable of eating as well.

Reimplemented Cat class

    class Cat(Animal):
        def __init__(self, name, sound):
            Animal.__init__(self)
            self.name = name
            self.sound = sound
            self.set_diet(DietRequires.carnivore)

What’s new here? We have added a single line of code to override the initial value for Animal‘s dietary needs using set_diet. Cats eat meat and are made of it too! Isn’t this fun? Look at how much code you did not have to write!

    class Mouse(Animal):
        def __init__(self, name, sound):
            Animal.__init__(self)
            self.name = name
            self.sound = sound
            self.set_diet(DietRequires.herbivore)

A Game of Cat and Mouse

Samuel L. Jackson once said, “Hold on to your butts.”
I implore you to do the same.

>>> cat = Cat("Gorda", "Meow")
>>>  mouse = Mouse("Mighty", "Squeak")
>>> cat.eat(mouse)
>>> mouse.is_eaten()
True

Is your jaw on the floor yet? Man, I hope so.

Accessing Object Attributes

The Nonsensical Way

First off, it isn’t inappropriate to access object attributes directly at the global scope:

    cat = Cat("Gorda", "Meow!")
    if cat.food_type is FoodType.meat:
        # Do something

However, you will need to avoid writing logic outside of your class at all costs, because after a while it becomes extremely difficult to read and comprehend. Secondly, more often than not you will find yourself reinventing the wheel at every turn. Accessing objects this way will lead to bugs that are hard to spot (or worse). You definitely don’t want that! Ask yourself, “Would I prefer to fix a mistake once, or the same mistake multiple times?” Always choose the former and never look back.

The Sensible Way

Applying a hefty dose of logic statements within your class structure, however, is imperative.

    cat = Cat("Gorda", "Meow!")
    if cat.is_meat():
        # Do something

Try your best to get in the habit of writing code once so that it can be reused. Spending a few extra moments to carve out a method to take care of something simple will actually cut down on your key strokes.

A Note Concerning Public/Private Access

If you have spent any time writing Python modules or packages you should also be familiar with restricting access to your underlying code by prefixing your functions or attributes with underscores (ex: _example or __example). This functionality is extraordinarily useful for masking internal functions and attributes within a class, too. Consider the following code fragment:

Testing the Theory

>>> cop = Cop()
>>> cop.name
"Bonkers"

After instantiating the object, cop, the Cop class automatically assigned self.name to the value of __name_default. What happens if you attempt to access __name_default?

>>> cop.__name_default
AttributeError: "Cop" object has no attribute "__name_default"

The AttributeError exception is a direct result of what we instructed Python to do. Completely disallow access to __name_default from outside the scope of the Cop class. You will be using this feature heavily in the very near future, so be creative and try doing this for yourself a few times.

Real Life Work

Ah yes, that. The thing we all do to survive. I have no doubt your boss would be disappointed if they walked into your office to find you writing cat and mouse classes. Looks like it is time to open Pandora’s box and let the demons flow out.

Intimidation

If everyone in your shop is writing procedural programs it can be easy to drop the idea of writing classes due to “lack of necessity”. Well, listen up. This all goes back to when you were little and your parents asked you, “If your friends jumped off a bridge would you jump off too?” This is the same thing. Just because everyone around you is wasting their time doesn’t mean you need to do the same. What if I am the only person writing classes? Won’t the people lacking my skills not be capable of interpreting my code?

Yes and no. To the untrained eye the mere sight of a complex class structure could send your co-workers screaming into the hills. On the bright side – if you’re an antisocial person this will probably work out in your favor. All joking aside, there will be people at your job interested in writing object-oriented code but never had the guts to try it out for themselves. The best thing to do is befriend them and collaborate at lunch time.

Procedural Versus Object-Oriented

What is the difference between writing procedural code and writing classes? I’m presented with this question a lot, and there is no easy answer. For example, my second grade teacher, Mrs. Howell, asked the class to consider the following question:

How would you tell to me make a peanut butter and jelly sandwich?

Mrs. Howell then instructed us to write a detailed procedure to illustrate the steps necessary to make a peanut butter and jelly sandwich. After collecting our papers she pulled out a loaf of bread and with a big grin on her face she said, “All right let’s see who knows how to make a sandwich.”

For the most part, our overall concept for how to create a sandwich wasn’t wrong, but she didn’t ask us to generalize a sandwich, she wanted to know how to make one.

Twenty-nine papers looked like this:

  1. Take two pieces of bread
  2. Put peanut butter on one slice
  3. Put jelly on another slice
  4. Now eat it.

Only one looked like this:

  1. Remove the lid of the peanut butter jar
  2. Remove the lid of the jelly jar
  3. Open the bread
  4. Take out two pieces of bread
  5. Place the two pieces of bread flat next to each other
  6. Using a knife, spread peanut butter on one piece of bread
  7. Using a knife, spread jelly on the other piece of bread

Mrs. Howell’s Point

She was trying to teach us the right way to work through a real problem. Making a sandwich seems entirely trivial until you write out a procedure. The same concept applies to programming. We have all encountered trivial tasks that may take hours to implement properly, especially when we have no idea where to begin.

What she didn’t emphasize:

There is no right or wrong way to make a sandwich. If you want to apply new-found knowledge, do so by trial and error, because there’s nothing wrong with being wrong, even at work. You will never be fired for trying, but you can be fired for not trying.

What you should know:

If you find procedural programming works best for the applications you write on a daily basis, then stick to it, but don’t limit yourself. Expanding your knowledge can only benefit you. Always keep your mind open to learning new and interesting ways to perform your job.

Classes at Work

In order to truly grasp the concept of how a class can benefit you, we’re going to create a full transfer charged-coupled device (like one that might be used in Astronomy). It will be capable of detecting pseudo-random electrons from photons passing through imaginary pixels. In order to keep this example short many attributes of the CCD have been abridged and values such as the electron charge, for example, have been normalized. Let’s get started!

The Beginning

Our CCD is going to do some pretty advanced list magic, so unless you enjoy reinventing the wheel and creating your own class to handle queues, stick to using the deque module.

    import random
    from collections import deque
    from matplotlib import pyplot

Don’t cry. If you’re wondering why NumPy hasn’t been imported, I’ll clear things up a little. You’re trying to learn how to program with classes in Python. While NumPy is a great module for processing large data sets its use would detract from the true scope of what you’re attempting to learn. Baby steps my friends, baby steps.

The Particles

Since the whole purpose of a CCD is to record the amount of electrons released when photons collide with a pixel’s potential well, so we need to create the basic building blocks.

    class Electron(object):
        def __init__(self):
            self.charge = 1
    class Photon(object):
        def __init__(self):
            self.electrons = []
            chance = random.randint(0, 10)
            for _ in range(chance):
                self.electrons.append(Electron())

As our Photons are initialized, and random quantity of electrons will also be added in order to mimic light intensity.

The Pixels

    class Pixel(object):
        def __init__(self):
            self.electrons = []
            self.active = False

        def receive(self, photon):
            if photon.electrons:
                self.active = True
                for electron in photon.electrons:
                    self.electrons.append(electron)
            self.active = False

        def dump(self):
            electrons = self.electrons
            self.electrons = []
            return electrons

Breaking it down, our Pixel starts off with two attributes self.electrons and self.active. The former is a fictitious potential well to house our trapped electrons, and the latter is boolean to allow for quick checking to see if our Pixel is currently receiving light. The receive method takes a single photon object (notice the lower case) as an argument. If the photon object contains electrons, then the Pixel is considered active. Each electron discovered as the photon objects pass through our Pixel are stored in self.electrons. dump returns the electron objects stored in the Pixel, but not before resetting the potential well. We don’t want any free electrons left over, right? That would certainly screw up our model.

Pipe-loads of Data

The SignalProccessor class acts as a uni-directional pipe to extract data from our CCD’s Pixels. Ideally, in the real world application of such a device it would massively complex, but for this example we’re going to stick with the K.I.S.S. method of programming.

    class SignalProcessor(object):
        def __init__(self):
            self.data = []

        def receive(self, signal):
            self.data.append(signal)

        def read(self):
            return self.data

Visualization

No CCD is complete without a way to parse our detector’s data! Without going too crazy, or making this overly complex, all we’re doing here is breaking down the electrons stored in each pixel into something more useful.

    class Monitor(object):
        def __init__(self, signal_processor):
            self.pixels = signal_processor.read()
            self.pixel_count = len(self.pixels)
            self.electrons = []
            for p in self.pixels:
                for electron in p.electrons:
                    self.electrons.append(electron)
            self.electron_count = len(self.electrons)
            self.plot()

        def plot(self):
            x_axis = [x for x in range(len(self.pixels))]
            y_axis = [len(y.electrons) for y in self.pixels]
            pyplot.xlabel("Pixels")
            pyplot.ylabel("Electrons")
            pyplot.plot(x_axis, y_axis)
            pyplot.show()

Monitor‘s __init__ method reads data from the signal processor (which has already been converted to a one-dimensional list of individual pixels), breaks down the innards into usable attributes, then automatically plots the data without any human intervention. This is the real world after all though, so stay away from automatic plots if you can help it. Seriously, they can be quite annoying.

Detector Time!

Take a deep breath. Now let it out. Good.

    class CCD(object):
        def __init__(self, width, height):
            self.width = width
            self.height = height
            self.pixels = self._generate()
            self.pixels_mask = self._generate()
            self.signal_processor = SignalProcessor()
            # shutter state is False when OPEN
            self.shutter = True

        def _generate(self):
            return deque(
                   deque([ Pixel() for col in range(self.height)]) 
                   for row in range(self.width))

        def _push(self):
            return deque([ Pixel() for col in range(self.width)])

        def display(self, data):
            for row in data:
                for index, obj in enumerate(row):
                    print("{:>10}({:>04})".format(
                        obj.__class__.__name__, len(obj.electrons))),
                print("")

        def shutter_open(self):
            self.shutter = False

        def shutter_close(self):
            self.shutter = True

        def detect(self, photon):
            if self.shutter:
                return False
            random_row = random.choice([x for x in self.pixels])
            pixel = random.choice([x for x in random_row])
            pixel.receive(photon)
            return True

        def _shift(self, register):
            output = register.pop()
            register.append(self._push())
            register.rotate()
            return output

        def _blit(self, source, destination):
            for _ in range(len(source)):
                output = self._shift(source)
                destination.pop()
                destination.append(output)
                destination.rotate()
            return output

        def sendto_mask(self):
            self._blit(self.pixels, self.pixels_mask)

        def sendto_signal_proccessor(self):
            for row in self.pixels_mask:
                for pixel in row:
                    self.signal_processor.receive(pixel)

Scared and confused yet? I was expecting this. Let’s take a look at the CCD class piece by piece so it’s a little easier to grasp.

CCD Breakdown

    class CCD(object):
        def __init__(self, width, height):
            self.width = width
            self.height = height
            self.pixels = self._generate()
            self.pixels_mask = self._generate()
            self.signal_processor = SignalProcessor()
            # shutter state is False when OPEN
            self.shutter = True

Thanks to the way classes encapsulate our code, the eventual end-user only needs to worry about defining the height and width of their CCD array. This really helps remove any guess-work that may be involved when you distribute your code to other people for general use. It’s the perfect interface for implementing the K.I.S.S. method. The CCD __init__ method takes care of the more complex tasks quickly and easily. Notice the reusable code here. The method calls self._generate twice. The first time to initialize the pixels we’ll use for detecting, and a second time to initialize the pixel mask. Why not simply make self.pixel_mask a copy of self.pixels using self.pixels_mask = self.pixels? Because what if generate were given the ability to create random errors in every pixel?

This is called being flexible and portable. Remember, we’re trying to write code once and reuse it indefinitely! Our CCD also needs a peripheral device; the SignalProcessor we defined earlier. There are many possible ways for us to implement CCDs usage of the signal processor, but I wanted to show you that, yes, class objects can initialized within a class without the need for inheritance. Notice we didn’t inherit the signal processor. Our CCD is not derived from a signal processor and even though the signal processor’s features are useful to us, we need to keep them segregated. We will be using it as a dumping point for our data.

    def _generate(self):
        return deque(
               deque([ Pixel() for col in range(self.height)]) 
               for row in range(self.width))

With list comprehensions we create a multidimensional “double-deck” of Pixel objects utilizing the dimensions specified in the __init__ method. _generate has been marked private, because there is no reason for it to be utilized outside of the class.

    def _push(self):
        return deque([ Pixel() for col in range(self.width)])

The _push method returns a single deque of Pixel objects equal to the width of the detector. In other words, we will use this to fill in detector rows that have been popped off the edge of the queue and rotated.

    def display(self, data):
        for row in data:
            for index, obj in enumerate(row):
                print("{:>10}({:>04})".format(
                    obj.__class__.__name__, len(obj.electrons))),
            print("")

While the display method could reside in the Monitor class, Monitor is primarily for graphical representation (e.g. plotting). display allows us to visually comprehend what our detector may look like in real life from a top-down perspective. Why isn’t display accessing the self attribute here? As long as we pass data that has been created with the _generate method we should be okay here. It is possible to incorporate additional error checking (i.e. isinstance()) to ensure the data attribute contains a valid double-deck, but for clarity sake it has been omitted it.

    def shutter_open(self):
        self.shutter = False

    def shutter_close(self):
        self.shutter = True

Now isn’t that just confusing? If the CCD shutter is open then self.shutter is False. Logically speaking, if the shutter of a camera is closed (not receiving light), it is also engaged, therefore it is set to True.

    def detect(self, photon):
        if self.shutter:
            return False
        random_row = random.choice([x for x in self.pixels])
        pixel = random.choice([x for x in random_row])
        pixel.receive(photon)
        return True

This is where all of your hopes and dreams come true. For something so short it sure packs a punch. detect creates a pseudo-random light show for our detector to parse by selecting a random pixel coordinate in the detector matrix and slams it with a Photon object using Pixel‘s receive method. detect is meant to illustrate receiving arguments from the global scope (__main__), interacting with a class’s local data (CCD.pixel), and transferring said data to another class object for processing (CCD.pixel[X].receive).

    def _shift(self, register):
        output = register.pop()
        register.append(self._push())
        register.rotate()
        return output

_shift allows us take the guess work out of moving our data down throughout the matrix of pixels. Our fictitious CCD shifts pixel data into a serial bus one row at a time (in this case an extremely dumbed-down signal processing unit). The register argument is analogous to a row of pixels stored in the CCD matrix. First we record the original pixel row into output and promptly destroy its original row in the deque using pop(). Then we use the private method, _push to literally repopulate the dead row with fresh (blank) pixels. You will see why this helper function is important in just moment.

    def _blit(self, source, destination):
        for _ in range(len(source)):
            output = self._shift(source)
            destination.pop()
            destination.append(output)
            destination.rotate()
        return output

A frame transfer CCD relies on a separate CCD pixel mask to store data in a safe place after exposure has ended, as well as prevent light contamination. _blit emulates this behavior by transferring each source “bit by bit“ into the destination from the top in descending order (creating a 1:1 copy).

    def sendto_mask(self):
        self._blit(self.pixels, self.pixels_mask

Instead of calling constantly calling _blit (and potentially confusing yourself) sendto_mask makes dumping existing pixel data into the full transfer mask much more convenient and easier to use. Wouldn’t you say?

    def sendto_signal_proccessor(self):
        for row in self.pixels_mask:
            for pixel in row:
                self.signal_processor.receive(pixel)

When sendto_signal_processor is called it reads the pixel mask matrix into the signal processor we defined in the __init__ method. No interaction is required by the end-user and it makes for much easier coding in the __main__ portion of your program.

Make Your Parents Proud

Believe it or not the CCD class we just wrote is actually a base class. Why? Because it contains everything you need to reuse the basic implementation of a fully functional CCD. Using CCD as a parent grants us the ability to modify small subsets of its internal structure. For example, what if we needed to simulate particular types failures CCDs are commonly subject to? What if we wanted to illustrate the difference between a digital camera you hold in your hand, compared to a giant ten meter telescope? It’s all about saving keystrokes! Most of your coding is already done. You can focus solely on what matters!

Isn’t magic fun? Since our original CCD design is a Full Transfer CCD, we may as well call it like we see it.

Putting Our Puzzle Together

It is time to put all of your hard work to the test. Let’s go crazy and create a nine by nine pixel array and fire off three-thousand photons just to see what happens.

    if __name__ == "__main__":
        DETECTOR_HEIGHT = 9
        DETECTOR_WIDTH = 9
        TIME = 1
        PHOTONS = 3000

        FT = FrameTransfer(DETECTOR_HEIGHT, DETECTOR_WIDTH)

        for _ in range(TIME):
            FT.shutter_open()
            for _ in range(PHOTONS):
                FT.detect(Photon())
            FT.shutter_close()

        print("Electrons per pixel")
        print("*" * 70)
        FT.display(FT.pixels)

        print("Electrons per pixel (after mask transfer)")
        print("*" * 70)
        FT.sendto_mask()
        FT.display(FT.pixels)

        print("Electrons per pixel (in pixel mask)")
        print("*" * 70)
        FT.display(FT.pixels_mask)

        FT.sendto_signal_proccessor()

        monitor = Monitor(FT.signal_processor)

You really didn’t have to sweat implementing a usable sequence of events, now did you? The resulting code is clear, simple, and easy on the eyes. Can you imagine what the procedural equivalent to all of this would have looked like? A completely convoluted nightmare, that’s what.

Results

CCD Results


Figure 1: Result of a simulated charged-coupled device

Dropping Purity

Has the searing light of a camera’s flashbulb ever left a nasty blotch in your vision? Our eyes have self-healing properties, but most man-made electronic hardware components do not. To make our CCD a bit more realistic we are going to simulate “stuck pixels” and “dead pixels”. How you ask? Easy! With the help of class inheritance adding potential failure cases becomes a trivial task.

    class FrameTransferPure(CCD):
        def __init__(self, width, height, dead_pixels=0, stuck_pixels=0):
            self.MAX_PHOTONS = 200000
            self.dead_pixels = dead_pixels
            self.stuck_pixels = stuck_pixels
            CCD.__init__(self, width, height)

        def is_dead(self, pixel):
            if pixel.electrons:
                return False
            return True

        def is_stuck(self, pixel):
            if pixel.electrons and pixel.active:
                return True
            return False

        def _generate(self):
            between = 10
            luck = 5
            pixels = deque(
               deque([ Pixel() for col in range(self.height)]) 
               for row in range(self.width))

            # Assign stuck pixels
            for _ in range(self.stuck_pixels):
                random_row = random.choice([x for x in pixels])
                chance = random.randint(0,between)
                if chance <= luck:
                    pixel = random.choice([x for x in random_row])
                    pixel.active = True
                    pixel.electrons = [ Photon() for x in range(self.MAX_PHOTONS)]

            # Assign dead pixels
            for _ in range(self.dead_pixels):
                random_row = random.choice([x for x in pixels])
                chance = random.randint(0,between)
                if chance <= luck:
                    pixel = random.choice([x for x in random_row])
                    if not self.is_stuck(pixel):
                        pixel.active = True
                        pixel.electrons = []

            return pixels

Thoughts

Feel free to rewrite the CCD class and its dependent classes in a procedural manner. In fact, it may be the only way for you to answer the age old question: “Are classes worth it?”

Happy programming!