Class.__class__ magic trick help

M

Massimo Di Pierro

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
 
S

Steven D'Aprano

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.
 
I

Ian Kelly

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.
 
M

Massimo Di Pierro

The fact is this works:

but this does not
.... def __init__(self):
.... self.__class__ = dictTraceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
TypeError: __class__ assignment: only for heap types
 
M

Massimo Di Pierro

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
{'x':1} # ok (3)

{} # 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?
 
O

Oscar Benjamin

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
{'x':1} # ok (3)
Try:

What does that show?



{} # 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


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
 
M

Massimo Di Pierro

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?










{} # 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
[('x',1')]

all the APIs work as expected except casting to dict.
 
M

Massimo Di Pierro

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
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top