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