Using class attributes

L

Leo Breebaart

I have a base class Foo with a number of derived classes FooA,
FooB, FooC, etc. Each of these derived classes needs to read
(upon initialisation) text from an associated template file
FooA.tmpl, FooB.tmpl, FooC.tmpl, etc.

I can derive the template filename string for each instance by
doing something like this in the base class (and then not
forgetting to call super() in the __init__() of each derived
class):

class Foo(object):

def __init__(self):
self.template_filename = "%s.tmpl" % self.__class__.__name__
self.template_body = read_body_from(self.template_filename)

But, since this information is the same for every instance of
each derived class, I was wondering if there was a way to achieve
the same thing outside of the __init__ function, and just have
these assignments be done as a class attribute (i.e. so that I
can refer to FooA.template_body, etc.)

I can of course always just hardcode the template filenames in
each derived class, but I am just curious if it can be automated
through some form of introspection.
 
C

Chris Rebert

I have a base class Foo with a number of derived classes FooA,
FooB, FooC, etc. Each of these derived classes needs to read
(upon initialisation) text from an associated template file
FooA.tmpl, FooB.tmpl, FooC.tmpl, etc.

I can derive the template filename string for each instance by
doing something like this in the base class (and then not
forgetting to call super() in the __init__() of each derived
class):

 class Foo(object):

     def __init__(self):
         self.template_filename = "%s.tmpl" % self.__class__.__name__
         self.template_body = read_body_from(self.template_filename)

But, since this information is the same for every instance of
each derived class, I was wondering if there was a way to achieve
the same thing outside of the __init__ function, and just have
these assignments be done as a class attribute (i.e. so that I
can refer to FooA.template_body, etc.)

I can of course always just hardcode the template filenames in
each derived class, but I am just curious if it can be automated
through some form of introspection.

Metaclasses to the rescue!:

class WithTemplateAttrs(type):
def __new__(cls, name, bases, dct):
klass = type.__new__(cls, name, bases, dct)
klass.template_filename = "%s.tmpl" % name
klass.template_body = read_body_from(klass.template_filename)
return klass

class Foo(object):
__metaclass__ = WithTemplateAttrs
#rest of class body here

Now just have FooA, FooB, etc. subclass Foo as before. They'll
automatically get the attributes generated.

Cheers,
Chris
 
L

Leo Breebaart

Chris Rebert said:
I have a base class Foo with a number of derived classes FooA,
FooB, FooC, etc. Each of these derived classes needs to read
(upon initialisation) text from an associated template file
FooA.tmpl, FooB.tmpl, FooC.tmpl, etc.
[...]
But, since this information is the same for every instance of
each derived class, I was wondering if there was a way to achieve
the same thing outside of the __init__ function, and just have
these assignments be done as a class attribute (i.e. so that I
can refer to FooA.template_body, etc.)

Metaclasses to the rescue!:

class WithTemplateAttrs(type):
def __new__(cls, name, bases, dct):
klass =3D type.__new__(cls, name, bases, dct)
klass.template_filename =3D "%s.tmpl" % name
klass.template_body =3D read_body_from(klass.template_filename)
return klass

class Foo(object):
__metaclass__ =3D WithTemplateAttrs
#rest of class body here

Now just have FooA, FooB, etc. subclass Foo as before. They'll
automatically get the attributes generated.

Thanks for the feedback! I am thrilled that an actual real-life
issue I'm having may be resolvable by metaclasses (which so far
I've only admired from afar but never considered relevant to my
day-to-day work), but unfortunately I'm still struggling to get
this to work.

If I add your code, what happens is that the Foo class will try
to read "Foo.tmpl", which does not exist -- it is only the
derived classes FooA etc, that need to execute this code, not Foo
itself.

And actually that makes sense -- I think my problem was not too
clearly thought out to begin with. Of course it's not possible to
associate a single template_body with the Foo class, it will be a
different template for each derived class. So at best I need to
associate your metaclass with each derived class, but at that
point I might as well just read the template in the __init__()
method with __class__.__name__, and use lazy evaluation / caching
to avoid doing the actual file-reading work more than once.

I think.
 
A

Arnaud Delobelle

Leo Breebaart said:
Chris Rebert said:
I have a base class Foo with a number of derived classes FooA,
FooB, FooC, etc. Each of these derived classes needs to read
(upon initialisation) text from an associated template file
FooA.tmpl, FooB.tmpl, FooC.tmpl, etc.
[...]
But, since this information is the same for every instance of
each derived class, I was wondering if there was a way to achieve
the same thing outside of the __init__ function, and just have
these assignments be done as a class attribute (i.e. so that I
can refer to FooA.template_body, etc.)

Metaclasses to the rescue!:

class WithTemplateAttrs(type):
def __new__(cls, name, bases, dct):
klass =3D type.__new__(cls, name, bases, dct)
klass.template_filename =3D "%s.tmpl" % name
klass.template_body =3D read_body_from(klass.template_filename)
return klass

class Foo(object):
__metaclass__ =3D WithTemplateAttrs
#rest of class body here

Now just have FooA, FooB, etc. subclass Foo as before. They'll
automatically get the attributes generated.

Thanks for the feedback! I am thrilled that an actual real-life
issue I'm having may be resolvable by metaclasses (which so far
I've only admired from afar but never considered relevant to my
day-to-day work), but unfortunately I'm still struggling to get
this to work.

If I add your code, what happens is that the Foo class will try
to read "Foo.tmpl", which does not exist -- it is only the
derived classes FooA etc, that need to execute this code, not Foo
itself.

And actually that makes sense -- I think my problem was not too
clearly thought out to begin with. Of course it's not possible to
associate a single template_body with the Foo class, it will be a
different template for each derived class. So at best I need to
associate your metaclass with each derived class, but at that
point I might as well just read the template in the __init__()
method with __class__.__name__, and use lazy evaluation / caching
to avoid doing the actual file-reading work more than once.

I think.

Descriptors to the rescue :)

def read_body_from(filename):
print "** loading content **"
return "<content of '%s'>" % filename

# This is a kind of class property
class TemplateFilename(object):
def __get__(self, obj, cls):
return "%s.tmpl" % cls.__name__

# And this is a kind of cached class property
class TemplateBody(object):
def __get__(self, obj, cls):
try:
return cls._body
except AttributeError:
cls._body = read_body_from(cls.template_filename)
return cls._body

class Foo(object):
template_filename = TemplateFilename()
template_body = TemplateBody()

class FooA(Foo):
pass

class FooB(Foo):
pass

# In action:** loading content **
** loading content **
"<content of 'FooB.tmpl'>"

HTH
 
J

Jean-Michel Pichavant

Arnaud said:
I have a base class Foo with a number of derived classes FooA,
FooB, FooC, etc. Each of these derived classes needs to read
(upon initialisation) text from an associated template file
FooA.tmpl, FooB.tmpl, FooC.tmpl, etc.
[...]
But, since this information is the same for every instance of
each derived class, I was wondering if there was a way to achieve
the same thing outside of the __init__ function, and just have
these assignments be done as a class attribute (i.e. so that I
can refer to FooA.template_body, etc.)

Metaclasses to the rescue!:

class WithTemplateAttrs(type):
def __new__(cls, name, bases, dct):
klass =3D type.__new__(cls, name, bases, dct)
klass.template_filename =3D "%s.tmpl" % name
klass.template_body =3D read_body_from(klass.template_filename)
return klass

class Foo(object):
__metaclass__ =3D WithTemplateAttrs
#rest of class body here

Now just have FooA, FooB, etc. subclass Foo as before. They'll
automatically get the attributes generated.
Thanks for the feedback! I am thrilled that an actual real-life
issue I'm having may be resolvable by metaclasses (which so far
I've only admired from afar but never considered relevant to my
day-to-day work), but unfortunately I'm still struggling to get
this to work.

If I add your code, what happens is that the Foo class will try
to read "Foo.tmpl", which does not exist -- it is only the
derived classes FooA etc, that need to execute this code, not Foo
itself.

And actually that makes sense -- I think my problem was not too
clearly thought out to begin with. Of course it's not possible to
associate a single template_body with the Foo class, it will be a
different template for each derived class. So at best I need to
associate your metaclass with each derived class, but at that
point I might as well just read the template in the __init__()
method with __class__.__name__, and use lazy evaluation / caching
to avoid doing the actual file-reading work more than once.

I think.

Descriptors to the rescue :)

def read_body_from(filename):
print "** loading content **"
return "<content of '%s'>" % filename

# This is a kind of class property
class TemplateFilename(object):
def __get__(self, obj, cls):
return "%s.tmpl" % cls.__name__

# And this is a kind of cached class property
class TemplateBody(object):
def __get__(self, obj, cls):
try:
return cls._body
except AttributeError:
cls._body = read_body_from(cls.template_filename)
return cls._body

class Foo(object):
template_filename = TemplateFilename()
template_body = TemplateBody()

class FooA(Foo):
pass

class FooB(Foo):
pass

# In action:
'FooA.tmpl'

'FooB.tmpl'
** loading content **
'FooB.tmpl'
** loading content **
"<content of 'FooB.tmpl'>"

HTH
While all these proposals are giving interesting technical anwsers to
the OP problem, I think that the OP original code is still the best (_imo_).

class Foo(object):
def __init__(self):
self.template_filename = "%s.tmpl" % self.__class__.__name__
self.template_body = read_body_from(self.template_filename)


That is true the same attributes are defined for every instance, but,
honestly, who cares ? (unless you target the best class design 2010
price :) )
This solution beat the others in terms of simplicity and readability.

It's still usefull to train your mind with decorators and metaclasses
though, they can save your life eventually.

JM
 
A

Arnaud Delobelle

Jean-Michel Pichavant said:
While all these proposals are giving interesting technical anwsers to
the OP problem, I think that the OP original code is still the best
(_imo_).

class Foo(object):
def __init__(self):
self.template_filename = "%s.tmpl" % self.__class__.__name__
self.template_body = read_body_from(self.template_filename)


That is true the same attributes are defined for every instance, but,
honestly, who cares ? (unless you target the best class design 2010
price :) )

You might care if you have many instances but few classes, and the size
of template_body is significant. Although with this design it is
possible to cache template bodies within the 'read_body_from' function
if necessary.

Also I was under the impression that the OP wanted these attribute to be
accessible from the class object as well.
 
T

Terry Reedy

Chris Rebert said:
I have a base class Foo with a number of derived classes FooA,
FooB, FooC, etc. Each of these derived classes needs to read
(upon initialisation) text from an associated template file
FooA.tmpl, FooB.tmpl, FooC.tmpl, etc.
[...]
But, since this information is the same for every instance of
each derived class, I was wondering if there was a way to achieve
the same thing outside of the __init__ function, and just have
these assignments be done as a class attribute (i.e. so that I
can refer to FooA.template_body, etc.)

Metaclasses to the rescue!:

class WithTemplateAttrs(type):
def __new__(cls, name, bases, dct):
klass =3D type.__new__(cls, name, bases, dct)
klass.template_filename =3D "%s.tmpl" % name
klass.template_body =3D read_body_from(klass.template_filename)
return klass

class Foo(object):
__metaclass__ =3D WithTemplateAttrs
#rest of class body here

Now just have FooA, FooB, etc. subclass Foo as before. They'll
automatically get the attributes generated.

Thanks for the feedback! I am thrilled that an actual real-life
issue I'm having may be resolvable by metaclasses (which so far
I've only admired from afar but never considered relevant to my
day-to-day work),

I thought this a really nice example of metaclass use too.
> but unfortunately I'm still struggling to get
this to work.

If I add your code, what happens is that the Foo class will try
to read "Foo.tmpl", which does not exist -- it is only the
derived classes FooA etc, that need to execute this code, not Foo
itself.

My simpleminded solution to the above would be to create a dummy
Foo.tmpl file, so it does exist. Or, in __new__ above, conditionalize
the fetch: if name != 'Foo': ...

Terry Jan Reedy
 
L

Leo Breebaart

Arnaud Delobelle said:
Descriptors to the rescue :)

def read_body_from(filename):
print "** loading content **"
return "<content of '%s'>" % filename

# This is a kind of class property
class TemplateFilename(object):
def __get__(self, obj, cls):
return "%s.tmpl" % cls.__name__

# And this is a kind of cached class property
class TemplateBody(object):
def __get__(self, obj, cls):
try:
return cls._body
except AttributeError:
cls._body = read_body_from(cls.template_filename)
return cls._body

class Foo(object):
template_filename = TemplateFilename()
template_body = TemplateBody()

class FooA(Foo):
pass

class FooB(Foo):
pass

Very enlightening, thanks!

By the way, I completely agree with the other posters in this
thread that intricate solutions such as this are likely to be
overkill, especially since at this point I have no idea if the
inefficiency of reading those templates multiple times would at
all matter (frankly, I'd doubt it). But it's certainly been
educational to learn about these techniques.

One observation: if I implement the descriptor solution as given
above, the code works perfectly, but running the code through
pychecker now causes an error, because that again causes an
attempt to read from the non-existant base class template file
"Foo.tmpl"...
 
A

Arnaud Delobelle

Leo Breebaart said:
Very enlightening, thanks!

By the way, I completely agree with the other posters in this
thread that intricate solutions such as this are likely to be
overkill, especially since at this point I have no idea if the
inefficiency of reading those templates multiple times would at
all matter (frankly, I'd doubt it). But it's certainly been
educational to learn about these techniques.

Writing programs is a great way to keep learning :)
One observation: if I implement the descriptor solution as given
above, the code works perfectly, but running the code through
pychecker now causes an error, because that again causes an
attempt to read from the non-existant base class template file
"Foo.tmpl"...

As someone said before, you could just provide a dummy Foo.tmpl file.
 
L

Leo Breebaart

It's not just pychecker that doesn't like the descriptor
solution: pydoc now also crashes with the same IOError. :)

As someone said before, you could just provide a dummy Foo.tmpl
file.

A pragmatic solution, but one that smells bad to me. All this
started because I didn't want my program to read files more than
once, but I also don't want it to read files it doesn't have to
read (and that don't even need to exist) in the first place!

I'll just go back to the original instance-based lazy evaluation
and caching solution now -- I never really had a big problem with
that.

My thanks again to all of you for helping me out with this.
 

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,770
Messages
2,569,584
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top