instance + classmethod question

L

Laszlo Zsolt Nagy

Hello,

Is it possible to tell, which instance was used to call the classmethod
that is currently running?

Background: I have a class called DatabaseConnection and it has a
classmethod called process_create_tables. This method should create some
database tables defined by a database definition object. The
DatabaseConnection has many descendants, for example
PostgreSQLConnection. Descendants know how to create tables in a given
RDBMS type. I also use subclasses of the 'SQLProcessor' class, that
processes SQL commands in different ways (print to stdout, write to
file, execute directly in the database etc.) I would like to use the
process_create_tables classmethod as is, because sometimes I only need
to save a SQL script. However, I also want to use the same classmethod
to create tables directly into an existing database. That database is
presented as a DatabaseConnection instance. In that case, I only want to
create the tables that do not exists yet. Examples:

processor = SQLProcessors.StdOutProcessor() # Print to stdout
PostgreSQLConnection.process_create_tables(processor,dbdef) # This
sould create all tables, using the processor

processor = SQLProcessors.DirectProcessor(conn) # Execute directly
conn.process_create_tables(processor,dbdef) # This should create
non-existing tables only, using the processor

Is this possible? Maybe there is a better way to achieve this, I'm not
sure. I was thinking about this construct:

@classsmethod
def process_create_tables(cls,processor,dbdef,conn=None)

and then calling it as

conn.process_create_tables(processor,dbdef,conn)

but this looks very ugly to me. It would be much easier if I could tell
which instance (if any) was used to call the classmethod.

Thanks,

Les
 
L

Laszlo Zsolt Nagy

Hello Steven,

I already implemented this using the form

@classmethod
def methodname(cls,other_params,self=None)

but your example code looks so neat! This is exactly what I needed. :)
In my methods, most code is about string manipulation and calling other
classmethods.
There are only a few places where I can use an instance, but it is not
required.
I would like to reuse as most code as possible, so I do not want to
create two different
methods. That would result in duplicating code. Now the only problem is
how I name this.
It is not a classmethod, but it is also not a normal method. All right,
it is a
"ClassOrInstanceMethod". Amazing! Probably Python is the only language
that is
flexible enough to do this. :)

Thanks again!

Laszlo
 
S

Steven Bethard

Laszlo said:
Hello,

Is it possible to tell, which instance was used to call the classmethod
that is currently running?
[snip]

processor = SQLProcessors.StdOutProcessor() # Print to stdout
PostgreSQLConnection.process_create_tables(processor,dbdef) # This
sould create all tables, using the processor

processor = SQLProcessors.DirectProcessor(conn) # Execute directly
conn.process_create_tables(processor,dbdef) # This should create
non-existing tables only, using the processor

Is this possible?

It looks like you want a method that accepts either a class or an
instance. I would typically create two methods, one for the class-based
table-creating, and one for the instance-based table-creating. However,
if you *have* to do it this way, you can introduce your own descriptor
which should give you the behavior you want::
.... def __init__(self, func):
.... self.func = func
.... self.classfunc = classmethod(func)
.... def __get__(self, obj, objtype=None):
.... func = obj is None and self.classfunc or self.func
.... return func.__get__(obj, objtype)
........ @ClassOrInstanceMethod
.... def f(*args):
.... print args
....(<__main__.C object at 0x00E73D90>,)

Basically, if the descriptor is called from a type (and thus ``obj is
None``), we return a bound classmethod, and if the descriptor is called
from an instance, we return a bound instance method. Of course this now
means you should write your code something like::

@ClassOrInstanceMethod
def process_create_tables(cls_or_self, processor, dbdef):
...

whose "cls_or_self" parameter gives me a bad code smell. YMMV.

STeVe
 
S

Steven Bethard

Laszlo said:
In my methods, most code is about string manipulation and calling other
classmethods.
There are only a few places where I can use an instance, but it is not
required.
I would like to reuse as most code as possible, so I do not want to
create two different
methods. That would result in duplicating code.

I would tend to do this by creating a wrapper method for the instance
that did the appropriate stuff for the instance, and then called the
classmethod, e.g.:

class C(object):
...
@classmethod
def do_stuff(cls, *args):
...
def do_instance_stuff(self, *args):
# instance stuff
...
self.do_stuff(*args)
# more instance stuff
...

But it does require some factoring of the classmethod so that it makes
sense to call it in this manner.

STeVe
 
M

Mike Meyer

Laszlo Zsolt Nagy said:
Is it possible to tell, which instance was used to call the
classmethod that is currently running?

Ok, I read through what got to my nntp server, and I'm still
completely confused.

A class method isn't necessarilry called by an instance. That's why
it's a class method. What should happen in that case?

You provided an example where you passed self as an optional
argument. If it's going to have self, shouldn't it be an instance
method?

I think I agree with Steven - you should use two methods. You deal
with the issue of duplicated code by pulling the code that would be
duplicated out into private methods. This would be a straightforward
refactoring problem.

<mike
 
L

Laszlo Zsolt Nagy

Mike said:
Ok, I read through what got to my nntp server, and I'm still
completely confused.

A class method isn't necessarilry called by an instance. That's why
it's a class method. What should happen in that case?
Here is the answer (an example):

@ClassOrInstanceMethod
def process_create_table(cls_or_self,tabledef,processor):
"""Process the CREATE TABLE command.

@param tabledef: a L{TableDefinition} instance.
@param processor: a L{SQLProcessor} instance."""
hname = cls_or_self.hashident(tabledef.name)
if (isinstance(cls_or_self,type)) or (not
cls_or_self.istableexists(hname)):
processor.addline("create table %s \n ("% hname)
for field in tabledef.fields:
if not (field() is None):

cls_or_self.process_create_table_field(field(),processor)
processor.addline(",")
processor.truncate_last_comma()
processor.addline("")
processor.addline(")")
cls_or_self.addtablespaceclause(tabledef,processor)
processor.processbuffer()

So if the method was called with an instance, it will check if the table
exists and create the table only if it did not exist before.
But if the method was called with a class, it will create the table anyway.

The above method is just a short example. I have many methods for
creating sequences, triggers, constraings etc.
The full pattern is:

def process_XXXXXXX(cls_or_self,defobject,processor):
<longer code>
<a condition, depending on the class or the instance>
<longer code>
<another condition, depending on the class or the instance>
<longer code>

There are two reasons why I do not want to create two methods (one
instance and one class method).

1. If you look the above pattern, it is clear that the method does the
same thing, just there are some conditions when I call it with an
instance. I do not want to call "process_create_table_with_class" and
"process_create_table_with_instance", because the name of the method
should reflect what it does primarily. (BTW, I also looked at
multimethods, but they are not exactly for this kind of problem.)

2. The pattern above makes it clear that I just can't easily split the
method into elementary parts. Steven wrote this pattern:
class C(object):
...
@classmethod
def do_stuff(cls, *args):
...
def do_instance_stuff(self, *args):
# instance stuff
...
self.do_stuff(*args)
# more instance stuff
But I cannot do this, because primarily I do class stuff, and in some
cases, I can make use of an instance (but do not require it).
Comments welcome

Les
 
M

Mike Meyer

Laszlo Zsolt Nagy said:
Here is the answer (an example):

@ClassOrInstanceMethod
def process_create_table(cls_or_self,tabledef,processor):
"""Process the CREATE TABLE command.

@param tabledef: a L{TableDefinition} instance.
@param processor: a L{SQLProcessor} instance."""
hname = cls_or_self.hashident(tabledef.name)
if (isinstance(cls_or_self,type)) or (not
cls_or_self.istableexists(hname)):
processor.addline("create table %s \n ("% hname)
for field in tabledef.fields:
if not (field() is None):
cls_or_self.process_create_table_field(field(),processor)
processor.addline(",")
processor.truncate_last_comma()
processor.addline("")
processor.addline(")")
cls_or_self.addtablespaceclause(tabledef,processor)
processor.processbuffer()

I assume that hashident, istableexists, process_create_table_field and
addtablespaceclause all do this screwy ClassOrInstanceMethod thing?
There are two reasons why I do not want to create two methods (one
instance and one class method).

1. If you look the above pattern, it is clear that the method does the
same thing, just there are some conditions when I call it with an
instance. I do not want to call "process_create_table_with_class" and
"process_create_table_with_instance", because the name of the method
should reflect what it does primarily. (BTW, I also looked at
multimethods, but they are not exactly for this kind of problem.)

Well, I'd call them process_create_table_for_instance and
create_table_for_class, but that's just me.
2. The pattern above makes it clear that I just can't easily split the
method into elementary parts. Steven wrote this pattern:

I didn't say it would be easy - just well understood, and on seeing
this, maybe not that. On the other hand, being hard doesn't mean it's
not worth doing.
But I cannot do this, because primarily I do class stuff, and in some
cases, I can make use of an instance (but do not require it).
Comments welcome

Proposing an alternative design is pretty much impossible, because I
don't know how the class hierarchy fits together. If there isn't much
of a hierarchy, maybe you'd be better off with an ADT model than an
object model?

<mike
 

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,764
Messages
2,569,567
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top