As a bit of a learning project, I decided to take the concept of proxying objects a bit further and extend it to creating proxy classes which create proxy'd objects. I originally found the idea of proxying objects in python on Activestate. I'm currently using a very odd pattern (creating a class with just staticmethods and abusing __new__
to return my classes, instead of using a function (since that would reinstantiate __special_names__
, __imethods__
, and __other_magic__
every time it ran.
Is this a good idea, or should I return to using a function that takes a type
and returns a Proxy subclass? On a second note, is there any way to make isinstance
and issubclass
happy without actually subclassing the given type
? I originally wanted to use __call__
for the class which would make it at least somewhat readable, since when you call functions, you call their __call__
attribute (afaik), but python seems to think I have to use the __new__
attribute instead to use a class as a function.
The entire purpose of this class Proxy
hack is to create Proxy
classes for immutable objects - this way if you do a = b = 10
and a += 5
, a == b == 15
without having to update b
.
Shameless self-plugging: This is actually a core piece of code at pyranoid, which I work on, since it seems interesting.
import functools
import collections
class Proxy(type):
__special_names__ = {
'__abs__', '__add__', '__and__', '__call__', '__cmp__', '__coerce__',
'__contains__', '__delitem__', '__delslice__', '__div__', '__divmod__',
'__eq__', '__float__', '__floordiv__', '__ge__', '__getitem__', '__getslice__',
'__gt__', '__hash__', '__hex__', '__int__', '__iter__', '__le__', '__len__',
'__long__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__',
'__oct__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdiv__',
'__rdivmod__', '__reduce__', '__reduce_ex__', '__reversed__',
'__rfloorfiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__rpow__',
'__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setitem__',
'__setslice__', '__sub__', '__truediv__', '__xor__', '__next__'
}
__imethods__ = {
'__iadd__', '__iand__', '__idiv__', '__idivmod__',
'__ifloordiv__', '__ilshift__', '__imod__', '__imul__',
'__invert__', '__ior__', '__ipow__', '__irshift__',
'__isub__', '__itruediv__', '__ixor__'
}
__other_magic__ = {
'__int__',
'__long__',
'__float__',
'__complex__',
'__oct__',
'__hex__',
'__index__',
'__trunc__',
'__coerce__',
'__str__',
'__repr__',
'__unicode__',
'__format__',
'__hash__',
'__nonzero__',
'__dir__',
'__sizeof__'
}
__overridden__ = __special_names__.union(__imethods__).union(__other_magic__)
@staticmethod
def __imethod_wrapper____(method):
'''makes a wrapper around __i<something>__ methods, such as __iadd__ to act on __subject__'''
@functools.wraps(method)
def wrapper(self,*args,**kwargs):
tmp = self.__subject__
tmp = method(tmp,*args,**kwargs)
self.__subject__ = tmp
return self
return wrapper
@staticmethod
def __method_wrapper__(method):
'''makes a wrapper around methods and cast the result to a proxytype if possible'''
@functools.wraps(method)
def wrapper(self,*args,**kwargs):
res = method(self.__subject__,*args,**kwargs)
try:
return Proxy(type(res),'Proxy<{t}>'.format(t=type(res).__name__))(res)
except TypeError: #if the result's type isn't subclassable, i.e. types.FunctionType would raise a TypeException
return res
return wrapper
@staticmethod
def usable_base_type(Type):
try:
type('',(Type,),{})
return True
except TypeError:
return False
def __new__(cls,parentType,classname=None): #So that Proxy emulates a function
'''parentType is the type you wish to proxy, and classname is the name that appears for the class, <class 'classname'>'''
if not cls.usable_base_type(parentType):
raise TypeError("type '{Type}' is not an acceptable base type".format(Type=parentType.__name__))
if classname is None:
classname = 'Proxy<{name}>'.format(name=parentType.__name__)
class newType(parentType):
def __init__(self,*args,**kwargs):
self.__subject__ = parentType(*args,**kwargs)
def setvalue(self,value):
self.__subject__ = value
def getvalue(self):
return self.__subject__
def __getattr__(self,name):
if name not in cls.__overridden__:
return getattr(self.__subject__,name)
for name,prop in ((k,v) for k,v in parentType.__dict__.items() if k != '__doc__'): #because magic methods are implemented as staticmethods
if name in cls.__special_names__:
setattr(newType,name,cls.__method_wrapper__(prop))
for name in cls.__imethods__: #parentType may not implement all of them
if hasattr(parentType,name):
setattr(newType,name,cls.__imethod_wrapper____(parentType.__dict__[name]))
else:
non_i_name = name[:2]+name[3:]
if hasattr(parentType,non_i_name):
setattr(newType,name,cls.__imethod_wrapper____(getattr(parentType,non_i_name)))
for name in cls.__other_magic__:
if hasattr(parentType,name):
parent_item = getattr(parentType,name)
if isinstance(parent_item,collections.Callable):
setattr(newType,name,lambda self,*args,**kwargs:parent_item(self.__subject__,*args,**kwargs))
else:
setattr(newType,name,parent_item)
newType.__name__ = classname
return newType
1 Answer 1
Some quick thoughts:
I can't tell from reading the code what the purpose is. What is this for? What are the use cases? How am I supposed to use it?
There's no docstring for the
Proxy
class or for theusable_base_type
method.What is the role of the
Proxy
class? You never seem to create any instances of it. (The__new__
method returns an instance of thenewType
that it has just created.) Python is not Java: not everything needs to be a class.Names starting and ending with two underscores are reserved for future use by the language and the standard library:
__*__
System-defined names. These names are defined by the interpreter and its implementation (including the standard library). Current system names are discussed in the Special method names section and elsewhere. More will likely be defined in future versions of Python. Any use of__*__
names, in any context, that does not follow explicitly documented use, is subject to breakage without warning.So I'd recommend avoiding names of this form in your own code.
What's the difference between
__special_names__
and__other_magic__
? Why is__slots__
not in either of these sets?The docstring for
__method_wrapper__
says "cast the result", but Python doesn't have casting. What does this method really do?Why does
__imethod_wrapper____
end in four underscores?Instead of
__special_names__.union(__imethods__).union(__other_magic__)
you could write
__special_names__ | __imethods__ | __other_magic__
__special_names__
is almost in alphabetical order (which is a useful way to order a list like this, because it helps you spot missing and duplicate items), but__next__
is out of order. Why is that?What order are the names in
__other_magic__
in? Why not alphabetical order like the two other lists? And why is this list formatted with one name per line while the others are paragraph-wrapped?The method returned by
__method_wrapper__
callsProxy()
each time it is called, which will create anothernewType
class. Is this really what you want to happen? Won't this lead to a proliferation of identical classes?
But really this is just fumbling in the dark because I don't understand what problem you are trying to solve. I see that you edited the post to add an explanation (you want to be able to proxy immutable objects), but this still leaves me in the dark. Why do you want to proxy immutable objects?
-
\$\begingroup\$ I think one use of this could be to simulate references that other language have, as illustrated in the example
a = b = 10
,a += 5
,a == b == 15
in the OP's question. WRT coding style, I think there should be a single space between items in most comma-separated list, a recommended in PEP-8. Also whitespace related is the fact that a number of lines are extremely long and should have been broken-up into multiple lines for better viewing and readability. \$\endgroup\$martineau– martineau2013年06月28日 03:29:38 +00:00Commented Jun 28, 2013 at 3:29 -
\$\begingroup\$ @martineau: That's what the OP wants to achieve (proxying of immutable objects) but I want to know why. \$\endgroup\$Gareth Rees– Gareth Rees2013年07月01日 09:56:51 +00:00Commented Jul 1, 2013 at 9:56
-
\$\begingroup\$ Oh, I don't know, most likely it's one of the several things people use references to accomplish. What relevance does knowing why have on answering the OP's question about whether a class is a good way to implement a class factory in Python? \$\endgroup\$martineau– martineau2013年07月01日 13:56:48 +00:00Commented Jul 1, 2013 at 13:56
-
\$\begingroup\$ @martineau: I'd like to know the purpose in order to try to provide a better answer. An implementation is only good (or bad) in so far as it meets (or fails to meet) someone's goals. If I knew more about the OP's goals (or use cases) then I would be in a better position to evaluate the solution presented in the question, or to propose a better solution. \$\endgroup\$Gareth Rees– Gareth Rees2013年07月01日 14:11:29 +00:00Commented Jul 1, 2013 at 14:11
Explore related questions
See similar questions with these tags.
Proxy
a function which returns the type. It can still be implemented in terms of the class but you shouldn't make it directly accessible. Keep its definition within the function so people can't go poking around in it. Cleaner (IMHO) if you are making this into a module. \$\endgroup\$Proxy
a class was so you could easily subclass it. In that case perhaps it would be better to implement it as a metaclass because you can also subclass them and it seems to me that's what you're really doing here. \$\endgroup\$