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

Reply via email to