Postpone creation of attributes until needed

F

Frank Millman

On Jun 12, 1:46 am, Steven D'Aprano
Because, as I have tried to explain elsewhere (probably not very
clearly), not all the information required to perform compute() is
available at __init__ time.

I'm sorry, but this explanation doesn't make sense to me.

Currently, something like this happens:

(1) the caller initializes an instance
=> instance.x = some known value
=> instance.y is undefined
(2) the caller tries to retrieve instance.y
(3) which calls instance.__getattr__('y')
(4) which calls instance.compute()
=> which forces the necessary information to be available
=> instance.__dict__['y'] = some value
(5) finally returns a value for instance.y

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.

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

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.

I hope that describes the problem a bit better. I'm going to leave it
at that for now, as I am getting a glimmer of an idea as to how I can
refactor this. I will tackle it again in the morning when I am feeling
fresh, and will report back then.

Frank
 
S

Steven D'Aprano

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.
 
F

Frank Millman

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

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

No problem - I really appreciate your input.

I snipped the rest of your post, as I have figured out a way to make
my problem go away, without changing my code radically.

It was a 'sequence of events' problem.

1. create table a
2. create table b, create pseudo column on table a, link to column on
table b
3. create table c, create pseudo column on table b, link to column on
table c

This works most times, but if, in step 2, I want to link the pseudo
column in table a to the pseudo column in table b, it fails, as the
pseudo column in table b does not get created until step 3.

Now, if I try to link to a column that does not exist, I set a flag
and carry on. When I go to the next step, after creating the pseudo
column, I check if the flag exists, and if so I go back and create the
link that it tried to create in the first place. It is not very
pretty, but it has the big advantage that, by the time all the tables
are opened, all the links are correctly set up, and I never have the
problem of trying to access non-existent attributes.

I will try to explain what benefit all this gives me.

Assume a Customers table and an Invoices table. Customers has a
primary key which is a generated 'next number', and an alternate key
which is the Customer Code. Users will never see the primary key, only
the alternate key.

Invoices has a foreign key CustomerId, which references the primary
key of Customers. However, when capturing an invoice, the user wants
to enter the code, not the primary key.

It is not difficult to program for this, but it is tedious to do it on
every data-entry form. Therefore, in my framework, I set up a pseudo
column on Invoices called CustomerCode, which the application
programmer can use as if it is a real column. Behind the scenes, I
automatically use that to check that it is a valid code, and populate
the real column with the primary key. This has been working for some
time, and really simplifies the 'business logic' side of things by
abstracting a common idiom which would otherwise have to be coded
explicitly every time.

Now I have added a complication. I have decided to implement the idea
of using a single table to store details of all parties with whom we
have a relationship, such as Customers, Suppliers, Agents, etc,
instead of separate tables each with their own Code, Name, and Address
details.

Therefore I now have the following- a Parties table with a 'next
number' primary key and a PartyCode alternate key, a Customers table
with a 'next number' primary key and a PartyId foreign key reference
to the Parties table, and an Invoices table with a CustomerId foreign
key reference to the Customers table.

Now when capturing an invoice, the user enters a Code, then the
program must check that the code exists on Parties, retrieve the
primary key, then check that it exists on Customers, retrieve that
primary key, then store the result on Invoices, with appropriate error
messages if any step fails. I have successfully abstracted all of
that, so all that complication is removed from the application.

Hope that makes sense.

Thanks very much for all your attempts to help me, Steven. You have
succeeded in getting me to think properly about my problem and come up
with a much cleaner solution. I really appreciate it.

Frank
 
S

Steven D'Aprano

Thanks very much for all your attempts to help me, Steven. You have
succeeded in getting me to think properly about my problem and come up
with a much cleaner solution. I really appreciate it.

Glad to be of help.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,600
Members
45,179
Latest member
pkhumanis73
Top