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

Reply via email to