Accessing a non-existant attribute modifies class dict - mixin problem

classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Accessing a non-existant attribute modifies class dict - mixin problem

Simon Colley
Hi

I have come across some behaviour that seems a little odd:

Python 2.7 (r27:82500, Jul  6 2010, 17:01:40)
[GCC 4.1.2 20071124 (Red Hat 4.1.2-42)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from enthought.traits.api import (HasTraits, Str)
>>> import pprint
>>>
>>> class IceCream(HasTraits):
...     name = Str('Cornetto')
...
>>>
>>> class StrawberryFlavour(HasTraits):
...     flavour = Str('strawberry')
...
>>>
>>> def ice_cream_factory(flavour_mixin):
...     class FlavouredIceCream(IceCream, flavour_mixin):
...         pass
...     return FlavouredIceCream()
...
>>> strawberry_icecream = ice_cream_factory(StrawberryFlavour)
>>> plain_ice_cream = IceCream()
>>>
>>> # Plain ice cream without mixin has no flavour attribute in class dict:
... pprint.pprint( plain_ice_cream.__class__.__dict__['__class_traits__'])
{'name': <enthought.traits.traits.CTrait object at 0xb7ba3c34>,
'trait_added': <enthought.traits.traits.CTrait object at 0xb7ba3c8c>,
'trait_modified': <enthought.traits.traits.CTrait object at 0xb7b816b4>}
>>> # As expected
... # Trying to access 'flavour' attribute on plain_ice_cream seems to add a 'flavour' attribute to class dict:
... try:
...     print plain_ice_cream.flavour
... except AttributeError, e:
...     pprint.pprint( plain_ice_cream.__class__.__dict__['__class_traits__'])
...
{'flavour': <enthought.traits.traits.CTrait object at 0xb7b819cc>,
'name': <enthought.traits.traits.CTrait object at 0xb7ba3c34>,
'trait_added': <enthought.traits.traits.CTrait object at 0xb7ba3c8c>,
'trait_modified': <enthought.traits.traits.CTrait object at 0xb7b816b4>}
>>> # Make a new flavoured icecream
... more_strawberry_icecream = ice_cream_factory(StrawberryFlavour)
>>> # Should be able to access 'flavour' attribute on more_strawberry_icecream but throws AttributeError instead:
... print more_strawberry_icecream.flavour
Traceback (most recent call last):
 File "<stdin>", line 2, in <module>
AttributeError: 'FlavouredIceCream' object has no attribute 'flavour'
>>> # Appears to be finding a rogue 'flavour' attribute in (plain) IceCream parent class before looking in StrawberryFlavour mixin
...

Is this expected behaviour ? Is there a work around or a better way to achieve the desired result ?


_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev
Reply | Threaded
Open this post in threaded view
|

Re: Accessing a non-existant attribute modifies class dict - mixin problem

Chris Colbert
This is a byproduct of how traits is implement (it's a wart) and will be fixed when we re-architect the core traits framework in traits 4. 

What's happening in the current architecture is this:

When you look up an attribute on a HasTraits class the lookup order progresses as follows:
  1. instance trait dict
  2. class trait dict
  3. normal python look
  4. (if 3 fails) create a new generic python trait and add it to the class dict
I can't give you a concrete reason why 4 exists, as it was built long before my time at enthought. 

So in your example, the first time you access .flavour and it doesnt exist, a trait gets created and added to the class dict. 

Then you create a new class with your mixin, but the flavour trait has already been added to the base class, and thus masks your flavour trait on the mixin.
You can work around your issue, by chaging the order of you mixins, so the mixin class gets first dibs at the lookup:

def ice_cream_factory(flavour_mixin):
     class FlavouredIceCream(flavour_mixin, IceCream):
         pass
     return FlavouredIceCream()


HTH, 

Chris


On Tue, Feb 15, 2011 at 4:42 AM, Simon Colley <[hidden email]> wrote:
Hi

I have come across some behaviour that seems a little odd:

Python 2.7 (r27:82500, Jul  6 2010, 17:01:40)
[GCC 4.1.2 20071124 (Red Hat 4.1.2-42)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from enthought.traits.api import (HasTraits, Str)
>>> import pprint
>>>
>>> class IceCream(HasTraits):
...     name = Str('Cornetto')
...
>>>
>>> class StrawberryFlavour(HasTraits):
...     flavour = Str('strawberry')
...
>>>
>>> def ice_cream_factory(flavour_mixin):
...     class FlavouredIceCream(IceCream, flavour_mixin):
...         pass
...     return FlavouredIceCream()
...
>>> strawberry_icecream = ice_cream_factory(StrawberryFlavour)
>>> plain_ice_cream = IceCream()
>>>
>>> # Plain ice cream without mixin has no flavour attribute in class dict:
... pprint.pprint( plain_ice_cream.__class__.__dict__['__class_traits__'])
{'name': <enthought.traits.traits.CTrait object at 0xb7ba3c34>,
'trait_added': <enthought.traits.traits.CTrait object at 0xb7ba3c8c>,
'trait_modified': <enthought.traits.traits.CTrait object at 0xb7b816b4>}
>>> # As expected
... # Trying to access 'flavour' attribute on plain_ice_cream seems to add a 'flavour' attribute to class dict:
... try:
...     print plain_ice_cream.flavour
... except AttributeError, e:
...     pprint.pprint( plain_ice_cream.__class__.__dict__['__class_traits__'])
...
{'flavour': <enthought.traits.traits.CTrait object at 0xb7b819cc>,
'name': <enthought.traits.traits.CTrait object at 0xb7ba3c34>,
'trait_added': <enthought.traits.traits.CTrait object at 0xb7ba3c8c>,
'trait_modified': <enthought.traits.traits.CTrait object at 0xb7b816b4>}
>>> # Make a new flavoured icecream
... more_strawberry_icecream = ice_cream_factory(StrawberryFlavour)
>>> # Should be able to access 'flavour' attribute on more_strawberry_icecream but throws AttributeError instead:
... print more_strawberry_icecream.flavour
Traceback (most recent call last):
 File "<stdin>", line 2, in <module>
AttributeError: 'FlavouredIceCream' object has no attribute 'flavour'
>>> # Appears to be finding a rogue 'flavour' attribute in (plain) IceCream parent class before looking in StrawberryFlavour mixin
...

Is this expected behaviour ? Is there a work around or a better way to achieve the desired result ?


_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev


_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev
Reply | Threaded
Open this post in threaded view
|

Re: Accessing a non-existant attribute modifies class dict - mixin problem

Chris Colbert
I should add that a generic python trait just uses normal python attribute lookup, which is why you still get the attribute error, even though a trait has been created and added to the class dict. It's that trait itself which is raising the attribute error!

On Tue, Feb 15, 2011 at 3:47 PM, Chris Colbert <[hidden email]> wrote:
This is a byproduct of how traits is implement (it's a wart) and will be fixed when we re-architect the core traits framework in traits 4. 

What's happening in the current architecture is this:

When you look up an attribute on a HasTraits class the lookup order progresses as follows:
  1. instance trait dict
  2. class trait dict
  3. normal python look
  4. (if 3 fails) create a new generic python trait and add it to the class dict
I can't give you a concrete reason why 4 exists, as it was built long before my time at enthought. 

So in your example, the first time you access .flavour and it doesnt exist, a trait gets created and added to the class dict. 

Then you create a new class with your mixin, but the flavour trait has already been added to the base class, and thus masks your flavour trait on the mixin.
You can work around your issue, by chaging the order of you mixins, so the mixin class gets first dibs at the lookup:

def ice_cream_factory(flavour_mixin):
     class FlavouredIceCream(flavour_mixin, IceCream):
         pass
     return FlavouredIceCream()


HTH, 

Chris


On Tue, Feb 15, 2011 at 4:42 AM, Simon Colley <[hidden email]> wrote:
Hi

I have come across some behaviour that seems a little odd:

Python 2.7 (r27:82500, Jul  6 2010, 17:01:40)
[GCC 4.1.2 20071124 (Red Hat 4.1.2-42)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from enthought.traits.api import (HasTraits, Str)
>>> import pprint
>>>
>>> class IceCream(HasTraits):
...     name = Str('Cornetto')
...
>>>
>>> class StrawberryFlavour(HasTraits):
...     flavour = Str('strawberry')
...
>>>
>>> def ice_cream_factory(flavour_mixin):
...     class FlavouredIceCream(IceCream, flavour_mixin):
...         pass
...     return FlavouredIceCream()
...
>>> strawberry_icecream = ice_cream_factory(StrawberryFlavour)
>>> plain_ice_cream = IceCream()
>>>
>>> # Plain ice cream without mixin has no flavour attribute in class dict:
... pprint.pprint( plain_ice_cream.__class__.__dict__['__class_traits__'])
{'name': <enthought.traits.traits.CTrait object at 0xb7ba3c34>,
'trait_added': <enthought.traits.traits.CTrait object at 0xb7ba3c8c>,
'trait_modified': <enthought.traits.traits.CTrait object at 0xb7b816b4>}
>>> # As expected
... # Trying to access 'flavour' attribute on plain_ice_cream seems to add a 'flavour' attribute to class dict:
... try:
...     print plain_ice_cream.flavour
... except AttributeError, e:
...     pprint.pprint( plain_ice_cream.__class__.__dict__['__class_traits__'])
...
{'flavour': <enthought.traits.traits.CTrait object at 0xb7b819cc>,
'name': <enthought.traits.traits.CTrait object at 0xb7ba3c34>,
'trait_added': <enthought.traits.traits.CTrait object at 0xb7ba3c8c>,
'trait_modified': <enthought.traits.traits.CTrait object at 0xb7b816b4>}
>>> # Make a new flavoured icecream
... more_strawberry_icecream = ice_cream_factory(StrawberryFlavour)
>>> # Should be able to access 'flavour' attribute on more_strawberry_icecream but throws AttributeError instead:
... print more_strawberry_icecream.flavour
Traceback (most recent call last):
 File "<stdin>", line 2, in <module>
AttributeError: 'FlavouredIceCream' object has no attribute 'flavour'
>>> # Appears to be finding a rogue 'flavour' attribute in (plain) IceCream parent class before looking in StrawberryFlavour mixin
...

Is this expected behaviour ? Is there a work around or a better way to achieve the desired result ?


_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev



_______________________________________________
Enthought-Dev mailing list
[hidden email]
https://mail.enthought.com/mailman/listinfo/enthought-dev