Class.__class__ magic trick help

Discussion in 'Python' started by Massimo Di Pierro, Aug 20, 2012.

  1. I discovered I can do this:

    class A(object): pass
    class B(object):
    __class__ = A # <<<< magic

    b = B()
    isinstance(b,A) # returns True (as if B derived from A)
    isinstance(b,B) # also returns True

    I have some reasons I may want to do this (I an object with same
    methods as a dict but it is not derived from dict and I want
    isinstance(x,dict)==True to use it in place of dict in some other
    code).

    What are the problems with the magic trick above? Why does it work?

    massimo
     
    Massimo Di Pierro, Aug 20, 2012
    #1
    1. Advertising

  2. On Mon, 20 Aug 2012 11:01:36 -0700, Massimo Di Pierro wrote:

    > I discovered I can do this:
    >
    > class A(object): pass
    > class B(object):
    > __class__ = A # <<<< magic


    Why do you think that's magic?


    > b = B()
    > isinstance(b,A) # returns True (as if B derived from A)


    b.__class__ is A, so naturally isinstance(b, A) will return True.

    > isinstance(b,B) # also returns True


    type(b) is B, so naturally isinstance(b, B) will return True.


    > I have some reasons I may want to do this (I an object with same methods
    > as a dict but it is not derived from dict and I want
    > isinstance(x,dict)==True to use it in place of dict in some other code).


    Be aware that some parts of Python will insist on real dicts, not just
    subclasses or fake dicts.


    --
    Steven
     
    Steven D'Aprano, Aug 20, 2012
    #2
    1. Advertising

  3. Massimo Di Pierro

    Ian Kelly Guest

    On Mon, Aug 20, 2012 at 12:01 PM, Massimo Di Pierro
    <> wrote:
    > I discovered I can do this:
    >
    > class A(object): pass
    > class B(object):
    > __class__ = A # <<<< magic
    >
    > b = B()
    > isinstance(b,A) # returns True (as if B derived from A)
    > isinstance(b,B) # also returns True
    >
    > I have some reasons I may want to do this (I an object with same
    > methods as a dict but it is not derived from dict and I want
    > isinstance(x,dict)==True to use it in place of dict in some other
    > code).
    >
    > What are the problems with the magic trick above? Why does it work?


    Normally with __class__ assignment, you would assign to the __class__
    attribute of the *instance*, not the class declaration. This actually
    changes the class of the object, and so isinstance(b, B) would no
    longer return True.

    I've never heard of assigning it in the class declaration, and as far
    as I know, this behavior isn't documented anywhere. I expect that
    what's happening here is that Python is not actually updating the
    class of the instance, but that A is merely assigned to the
    "__class__" attribute in the class dict, and that isinstance is
    somehow (perhaps accidentally) finding this. So I think this is
    probably a bug, and I would not rely on it to work correctly in all
    cases.

    In any event, the use case that you're looking for is usually
    accomplished using abstract base classes. Instead of "isinstance(x,
    dict)", you should use "isinstance(x, collections.MutableMapping)",
    and then inherit your class from or register it with the
    MutableMapping ABC.
     
    Ian Kelly, Aug 20, 2012
    #3
  4. The fact is this works:

    >>> class B(object):

    .... __class__ = dict
    >>> b=B()


    but this does not

    >>> class B(object):

    .... def __init__(self):
    .... self.__class__ = dict
    >>> b=B()

    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "<stdin>", line 3, in __init__
    TypeError: __class__ assignment: only for heap types



    On Aug 20, 1:39 pm, Ian Kelly <> wrote:
    > On Mon, Aug 20, 2012 at 12:01 PM, Massimo Di Pierro
    >
    > <> wrote:
    > > I discovered I can do this:

    >
    > >     class A(object): pass
    > >     class B(object):
    > >         __class__ = A # <<<< magic

    >
    > >     b = B()
    > >     isinstance(b,A) # returns True (as if B derived from A)
    > >     isinstance(b,B) # also returns True

    >
    > > I have some reasons I may want to do this (I an object with same
    > > methods as a dict but it is not derived from dict and I want
    > > isinstance(x,dict)==True to use it in place of dict in some other
    > > code).

    >
    > > What are the problems with the magic trick above? Why does it work?

    >
    > Normally with __class__ assignment, you would assign to the __class__
    > attribute of the *instance*, not the class declaration.  This actually
    > changes the class of the object, and so isinstance(b, B) would no
    > longer return True.
    >
    > I've never heard of assigning it in the class declaration, and as far
    > as I know, this behavior isn't documented anywhere.  I expect that
    > what's happening here is that Python is not actually updating the
    > class of the instance, but that A is merely assigned to the
    > "__class__" attribute in the class dict, and that isinstance is
    > somehow (perhaps accidentally) finding this.  So I think this is
    > probably a bug, and I would not rely on it to work correctly in all
    > cases.
    >
    > In any event, the use case that you're looking for is usually
    > accomplished using abstract base classes.  Instead of "isinstance(x,
    > dict)", you should use "isinstance(x, collections.MutableMapping)",
    > and then inherit your class from or register it with the
    > MutableMapping ABC.
     
    Massimo Di Pierro, Aug 20, 2012
    #4
  5. Consider this code:

    class SlowStorage(dict):
    def __getattr__(self,key):
    return self[key]
    def __setattr__(self,key):
    self[key]=value

    class FastStorage(dict):
    def __init__(self, __d__=None, **kwargs):
    self.update(__d__,**kwargs)
    def __getitem__(self,key):
    return self.__dict__.get(key,None)
    def __setitem__(self,key,value):
    self.__dict__[key] = value
    def __delitem__(self,key):
    delattr(self,key)
    def __copy__(self):
    return Storage(self)
    def __nonzero__(self):
    return len(self.__dict__)>0
    def pop(self,key,default=None):
    if key in self:
    default = getattr(self,key)
    delattr(self,key)
    return default
    def clear(self):
    self.__dict__.clear()
    def __repr__(self):
    return repr(self.__dict__)
    def keys(self):
    return self.__dict__.keys()
    def values(self):
    return self.__dict__.values()
    def items(self):
    return self.__dict__.items()
    def iterkeys(self):
    return self.__dict__.iterkeys()
    def itervalues(self):
    return self.__dict__.itervalues()
    def iteritems(self):
    return self.__dict__.iteritems()
    def viewkeys(self):
    return self.__dict__.viewkeys()
    def viewvalues(self):
    return self.__dict__.viewvalues()
    def viewitems(self):
    return self.__dict__.viewitems()
    def fromkeys(self,S,v=None):
    return self.__dict__.fromkeys(S,v)
    def setdefault(self, key, default=None):
    try:
    return getattr(self,key)
    except AttributeError:
    setattr(self,key,default)
    return default
    def clear(self):
    self.__dict__.clear()
    def len(self):
    return len(self.__dict__)
    def __iter__(self):
    return self.__dict__.__iter__()
    def has_key(self,key):
    return key in self.__dict__
    def __contains__(self,key):
    return key in self.__dict__
    def update(self,__d__=None,**kwargs):
    if __d__:
    for key in __d__:
    kwargs[key] = __d__[key]
    self.__dict__.update(**kwargs)
    def get(self,key,default=None):
    return getattr(self,key) if key in self else default

    >>> s=SlowStorage()
    >>> a.x=1 ### (1)
    >>> a.x ### (2)

    1 # ok
    >>> isinstance(a,dict)

    True # ok
    >>> print dict(a)

    {'x':1} # ok (3)


    >>> s=FastStorage()
    >>> a.x=1 ### (4)
    >>> a.x ### (5)

    1 # ok
    >>> isinstance(a,dict)

    True # ok
    >>> print dict(a)

    {} # not ok (6)

    Lines (4) and (5) are about 10x faster then lines (1) and (2). I like
    FastStorage better but while (3) behaves ok, (6) does not behave as I
    want.

    I intuitively understand why FastStorage is cannot cast into dict
    properly.

    What I do not know is how to make it do the casting properly without
    losing the 10x speedup of FastStorage over SlowStorage.

    Any idea?
     
    Massimo Di Pierro, Aug 21, 2012
    #5
  6. On Mon, 20 Aug 2012 21:17:15 -0700 (PDT), Massimo Di Pierro
    <> wrote:
    > Consider this code:



    > class SlowStorage(dict):
    > def __getattr__(self,key):
    > return self[key]
    > def __setattr__(self,key):
    > self[key]=value



    > class FastStorage(dict):
    > def __init__(self, __d__=None, **kwargs):
    > self.update(__d__,**kwargs)
    > def __getitem__(self,key):
    > return self.__dict__.get(key,None)
    > def __setitem__(self,key,value):
    > self.__dict__[key] = value
    > def __delitem__(self,key):
    > delattr(self,key)
    > def __copy__(self):
    > return Storage(self)
    > def __nonzero__(self):
    > return len(self.__dict__)>0
    > def pop(self,key,default=None):
    > if key in self:
    > default = getattr(self,key)
    > delattr(self,key)
    > return default
    > def clear(self):
    > self.__dict__.clear()
    > def __repr__(self):
    > return repr(self.__dict__)
    > def keys(self):
    > return self.__dict__.keys()
    > def values(self):
    > return self.__dict__.values()
    > def items(self):
    > return self.__dict__.items()
    > def iterkeys(self):
    > return self.__dict__.iterkeys()
    > def itervalues(self):
    > return self.__dict__.itervalues()
    > def iteritems(self):
    > return self.__dict__.iteritems()
    > def viewkeys(self):
    > return self.__dict__.viewkeys()
    > def viewvalues(self):
    > return self.__dict__.viewvalues()
    > def viewitems(self):
    > return self.__dict__.viewitems()
    > def fromkeys(self,S,v=None):
    > return self.__dict__.fromkeys(S,v)
    > def setdefault(self, key, default=None):
    > try:
    > return getattr(self,key)
    > except AttributeError:
    > setattr(self,key,default)
    > return default
    > def clear(self):
    > self.__dict__.clear()
    > def len(self):
    > return len(self.__dict__)
    > def __iter__(self):
    > return self.__dict__.__iter__()
    > def has_key(self,key):
    > return key in self.__dict__
    > def __contains__(self,key):
    > return key in self.__dict__
    > def update(self,__d__=None,**kwargs):
    > if __d__:
    > for key in __d__:
    > kwargs[key] = __d__[key]
    > self.__dict__.update(**kwargs)
    > def get(self,key,default=None):
    > return getattr(self,key) if key in self else default



    > >>> s=SlowStorage()
    > >>> a.x=1 ### (1)
    > >>> a.x ### (2)

    > 1 # ok
    > >>> isinstance(a,dict)

    > True # ok
    > >>> print dict(a)

    > {'x':1} # ok (3)


    Try:
    >>> a.items()


    What does that show?




    > >>> s=FastStorage()
    > >>> a.x=1 ### (4)
    > >>> a.x ### (5)

    > 1 # ok
    > >>> isinstance(a,dict)

    > True # ok
    > >>> print dict(a)

    > {} # not ok (6)



    > Lines (4) and (5) are about 10x faster then lines (1) and (2). I

    like
    > FastStorage better but while (3) behaves ok, (6) does not behave as

    I
    > want.



    > I intuitively understand why FastStorage is cannot cast into dict
    > properly.



    > What I do not know is how to make it do the casting properly without
    > losing the 10x speedup of FastStorage over SlowStorage.



    > Any idea?


    I don't really understand what your trying to do but since you didn't
    add the __setattr__ method to FastStorage the item is not added to
    the dictionary when you do a.x = 1

    Oscar
     
    Oscar Benjamin, Aug 21, 2012
    #6
  7. On Aug 21, 2:40 am, Oscar Benjamin <> wrote:
    > On Mon, 20 Aug 2012 21:17:15 -0700 (PDT), Massimo Di Pierro
    >
    >
    >
    >
    >
    >
    >
    >
    >
    > <> wrote:
    > > Consider this code:
    > > class SlowStorage(dict):
    > >     def __getattr__(self,key):
    > >           return self[key]
    > >     def __setattr__(self,key):
    > >           self[key]=value
    > > class FastStorage(dict):
    > >     def __init__(self, __d__=None, **kwargs):
    > >         self.update(__d__,**kwargs)
    > >     def __getitem__(self,key):
    > >         return self.__dict__.get(key,None)
    > >     def __setitem__(self,key,value):
    > >         self.__dict__[key] = value
    > >     def __delitem__(self,key):
    > >         delattr(self,key)
    > >     def __copy__(self):
    > >         return Storage(self)
    > >     def __nonzero__(self):
    > >         return len(self.__dict__)>0
    > >     def pop(self,key,default=None):
    > >         if key in self:
    > >             default = getattr(self,key)
    > >             delattr(self,key)
    > >         return default
    > >     def clear(self):
    > >         self.__dict__.clear()
    > >     def __repr__(self):
    > >         return repr(self.__dict__)
    > >     def keys(self):
    > >         return self.__dict__.keys()
    > >     def values(self):
    > >         return self.__dict__.values()
    > >     def items(self):
    > >         return self.__dict__.items()
    > >       def iterkeys(self):
    > >         return self.__dict__.iterkeys()
    > >     def itervalues(self):
    > >         return self.__dict__.itervalues()
    > >     def iteritems(self):
    > >         return self.__dict__.iteritems()
    > >     def viewkeys(self):
    > >         return self.__dict__.viewkeys()
    > >     def viewvalues(self):
    > >         return self.__dict__.viewvalues()
    > >     def viewitems(self):
    > >         return self.__dict__.viewitems()
    > >     def fromkeys(self,S,v=None):
    > >         return self.__dict__.fromkeys(S,v)
    > >     def setdefault(self, key, default=None):
    > >         try:
    > >             return getattr(self,key)
    > >         except AttributeError:
    > >             setattr(self,key,default)
    > >             return default
    > >     def clear(self):
    > >         self.__dict__.clear()
    > >     def len(self):
    > >         return len(self.__dict__)
    > >     def __iter__(self):
    > >         return self.__dict__.__iter__()
    > >     def has_key(self,key):
    > >         return key in self.__dict__
    > >     def __contains__(self,key):
    > >         return key in self.__dict__
    > >     def update(self,__d__=None,**kwargs):
    > >         if __d__:
    > >             for key in __d__:
    > >                 kwargs[key] = __d__[key]
    > >         self.__dict__.update(**kwargs)
    > >     def get(self,key,default=None):
    > >         return getattr(self,key) if key in self else default
    > > >>> s=SlowStorage()
    > > >>> a.x=1  ### (1)
    > > >>> a.x    ### (2)

    > > 1 # ok
    > > >>> isinstance(a,dict)

    > > True # ok
    > > >>> print dict(a)

    > > {'x':1} # ok (3)

    >
    > Try:
    >
    > >>> a.items()

    >
    > What does that show?
    >
    >
    >
    >
    >
    >
    >
    >
    >
    >
    >
    > > >>> s=FastStorage()
    > > >>> a.x=1  ### (4)
    > > >>> a.x    ### (5)

    > > 1 # ok
    > > >>> isinstance(a,dict)

    > > True # ok
    > > >>> print dict(a)

    > > {} # not ok (6)
    > > Lines (4) and (5) are about 10x faster then lines (1) and (2). I

    > like
    > > FastStorage better but while (3) behaves ok, (6) does not behave as

    > I
    > > want.
    > > I intuitively understand why FastStorage is cannot cast into dict
    > > properly.
    > > What I do not know is how to make it do the casting properly without
    > > losing the 10x speedup of FastStorage over SlowStorage.
    > > Any idea?

    >
    > I don't really understand what your trying to do but since you didn't
    > add the __setattr__ method to FastStorage the item is not added to
    > the dictionary when you do a.x = 1
    >
    > Oscar


    >>> a.items()

    [('x',1')]

    all the APIs work as expected except casting to dict.
     
    Massimo Di Pierro, Aug 21, 2012
    #7
  8. Hello Oscar,

    thanks for your help but your proposal of adding:

    def __setitem__(self,key,value):
    self.__dict__[key] = value
    dict.__setitem__(self, key, value)

    does not help me.

    What I have today is a class that works like SlowStorage. I want to
    replace it with NewStorage because it is 10x faster. That is the only
    reason. NewStorage does everything I want and all the APIs work like
    SlowStorage except casting to dict.

    By defining __setitem__ as you propose, you solve the casting to dict
    issue but you have two unwanted effects: each key,value is store twice
    (in different places), accessing the elements becomes slower the
    SlowStprage which is my problem in the first place.

    The issue for me is understanding how the casting dict(obj) works and
    how to change its behavior so that is uses methods exposed by obj to
    do the casting, if all possible.

    Massimo
     
    Massimo Di Pierro, Aug 21, 2012
    #8
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. =?Utf-8?B?U2hhd24=?=

    Magic Trick Datagrid Delete Row

    =?Utf-8?B?U2hhd24=?=, Apr 6, 2005, in forum: ASP .Net
    Replies:
    3
    Views:
    3,485
    =?Utf-8?B?U2hhd24=?=
    Apr 6, 2005
  2. Jp Calderone
    Replies:
    1
    Views:
    528
    =?ISO-8859-1?Q?=22Martin_v=2E_L=F6wis=22?=
    Jul 6, 2003
  3. gregory lielens
    Replies:
    6
    Views:
    617
    Gregory Lielens
    Dec 2, 2004
  4. Paul McGuire

    Assigning to self.__class__

    Paul McGuire, Jan 26, 2006, in forum: Python
    Replies:
    4
    Views:
    472
    Terry Reedy
    Jan 26, 2006
  5. Giles Bowkett
    Replies:
    9
    Views:
    425
    Giles Bowkett
    Dec 17, 2007
Loading...

Share This Page