Postpone creation of attributes until needed

Steven D'Aprano steve at REMOVE.THIS.cybersource.com.au
Tue Jun 12 19:24:58 EDT 2007


On Tue, 12 Jun 2007 08:53:11 -0700, Frank Millman wrote:

>> Since, as far as I can tell, there is no minimum time between creating the
>> instance at (1) and trying to access instance.y at (2), there is no
>> minimum time between (1) and calling compute() at (4), except for the
>> execution time of the steps between them. So why not just make compute()
>> the very last thing that __init__ does?
>>
> 
> I wrote a long reply to this about an hour ago, but Google Groups
> seems to have eaten it. I hope I can remember what I wrote.
> 
> This is more like what I am doing -
> 
> (t=table, c=column, p=pseudo column)
> 
> (1) the caller initializes table t1 and columns c1-c10
> (2) the caller initializes table t2 and columns c11-c20
> (3) t2.__init__() creates a link to t1, and updates t1 with a link to
> t2
> (4) t2.__init__() creates a link from c12 to c3, and updates c3 with a
> link to c12
> (5) t2.__init__() creates pseudo column p1 on table t1, creates a link
> from c14 to p1, updates p1 with a link to c14
> 
> This all works well, and has been working for some time.

Ah, if I had ever read that there were two instances involved, I hadn't
noticed. Sorry!



> You can already see a difference between your scenario and mine.
> Various attributes are set up *after* the original __init__() method
> has completed.

By "original" I guess you mean t1.

I also assume that both t1 and t2 are instances of the same Table class.

Here are some thoughts:

- Don't ask the caller to initiate t1 and t2 (steps 1 and 2 above).
Instead, write a function which does all the initiation (steps 1 through
5) and returns a tuple (t1, t2). That way, your initiate function
("make_tables" perhaps?) can do all the setup needed before the caller
starts using either t1 or t2.


- How does t2 know about t1? As a named global variable? If so, there is
probably a better way, maybe something like this:

class Table(object):
    def __init__(self, start_column, end_column, sibling=None):
        self.columns = []
        for i in range(start_column, end_column):
            self.columns.append(get_a_column(i))
        self.sibling = sibling
        if sibling is not None:
            # I am the sibling of my sibling
            sibling.sibling = self 
            # make links between columns
            self.columns[12].make_link(sibling.columns[3])
            sibling.columns[3].make_link(self.columns[12])
            # create pseudo-columns (whatever they are!)
            sibling.p1 = contents_of_pseudo_column()
            self.columns[14].make_link(sibling.p1)
            sibling.p1.make_link(self.columns[14])

Now you call them like this:

t1 = Table(1, 11)
t2 = Table(11, 21, t1)
# and now everything is initiated and ready to use...


- If both tables t1 and t2 need to exist, it should be an error to use t1
without creating t2, or vice versa. An easy check for that will be:

    if self.sibling is None: raise TableError('no sibling')


- Most importantly... I hope you are getting some benefit from all this
extra work needed to support splitting the columns across two instances.



> I have now added a complication.
> 
> I want to create a t3 instance, with columns c21-c30, and I want to
> create a pseudo column p2 on table t2, exactly as I did in steps 2 to
> 5 above. I also want to change step 5 so that instead of linking p1 on
> table 1 to c14 on table 2, I link it to p2 on table 2. However, at
> that point, p2 does not exist.


Perhaps each table needs two siblings, a left and a right. Should it be
circular? As in t1 <-> t2 <-> t3 <-> t1. Or perhaps your requirement is
that each table must have _at least_ one sibling, but not necessarily two.

Either way, changing the magic constants 3, 12 and 14 above into either
arguments or calculated values should allow you to make the code general
enough to have any number of tables.

Another suggestion: factor out the "make these two tables siblings" bits
out of the __init__, so you can do this:

def create_tables():
    t1 = Table(1, 11)
    t2 = Table(11, 21)
    t3 = Table(21, 31)
    make_sibling(t1, t2)
    make_sibling(t2, t3)
    return (t1, t2, t3)

t1, t2, t3 = create_Tables()

As before, it is an error to access the data in a half-initiated table.
But since the caller is not expected to create table instances themselves,
but only call the create_table() function, that is never a problem.



-- 
Steven.




More information about the Python-list mailing list