properties + types, implementing meta-class desciptors elegantly?

Bengt Richter bokr at oz.net
Sun Jul 20 18:52:09 EDT 2003


On 20 Jul 2003 09:39:51 -0400, aahz at pythoncraft.com (Aahz) wrote:

>In article <mailman.1058654317.6488.python-list at python.org>,
>Mike C. Fletcher <mcfletch at rogers.com> wrote:
>>
>>    * finally, stores the value
>>          o tries to do what would have been done if there were no
>>            descriptor (with the new, coerced value)
>>          o does *not* create new names in the object's namespace (all
>>            names are documented w/ descriptors, there's not a lot of
>>            '_' prefixed names cluttering the namespace)
>>          o does *not* require a new dictionary/storage-object attribute
>>            for the object (the descriptor works like any other
>>            descriptor, a *stand-alone* object that replaces a regular
>>            attribute)
>
>But this is a recipe for name clashes.  If you follow the first bullet,
>it's a normal attribute, but everything else says you want a property.
>Properties are themselves objects that are attached to names in an
>object.  You can't have the same name bound to two different objects.

I was wondering if Mike was wanting to have class C(...) with property p
(meaning the property object is an attribute of class C or a base), and
wanting also to have the instance dict contain the data as c.p rather
than, say, c._p.

IOW, does he want a sneaky way to avoid seeing the underscored or otherwise
modified names in the object, so as to have a clean dir(c)?

Other than the problem of getting the class defined in a dynamically configurable way
via metaclasses or other machinery, c.__dict__['p'] = value_from_property_fset should
work, IWT.  The code below does that, though a little more parameterized ;-)

What I'm not getting is why a dict proxy is getting in the way, unless there is an
attempt to use the property mechanism to store properties-as-values in a class-as-instance.

If so, does he (continuing 3rd person since I'm replying to AAhz. Hi Mike ;-) really want
all that dynamic meta-stuff to happen every time a class is instatiated, or should the
classes-to-use be created once at some configuration time (maybe system start or other
special times), and thereafter appear to be equivalent to plain valnilla manually written
classes with properties?

If the latter, some kind of factory module that manufactures configured classes with
configured properties and puts them in its global name space to be used in apparently
normal "import m; inst=m.C()" fashion would seem better than metaclass inheritance magic behind C.
If there are other configuration times than first-import, the module could have a special
factory functions for that, IWT (as in cpc.py below). The properties of the final
configured/manufactured classes could of course act with any degree of dynamism desired
when accessed as attributes of the final class instances.

BTW, do I recall somewhere seeing that defining __dict__ as a slot induces creation of
a writable dict, and could that be used to get a dict in place of proxy for the trouble spot?

For Mike:

What does the following not address (I assume you can rearrange things to your taste ;-)
that you want to do? (InfoSource is my placeholder as an example for whatever dynamic
configuration of the ultimate class you want to do).


====< cpc.py >=================================================================
def getCustPropClass(infoSource):
    class MC_CustPropBase(type): 
        """Metaclass to provide basic customized property methods and data"""
        def __new__(cls, name, bases, cdict):
            if name == 'CustPropBase': return type.__new__(cls, name, bases, cdict)
            cdict['__name__'] = name
            cdict['__doc__'] = infoSource[name, '__doc__']
            # was raw_input('Base Doc string for property %r: '%name)
            # let subclasses "underride" default and config
            if 'default' not in cdict:
                cdict['default'] = infoSource[name, 'default']
                # was raw_input('Base Default for property %r: '%name)
            if 'config' not in cdict:
                cdict['config'] = infoSource[name, 'config']
                # was  raw_input('Base Config data for property %r: '%name)
            def __set__(self, client, val):
                client.__dict__[self.__name__] = val
            def __get__(self, client, ctype=None):
                if client is None: return self
                if self.__name__ not in client.__dict__: return self.default
                return client.__dict__[self.__name__]
            def __delete__(self, client):
                if self.__name__ not in client.__dict__:
                    raise AttributeError, 'Property %r has no instance data to delete.' % self.__name__
                del client.__dict__[self.__name__]
            for method in '__set__ __get__ __delete__'.split():
                if method in cdict: continue  # don't override
                cdict[method] = vars()[method]
            return type.__new__(cls, name, bases, cdict)

    class CustPropBase(object): __metaclass__ = MC_CustPropBase

    class MC_ConfProps(type):
        """
        Configurable property definitions to be instantiated in 
        the instances of (this meta) class.
        """
        def __new__(cls, name, bases, cdict):
            docs = filter(None, [cdict.get('__doc__', '')])
            for k,v in MC_ConfProps.__dict__.items(): #walk prop defs in this class
                if isinstance(v, type) and (hasattr(v,'__set__') or hasattr(v, '__get__')):
                    cdict[k] = v()
                    docs.append('\n    see also %s.%s.__doc__ for property %r'%(name, k, k))
            cdict['__doc__'] = ''.join(docs)
            return type.__new__(cls, name, bases, cdict)

        class p(CustPropBase):
            """property p direct doc string will be overwritten by CustPropBase"""
            default = infoSource['p','default']
            # was raw_input('Property p: "Underride" of Base default value: ')
            conf = infoSource['p','conf']
            # was raw_input('Property p: Special Config data: ')
            def __set__(self, client, val): # special set method differs from CustPropBase
                client.__dict__['p'] = (
                    '(%r) modified w/ overridden base default(%r) data plus\n'
                    'base config(%r) & special config(%r) data.' %
                        ( val, self.default, self.config, self.conf))

        class q(CustPropBase): pass # totally default

        class r(CustPropBase):      # totally default except read-only
            def __set__(self, client, val):
                raise AttributeError, 'Property %r is read-only' % self.__name__

    class ConfiguredProperties(object): __metaclass__ = MC_ConfProps

    class C(ConfiguredProperties):
        """Empty  test class C inheriting from ConfiguredProperties"""
    return C

class InteractiveInfo(object):
    def __getitem__(self, ixtup): # expect (propname, dataname) tuple as key
        return raw_input('Enter value for property %r attribute %r: '% ixtup) #(propname,dataname)

testInfo = {
    ('p','__doc__'): '<p doc string>',
    ('p','default'): '<p default value>',
    ('p','config'):  '<p config value>',
    ('p','conf'):    '<p special conf value>',
    ('q','__doc__'): '<q doc string>',
    ('q','default'): ('<q default value>', (lambda x: 123456789),'(not just strings ;-)'),
    ('q','config'):  '<q config value>',
    ('r','__doc__'): '<r doc string>',
    ('r','default'): '<r default value>',
    ('r','config'):  '<r config value>',
}   

def test():
    for info_source in (testInfo, InteractiveInfo()):
        C = getCustPropClass(info_source)
        c = C()
        print 'intitial p:', c.p
        print 'intitial q:', c.q
        print 'intitial r:', c.r
        print 'try to set them to 123 and retrieve'
        for prop in 'pqr':
            print 'setting %s to 123'%prop
            try:
                setattr(c, prop, 123)
            except Exception, e:
                print '%s: %s'% (e.__class__.__name__, e)
            print 'getting %s:\n----\n%s\n----' %(prop, getattr(c,prop,'???'))
        print 'try to delete them  and retrieve'
        for prop in 'pqr':
            print 'deleting %s'%prop
            try:
                delattr(c, prop)
            except Exception, e:
                print '%s: %s'% (e.__class__.__name__, e)
            print 'after deleting %s:\n----\n%s\n----' %(prop, getattr(c,prop,'???'))
        print 'c.__doc__\n', c.__doc__
        for prop in 'pqr':
            print getattr(C, prop).__doc__

if __name__ == '__main__':
    test()
===============================================================================
An interactive run (with automatic test data first):

[15:46] C:\pywk\clp\fletcher>cfc.py
intitial p: <p default value>
intitial q: ('<q default value>', <function <lambda> at 0x00800730>, '(not just strings ;-)')
intitial r: <r default value>
try to set them to 123 and retrieve
setting p to 123
getting p:
----
(123) modified w/ overridden base default('<p default value>') data plus
base config('<p config value>') & special config('<p special conf value>') data.
----
setting q to 123
getting q:
----
123
----
setting r to 123
AttributeError: Property 'r' is read-only
getting r:
----
<r default value>
----
try to delete them  and retrieve
deleting p
after deleting p:
----
<p default value>
----
deleting q
after deleting q:
----
('<q default value>', <function <lambda> at 0x00800730>, '(not just strings ;-)')
----
deleting r
AttributeError: Property 'r' has no instance data to delete.
after deleting r:
----
<r default value>
----
c.__doc__
Empty  test class C inheriting from ConfiguredProperties
    see also C.q.__doc__ for property 'q'
    see also C.p.__doc__ for property 'p'
    see also C.r.__doc__ for property 'r'
<p doc string>
<q doc string>
<r doc string>
Enter value for property 'p' attribute 'default': [p default]
Enter value for property 'p' attribute 'conf': [p conf]
Enter value for property 'p' attribute '__doc__': [p doc]
Enter value for property 'p' attribute 'config': [p config]
Enter value for property 'q' attribute '__doc__': [q doc]
Enter value for property 'q' attribute 'default': [q default]
Enter value for property 'q' attribute 'config': [q config]
Enter value for property 'r' attribute '__doc__': [r doc]
Enter value for property 'r' attribute 'default': [r default]
Enter value for property 'r' attribute 'config': [r config]
intitial p: [p default]
intitial q: [q default]
intitial r: [r default]
try to set them to 123 and retrieve
setting p to 123
getting p:
----
(123) modified w/ overridden base default('[p default]') data plus
base config('[p config]') & special config('[p conf]') data.
----
setting q to 123
getting q:
----
123
----
setting r to 123
AttributeError: Property 'r' is read-only
getting r:
----
[r default]
----
try to delete them  and retrieve
deleting p
after deleting p:
----
[p default]
----
deleting q
after deleting q:
----
[q default]
----
deleting r
AttributeError: Property 'r' has no instance data to delete.
after deleting r:
----
[r default]
----
c.__doc__
Empty  test class C inheriting from ConfiguredProperties
    see also C.q.__doc__ for property 'q'
    see also C.p.__doc__ for property 'p'
    see also C.r.__doc__ for property 'r'
[p doc]
[q doc]
[r doc]

Regards,
Bengt Richter




More information about the Python-list mailing list