[Edu-sig] poking around in Py3k (recycling old algebra)

kirby urner kirby.urner at gmail.com
Thu May 28 17:22:15 CEST 2009

On Wed, May 27, 2009 at 11:45 PM, kirby urner<kirby.urner at gmail.com> wrote:

<< SNIP >>

> Hey, I was just learning from David Beazley on Safari that __repr__ is
> generally supposed to emit a string that'll eval right back to the
> object represented.  So if I have Modulo type and go k = Modulo(10)
> then my __repr__ should maybe just emit 'Modulo(10)'.  __str__ is for
> something prettier maybe (adding makeup).

Speaking of David Beazley, not all published books about Python on
Safari are of equally high quality and David's seem among the best.

I was reading about descriptors, again per Holden workshop, and
noticing the author adding at class level, but never making use of the
passed instance argument, only self (inside the descriptor).

David, on the other hand, explicitly tells us not to instantiate
descriptors at the instance level (they're meant for the classdict)
but then shows using one to pass through to the instance __dict__,
using __set__ and __get__ to communicate at the instance level.

This is what one would expect of attributes as the "normal" case is
instance-level attributes (stuff in self.__dict__) and the first
advanced Python book I was reading demonstrated no awareness of that,
plus handed off to other writers in the case of needing some heavy
lifting (OK to do, but I thought this one, not by David, over did it).

He prepends a "_" to make sure the attribute name doesn't collide with
its own instance-level key in self.__dict__.

Here's some related material:

>>> class Codeguardian:
	def __init__(self,codeword=None):
		print("I am here master...")
		self.default = 0
		self.payme = 0
		self.codeword = codeword

	def __set__(self, instance, value):
		print("I shall perform my duty...")
		self.payme += 1
		if (value == self.codeword or value in range(5)):
			print("the value was rejected, pah!")
	def __get__(self, instance, klass):
		print("I shall fetch ._thecode")
		self.payme += 1
		if "_thecode" not in instance.__dict__:
			print("I have set it to 0 for you sir/mam!")
		return instance._thecode

>>> class Castle:
	def __init__(self, name):
		self.name = name
		# build castle real quick

>>> mycastle = Castle("Somewhere Special")
>>> mycastle.keeper = Codeguardian("hello kitty")
I am here master...
>>> myothercastle = Castle("Somewhere Less Special")
>>> myothercastle = Codeguardian("pssst!")
I am here master...
>>> mycastle.keeper = 4
>>> mycastle.keeper
>>> mycastle.__dict__
{'keeper': 4, 'name': 'Somewhere Special'}

OK, this isn't going according to plan at all.  Why?

Because I did what David said not to do and tried stuffing my
Codeguardian into an instance of Castle, rather than into the Castle
class itself.  Result, __getattribute__ and __setattr__ don't find my
Codeguardian when I go to set the "keep" attribute.

So let's try that again:

>>> Castle.keep = Codeguardian("hello kitty")
I am here master...
>>> mycastle = Castle("Somewhere Special")
>>> myothercastle = Castle("Somewhere Less Special")
>>> mycastle.keeper = 4
>>> mycastle.keep = "the crown jewels"
I shall perform my duty...
the value was rejected, pah!
>>> mycastle.keep = 4
I shall perform my duty...
>>> myothercastle.keep
I shall fetch ._thecode
I have set it to 0 for you sir/mam!
>>> myothercastle.__dict__
{'_thecode': 0, 'name': 'Somewhere Less Special'}
>>> mycastle.__dict__
{'keeper': 4, '_thecode': 4, 'name': 'Somewhere Special'}

OK, that worked, except I left in the goof where I set "keeper"
instead of "keep", cluttering my instance __dict__ with some worthless
DNA.  Or maybe that was intentional?

The point here is the Codeguardian works for the class, even if it has
a way to communicate with the instance at the time of setting and
getting.  In David's example, the instance __dict__ key is built by
prepending a "_" to a passed in variable name so the same Codeguardian
may be set to guard several unique attributes.**

I question here is how does Codeguardian get paid, since he's clearly
keeping an internal tally.  How do we get at the instance level
properties of Codeguardian?

First, note that there's nothing to prevent just poking a value for
_thecode directly into an instance:

>>> mycastle.__dict__["_thecode"]="hah, got around ya!"
>>> mycastle.__dict__
{'keeper': 4, '_thecode': 'hah, got around ya!', 'name': 'Somewhere Special'}
>>> mycastle.keep
I shall fetch ._thecode
'hah, got around ya!'

Anyway, recommended:

Python Essential Reference, Fourth Edition
By: David M. Beazley
Last Updated on Safari: 2009/04/30


** David's example:

class TypedProperty(object):
    def __init__(self,name,type,default=None):
        self.name = "_" + name
        self.type = type
        if default:
            self.default = default
            self.default = type()
    def __get__(self,instance,cls):
        return getattr(instance,self.name,self.default)
    def __set__(self,instance,value):
        if not isinstance(value,self.type):
            raise TypeError("Must be a %s" % self.type)
    def __delete__(self,instance):
        raise AttributeError("Can't delete attribute")

class Foo(object):
    name = TypedProperty("name",str)
    num  = TypedProperty("num",int,42)

More information about the Edu-sig mailing list