Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #27518 > unrolled thread

Class.__class__ magic trick help

Started byMassimo Di Pierro <massimo.dipierro@gmail.com>
First post2012-08-20 11:01 -0700
Last post2012-08-21 06:50 -0700
Articles 8 — 4 participants

Back to article view | Back to comp.lang.python


Contents

  Class.__class__ magic trick help Massimo Di Pierro <massimo.dipierro@gmail.com> - 2012-08-20 11:01 -0700
    Re: Class.__class__ magic trick help Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2012-08-20 18:21 +0000
    Re: Class.__class__ magic trick help Ian Kelly <ian.g.kelly@gmail.com> - 2012-08-20 12:39 -0600
      Re: Class.__class__ magic trick help Massimo Di Pierro <massimo.dipierro@gmail.com> - 2012-08-20 12:28 -0700
        Re: Class.__class__ magic trick help Massimo Di Pierro <massimo.dipierro@gmail.com> - 2012-08-20 21:17 -0700
          Re: Class.__class__ magic trick help Oscar Benjamin <oscar.j.benjamin@gmail.com> - 2012-08-21 08:40 +0100
            Re: Class.__class__ magic trick help Massimo Di Pierro <massimo.dipierro@gmail.com> - 2012-08-21 05:52 -0700
              Re: Class.__class__ magic trick help Massimo Di Pierro <massimo.dipierro@gmail.com> - 2012-08-21 06:50 -0700

#27518 — Class.__class__ magic trick help

FromMassimo Di Pierro <massimo.dipierro@gmail.com>
Date2012-08-20 11:01 -0700
SubjectClass.__class__ magic trick help
Message-ID<21067364-7b9c-4415-bad1-947961497343@u20g2000yqc.googlegroups.com>
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

[toc] | [next] | [standalone]


#27521

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2012-08-20 18:21 +0000
Message-ID<50328040$0$6574$c3e8da3$5496439d@news.astraweb.com>
In reply to#27518
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

[toc] | [prev] | [next] | [standalone]


#27523

FromIan Kelly <ian.g.kelly@gmail.com>
Date2012-08-20 12:39 -0600
Message-ID<mailman.3576.1345488010.4697.python-list@python.org>
In reply to#27518
On Mon, Aug 20, 2012 at 12:01 PM, Massimo Di Pierro
<massimo.dipierro@gmail.com> 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.

[toc] | [prev] | [next] | [standalone]


#27528

FromMassimo Di Pierro <massimo.dipierro@gmail.com>
Date2012-08-20 12:28 -0700
Message-ID<bd3da121-f77c-49b3-a9d5-ae1aebbd9398@r10g2000yqm.googlegroups.com>
In reply to#27523
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 <ian.g.ke...@gmail.com> wrote:
> On Mon, Aug 20, 2012 at 12:01 PM, Massimo Di Pierro
>
> <massimo.dipie...@gmail.com> 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.

[toc] | [prev] | [next] | [standalone]


#27547

FromMassimo Di Pierro <massimo.dipierro@gmail.com>
Date2012-08-20 21:17 -0700
Message-ID<8dc49931-de0d-4ce3-8b0d-8789bc8c07c8@w9g2000yqe.googlegroups.com>
In reply to#27528
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?

[toc] | [prev] | [next] | [standalone]


#27555

FromOscar Benjamin <oscar.j.benjamin@gmail.com>
Date2012-08-21 08:40 +0100
Message-ID<mailman.3593.1345534858.4697.python-list@python.org>
In reply to#27547
On Mon, 20 Aug 2012 21:17:15 -0700 (PDT), Massimo Di Pierro 
<massimo.dipierro@gmail.com> 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

[toc] | [prev] | [next] | [standalone]


#27567

FromMassimo Di Pierro <massimo.dipierro@gmail.com>
Date2012-08-21 05:52 -0700
Message-ID<c44a1861-40fc-4583-af66-511591d7ce9a@s7g2000yqm.googlegroups.com>
In reply to#27555
On Aug 21, 2:40 am, Oscar Benjamin <oscar.j.benja...@gmail.com> wrote:
> On Mon, 20 Aug 2012 21:17:15 -0700 (PDT), Massimo Di Pierro
>
>
>
>
>
>
>
>
>
> <massimo.dipie...@gmail.com> 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.

[toc] | [prev] | [next] | [standalone]


#27568

FromMassimo Di Pierro <massimo.dipierro@gmail.com>
Date2012-08-21 06:50 -0700
Message-ID<ad794629-cca6-4f1b-bc95-500d430405e6@p11g2000yqg.googlegroups.com>
In reply to#27567
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

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web