[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.python

Using class attributes

Leo Breebaart

2/15/2010 6:30:00 PM


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.
--
Leo Breebaart <leo@lspace.org>
8 Answers

Chris Rebert

2/15/2010 7:54:00 PM

0

On Mon, Feb 15, 2010 at 10:29 AM, Leo Breebaart <leo@lspace.org> wrote:
> 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
--
http://blog.re...

Leo Breebaart

2/16/2010 1:57:00 PM

0

Chris Rebert <clp2@rebertia.com> writes:

> On Mon, Feb 15, 2010 at 10:29 AM, Leo Breebaart <leo@lspace.org> wrote:
>
> > 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.

--
Leo Breebaart <leo@lspace.org>

Arnaud Delobelle

2/16/2010 2:39:00 PM

0

Leo Breebaart <leo@lspace.org> writes:

> Chris Rebert <clp2@rebertia.com> writes:
>
>> On Mon, Feb 15, 2010 at 10:29 AM, Leo Breebaart <leo@lspace.org> wrote:
>>
>> > 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.template_filename
'FooA.tmpl'
>>> FooB.template_filename
'FooB.tmpl'
>>> FooA.template_body
** loading content **
"<content of 'FooA.tmpl'>"
>>> FooA.template_body
"<content of 'FooA.tmpl'>"
>>> foob = FooB()
>>> foob.template_filename
'FooB.tmpl'
>>> foob.template_body
** loading content **
"<content of 'FooB.tmpl'>"
>>> foob.template_body
"<content of 'FooB.tmpl'>"

HTH

--
Arnaud

Arnaud Delobelle

2/16/2010 3:16:00 PM

0

Jean-Michel Pichavant <jeanmichel@sequans.com> writes:
[...]
> 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.

--
Arnaud

Terry Reedy

2/16/2010 5:00:00 PM

0

On 2/16/2010 8:56 AM, Leo Breebaart wrote:
> Chris Rebert<clp2@rebertia.com> writes:
>
>> On Mon, Feb 15, 2010 at 10:29 AM, Leo Breebaart<leo@lspace.org> wrote:
>>
>>> 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

Leo Breebaart

2/18/2010 10:47:00 AM

0

Arnaud Delobelle <arnodel@googlemail.com> writes:

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

--
Leo Breebaart <leo@lspace.org>

Arnaud Delobelle

2/18/2010 12:39:00 PM

0

Leo Breebaart <leo@lspace.org> writes:

> Arnaud Delobelle <arnodel@googlemail.com> writes:
>
>> 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.

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.

--
Arnaud

Leo Breebaart

2/18/2010 1:28:00 PM

0

Arnaud Delobelle <arnodel@googlemail.com> writes:

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

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.

--
Leo Breebaart <leo@lspace.org>