Ahhhhhhhh! A HUGE thanks Paul - I haven't created a function in a loop before, and it didn't occur to me that the functions I was defining were looking up the assignFnc and fnc variables. A pretty fundamental misunderstanding on my part - doh! The lesson here is that if I'm defining an object in a loop, I need to make sure to 'localize' items that those objects are referencing.
Many thanks again!! -JP On Sat, Jan 15, 2011 at 12:46 PM, Paul Molodowitch <[email protected]>wrote: > Ah - the problem was, indeed, in the bit of code you left out. The key > difference is here: > > for fName, fnc in fncs: > > ... > > assignFnc = lambda self: fnc() # Oh noes! > > result[name] = type(name, (Dummy,), {'shapeFunc':assignFnc}) > > > Why is this different from > > for fName, fnc in fncs: > > ... > > result[name] = type(name, (Dummy,), {'shapeFunc':fnc}) > > > ? > > > It's probably easier to see with a stripped down example: > > > # Let's make some functions! > > funcs = [] > > for x in xrange(5): > > def myFunc(): > > return x > > funcs.append(myFunc) > > > print funcs[1]() > > print funcs[2]() > > > All the created functions will return 4; why? Well, each myFunc is told to > return x... but since x is not a local variable name, it looks it up in the > 'parent' namespace. And here's where the problem comes in - since for loops > don't create their own namespaces, all the generated functions end up > looking in the same place - the namespace in which the functions were > defined. Which means they all find the same value for x - the last value it > had after exiting the loop. > > Probably the easiest way to fix it in your case is by doing something like > this: > > for fName, fnc in fncs: > > ... > > class NewShapeClass(Shape): > > shapeFunc = staticmethod(fnc) > > NewShapeClass.__name__ = name > > result[name] = NewShapeClass > > > or > > for fName, fnc in fncs: > ... > class NewShapeClass(Shape): > _rawShapeFunc = fnc > def shapeFunc(self): > return self._rawShapeFunc() > NewShapeClass.__name__ = name > result[name] = NewShapeClass > > Note that in the second case, the 'shapeFunc' definition ends up being > identical for every class - so you could just put shapeFunc onto the base > class: > > class Shape(object): > def shapeFunc(self): > return self._rawShapeFunc() > > ...which is probably my personal preference. > > Also - note that if you do: > > for fName, fnc in fncs: > ... > class NewShapeClass(Shape): > def shapeFunc(self): > return fnc > NewShapeClass.__name__ = name > result[name] = NewShapeClass > > ...it will NOT fix anything, as you run back into the same namespace-lookup > issues. > > > - Paul > > PS - You'll also note that I personally prefer the use of the 'normal' > class definition syntax, as opposed to calling 'type', as it's more > readable... > > > > On Fri, Jan 14, 2011 at 11:16 PM, John Patrick <[email protected]>wrote: > >> To simplify/narrow down this issue quite a bit, here's the problem: >> >> #Problem >> def testDct(): >> >> result = {} >> >> l = [1, 2, 3] >> >> for item in l: >> >> result[str(item)] = type(str(item), (), {'test': lambda self: item}) >> >> return result >> >> >> print testDct()['1']().test() >> >> print testDct()['2']().test() >> >> #This, however, gives me what I'd expect: >> def testDct(): >> >> l = [1, 2, 3] >> >> for item in l: >> >> yield type(str(item), (), {'test': lambda self: item}) >> >> >> classes = [] >> >> for X in testDct(): >> >> print X().test() >> >> classes.append(X) >> >> >> #right until here >> >> print classes[0]().test() >> >> print classes[1]().test() >> >> >> >> On Fri, Jan 14, 2011 at 10:43 PM, John Patrick <[email protected]>wrote: >> >>> Hey Paul - I did leave out the last line by mistake, which is just >>> 'return result', as you guessed. Thanks a bunch for checking it out. Here >>> is a full chunk of code that can be run without modification: >>> >>> >>> import logging, inspect >>> import pymel.core as pm >>> >>> logger = logging.getLogger(__name__) >>> >>> logger.setLevel(logging.DEBUG) >>> >>> >>> def make_square_crv(): >>> >>> pm.curve(p=[(1, 0, 1), (-1, 0, 1), (-1, 0, -1), (1, 0, -1), (1, 0, 1)], >>> k=[0, 1, 2, 3, 4], d=1) >>> >>> >>> def make_circle_crv(): >>> >>> pm.circle(nr=[0, 1, 0], sw=360, d=3, s=12)[0] >>> >>> >>> class Dummy(object): pass >>> >>> >>> def genShapeClasses(): >>> >>> """ >>> >>> A factory that generates shape creation classes >>> >>> @return: a dict of {'className': class} >>> >>> """ >>> >>> result = {} >>> >>> fncs = inspect.getmembers(sys.modules[__name__], inspect.isfunction) >>> >>> fncs = [(name, fnc) for name, fnc in fncs if name.startswith('make_')] >>> >>> >>> for fName, fnc in fncs: >>> >>> tmp, sName, sType = fName.split('_') >>> >>> msg = "Generating Shape Class:\n" >>> >>> msg += "name: %s\n" % sName >>> >>> msg += "shapeType: %s\n" % sType >>> >>> msg += "func: %s\n" % fnc.__name__ >>> >>> msg += "func obj: %s\n" % str(fnc) >>> >>> msg += "fnc id: %s\n" % str(id(fnc)) >>> >>> logger.debug(msg) >>> >>> >>> name = sName.capitalize() + sType.capitalize() + 'Shape' >>> >>> assignFnc = lambda self: fnc() >>> >>> result[name] = type(name, (Dummy,), {'shapeFunc':assignFnc}) >>> >>> >>> return result >>> >>> >>> #The objects end up being the same >>> >>> classes = genShapeClasses() >>> >>> print id(classes['CircleCrvShape'].shapeFunc) >>> >>> print id(classes['SquareCrvShape'].shapeFunc) >>> >>> >>> I'm still stumped. This is the first time I've needed a class factory, >>> and it sure is making me feel inept... >>> >>> >>> On Fri, Jan 14, 2011 at 9:28 PM, Paul Molodowitch <[email protected]>wrote: >>> >>>> The problem must be somewhere in the portion of the code you're leaving >>>> out... and you must be leaving out SOMETHING, since the genShapeClasses >>>> function you've given doesn't return a value. I tried running the code you >>>> gave (simply adding a 'return result' statement, and some dummy make_* >>>> functions), and it worked fine. (ie, id(classes['TextCrvShape'].shapeFunc) >>>> != print id(classes['SquareCrvShape'].shapeFunc) >>>> >>>> - Paul >>>> >>>> On Fri, Jan 14, 2011 at 6:09 PM, JP <[email protected]> wrote: >>>> >>>>> It might be worth mentioning that i was originally doing this inside >>>>> the factory loop with the same issue: >>>>> >>>>> name = sName.capitalize() + sType.capitalize() + 'Shape' >>>>> ##This is really what needs to happen anyways to since the >>>>> method will be passed a 'self' arg >>>>> assignmentFunc = lambda self: fnc() >>>>> result[name] = type(name, (Shape,), >>>>> {'shapeFunc':assignmentFunc}) >>>>> >>>>> In the end, I end up with something like this: >>>>> >>> id(result['class1']) != id(result['class2']) >>>>> True >>>>> >>> id(result['Class1'].shapeFunc) == id(result['Class2'].shapeFunc) >>>>> True >>>>> >>>>> hrm... >>>>> >>>>> On Jan 14, 5:15 pm, John Patrick <[email protected]> wrote: >>>>> > Hey all, >>>>> > >>>>> > I've been looking at this for quite a while, and I still can't figure >>>>> it >>>>> > out. I'm trying to write a class factory that generates 'Shape' >>>>> subclasses, >>>>> > each of which has a different 'shapeFunc' attribute that creates a >>>>> shape. >>>>> > The problem I'm running into is that all the classes come out with >>>>> the same >>>>> > function - the last one in the list. So it looks like I'm somehow >>>>> creating >>>>> > a reference to the same object in all the classes...but I'm not sure >>>>> why. >>>>> > >>>>> > I have a number of 'make_someShapeName_crv' functions in the same >>>>> module, >>>>> > and I'm trying to get a dictionary of all the classes like this: >>>>> > >>>>> > def genShapeClasses(): >>>>> > """ >>>>> > A factory that generates shape creation classes >>>>> > >>>>> > @return: a dict of {'className': class} >>>>> > """ >>>>> > result = {} >>>>> > >>>>> > fncs = inspect.getmembers(sys.modules[__name__], >>>>> inspect.isfunction) >>>>> > fncs = [(name, fnc) for name, fnc in fncs if >>>>> name.startswith('make_')] >>>>> > >>>>> > for fName, fnc in fncs: >>>>> > tmp, sName, sType = fName.split('_') >>>>> > >>>>> > #debugging log >>>>> > msg = "Generating Shape Class:\n" >>>>> > msg += "name: %s\n" % sName >>>>> > msg += "shapeType: %s\n" % sType >>>>> > msg += "func: %s\n" % fnc.__name__ >>>>> > msg += "func obj: %s\n" % str(fnc) >>>>> > msg += "fnc id: %s\n" % str(id(fnc)) >>>>> > logger.debug(msg) >>>>> > >>>>> > name = sName.capitalize() + sType.capitalize() + 'Shape' >>>>> > >>>>> > result[name] = type(name, (Shape,), {'shapeFunc':fnc}) >>>>> > >>>>> > Here are the last two log blocks: >>>>> > >>>>> > *# RigIt.lib.Shapes : Generating Shape Class:* >>>>> > >>>>> > *name: square* >>>>> > >>>>> > *shapeType: crv* >>>>> > >>>>> > *func: make_square_crv* >>>>> > >>>>> > *func obj: <function make_square_crv at 0x000000001827EF98>* >>>>> > >>>>> > *fnc id: 405270424* >>>>> > >>>>> > * # * >>>>> > >>>>> > *# RigIt.lib.Shapes : Generating Shape Class:* >>>>> > >>>>> > *name: text* >>>>> > >>>>> > *shapeType: crv* >>>>> > >>>>> > *func: make_text_crv* >>>>> > >>>>> > *func obj: <function make_text_crv at 0x000000001827E6D8>* >>>>> > >>>>> > *fnc id: 405268184* >>>>> > >>>>> > * #* >>>>> > >>>>> > Finally, here is the problem I'm having: >>>>> > >>>>> > >>>classes = Shapes.genShapeClasses() >>>>> > >>>print id(classes['TextCrvShape'].shapeFunc) >>>>> > >>>>> > *478555520* >>>>> > >>>>> > >>>print id(classes['SquareCrvShape'].shapeFunc) >>>>> > >>>>> > *478555520* >>>>> > >>>>> > I've tried a shotgun approach and put a deepcopy() just about >>>>> everywhere to >>>>> > no avail. The thing that's stumping me is that i have a new object >>>>> in >>>>> > memory when the loop starts - but somehow in the process of assigning >>>>> it to >>>>> > the new class, they all end up referencing the same object. When I >>>>> id() the >>>>> > class itself, they are different Class objects. >>>>> > >>>>> > Any Pythonistas out there that could offer a hand? >>>>> > >>>>> > Thanks! >>>>> > >>>>> > -JP >>>>> > -- >>>>> > John Patrick >>>>> > 404-242-2675 >>>>> > [email protected]http://www.canyourigit.com >>>>> >>>>> -- >>>>> http://groups.google.com/group/python_inside_maya >>>>> >>>> >>>> -- >>>> http://groups.google.com/group/python_inside_maya >>>> >>> >>> >>> >>> -- >>> John Patrick >>> 404-242-2675 >>> [email protected] >>> http://www.canyourigit.com >>> >> >> >> >> -- >> John Patrick >> 404-242-2675 >> [email protected] >> http://www.canyourigit.com >> >> -- >> http://groups.google.com/group/python_inside_maya >> > > -- > http://groups.google.com/group/python_inside_maya > -- John Patrick 404-242-2675 [email protected] http://www.canyourigit.com -- http://groups.google.com/group/python_inside_maya
