Blog of


APyTA_10: Function mechanics, arguments and scope notions.

APyTA_10:  Function mechanics, arguments and scope notions.

Main keywords for defining functions logic are :
def #Define a function (or method)
print #Display a value in current output (sys.stdout)
del #Remove the variable from the RAM
pass #Exit the scope, doing nothing (useful to tag a scope into a TODO status, because python wants always something if you create a scope (by ':')
return #Allow the function to return a computed result, the best way to communicate a value through a scope.
class #Create an object (a future APyTA will explain that)
assert #Can authenticate your function arguments

As we already seen some function snippets, let's dig into assert keyword:
Its purpose is to help you validate your given argument in a scope of a function.
Python polymorphism could be quite scary, and when you have data from the 'outside world', you may want to be really sure of the type of the objects you will have to deal with:

import pymel.core.nodetypes, pymel.core.modeling, pymel.core.datatypes

def raiseObject(_OffsetZ, _theObject, _TypeChecking=False): #Putting a value like that MyArg=Value are called Optional Argument (Syntax law force optionnal arguments to be ther LAST ones).
    assert type(_OffsetZ) is float, "_OffsetZ is not an float: %s" % `_OffsetZ` #I kept these string formating (usable also with print) to show you them but I'm not a big fan of it.
    assert type(_theObject) is pymel.core.nodetypes.Transform, "_theObject is not a Transform: %s" % `_theObject`

  _theObject.setTranslation( pymel.core.datatypes.Vector(y=_OffsetZ) ) #See how here I need _theObject of being a Transform ? .setTranslation( will be meaningless otherwise.

myCubeTranform  = pymel.core.modeling.polyCube()[0]
liftObj = raiseObject #Function are vars to. Usefull to create aliases.

raiseObject(5.0, "pCube1",True) # AssertionError: _theObject is not a Transform: 'pCube1' #
raiseObject(5.0, "pCube1")# AttributeError: 'str' object has no attribute 'setTranslation' #   Excepected, we skip the assert part, but the core of the function compute with a wrong type
liftObj(5, myCubeTranform,True) # AssertionError: _OffsetZ is not an float: 5 #   but liftObj(5, myPolyCube)  could have worked
raiseObject(5.0, myCubeTranform,True) #OK#

For the validation of function arguments as a process in development, we can really modelize an Axis, from devs that check everything: every variables every time;  into those who delegates these tests to dev/user intelligence and API knowledge.
My advice is to be nearly at the middle of it. Python scripts are clear text file, readable, and easy understandable, so personally I don't not check often my arguments integrity.
Especially when you build a framework, with a lot of packages/modules navigation, your variable could be checked in chains through its living time if you implement an assert in every function/methods...
But, I always check my data when they are coming from a different context, I mean by that a user graphical interface (prompt, comboBox, etc.), when reading a file or accessing your selection (Does the user REALLY selected a joint ?), or any scene content (therefore, a "context stranger").

Well, in the last example, we will see how a function could be flexible in python, and dig into the notion of scope.
A SCOPE is the meaning of an new indentation (mandatory after a ':') , and represent a "living context" for all objects (variables) created in it.
The golden rule to remember is this tagging mechanic: when escaping a scope (therefore reaching a dedented line), the tags will be discarded (by the engine called Garbage collector).
When you create a variable in a scope (aka a level of indentation), your variable will 'live' through it, and 'die' when the scope die, so at its dedentation (there are exceptions, but the collector will always destroy your variables at the exit of a function/method).
We saw in looping lesson the 'for' and the 'while', but let's have a look of another loop type, without keyword this time, just a play with a function that can call itself:

def createCubes(DefaultName="Qube",MaxNumber=25):

  def createCubeAndGoOn():
    if len(<MaxNumber : #Like the while condition be REALLY SURE to reach your test unless it is infinite loop!
      #Here, VariableScopeRelated_tocreateCubesis still alive, as in any scope  'under' the first one (createCubes one)

createCubes(MaxNumber=10,DefaultName="Kube") #Creates 10 cubes

print VariableScopeRelated_tocreateCubes# Error: NameError: name 'VariableScopeRelated_tocreateCubes' is not defined #

Several comments on that one:
Please notice that you are allowed by python to create a function (and its scope) anywhere, and also in a class definition.
It is quite useful in the resistivity mode, which need often an starting function, and a recursive one.
Notice also that if you specify a keyword argument, you are no longer tied to the order of the argument.

Last, but not least, learn that you can define functions without specify arguments,  like that:

def doIt (*args):
  print type(args), args
doIt(1,"anything",1.0) #<type 'tuple'> (1, 'anything', 1.0)
def doIt2 (**Kwargs):
  print type(Kwargs),Kwargs
doIt2(arg1=2,arg2="anything") #<type 'dict'> {'arg1': 2, 'arg2': 'anything'}

*args: Tells python to pack all arguments into a TUPLE.
**Kwargs: Tells python to pack arguments into a DICT

Of course, you will to start the core of your function by analyzing the given tuple or dict.
... and that's all folks !