1

Here, "OK" means of course AYOR (at your own risk), but otherwise there are no foreseeable problems if one avoids obvious clashes with existing attributes names.

Skyfield objects - especially planets - typically have a limited number of attributes. I am frequently writing short scripts to extract numerical data that I save as text and use later. These are essentially 'disposable' scripts as I rarely use them more than once or twice and never ever share them.

When I write more durable code, I certainly create my own container objects.

My Question: It seems to be working well for me, so within this specific context, is there anything that can go wrong besides a conflict in attribute names?

from skyfield.api import load
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

eph   = load('de421.bsp')
earth = eph['earth']
sun   = eph['sun']

ts = load.timescale()

t  = ts.utc(2016, 1, np.linspace(0, 366, 1000))

# SLOPPY WAY: just add them directly
earth.pos  = earth.at(t).position.km
sun.pos    = sun.at(t).position.km
earth.r    = np.sqrt(((earth.pos-sun.pos)**2).sum(axis=0))
earth.peri = earth.r.min()
earth.apo  = earth.r.max()

print earth.peri, earth.apo, earth.pos.shape


# BETTER WAY: tedious but more cautious
uhoh  = dict()
ep    = earth.at(t).position.km
sp    = sun.at(t).position.km
r     = np.sqrt(((ep-sp)**2).sum(axis=0))
uhoh['pos']  = ep
uhoh['r']    = r
uhoh['peri'] = r.min()
uhoh['apo']  = r.max()
earth.uhoh   = uhoh

print earth.uhoh['peri'], earth.uhoh['apo'], earth.uhoh['pos'].shape

returns:

147100175.99 152103762.948 (3, 1000)
147100175.99 152103762.948 (3, 1000)
uhoh
  • 3,713
  • 6
  • 42
  • 95
  • It's almost always a bad idea to add arbitrary elements to an object unless the class was specifically designed for it. This could break in terrible ways later. Why do you want to do this? If you must, a better way would be to create your own container class that wraps the skyfield object, and includes any additional attributes you want to carry around that are associated withit. – Iguananaut May 06 '16 at 11:09
  • 1
    @uhoh That's a little bit better, as it will help to avoid name clashes (you're only technically adding one attribute). But I still agree with @Iguananaut; create a container class that accepts a skyfield object in its constructor and calculates and assigns whatever values you need to instance variables. – pzp May 06 '16 at 11:36
  • @pzp then we are all in agreement about that, thanks! But *if* I do this, are there other things that can go wrong *besides* a conflict in attribute name? Something a little bit deeper in python that I'm not aware of? That's my question here. I'm an advocate of good programming, but I still have a morbid curiosity for what things can go wrong. – uhoh May 06 '16 at 11:40
  • 1
    With most Python objects, nothing. Assignment of non-existent attributes is just a shortcut for `obj.__dict__[attr] = val`, unless the object is using slots, or has overridden `__setattr__`. – Iguananaut May 06 '16 at 12:00
  • A [colorful discussion](http://stackoverflow.com/a/472024/3904031) of slots – uhoh May 06 '16 at 12:06

1 Answers1

1

This is indeed a pattern that one runs across occasionally in informal Python code. The other major thing that can go wrong, besides a future attribute name conflict, is that the library author, under the instigation of people who want to more efficiently create millions of objects, adds a __slots__ specification and you start getting an error when you try to add additional attributes.

The defense against __slots__, should it arise, is to use your own subclass of the class you want to use. If a subclass fails to specify __slots__ too — saying at least __slots__ = [] even if it has no more attributes it wants to add — then the subclass's instances are wide open and can have any attributes whatsoever, so you can always create a subclass of your own that "unlocks" the instances and lets them have any attributes.

Note that objects like earth are eligible to be dictionary keys, so if you have a piece of information that you want to associate with every planet, you could say something like:

positions = {}
positions[earth] = ...

That is one common pattern when you have extra information to remember about each of a set of objects.

Brandon Rhodes
  • 83,755
  • 16
  • 106
  • 147