[python] dealing with python's loose typing responsibly, 'pythonically'

Returning to Maya/Python after years of MaxScript /C#, I am occasionally flustered by python’s weak typing ,
and unknowingly passing the wrong variable types.In another thread i complained bout it.

So, If I need my variable to be a string for example,
what is the best way to deal with the possibility that it may be a list or some other type?
Should I add if type(arg)==string type validation to every function?
Or do I just need to be super careful about my code?

Is there some general ‘pythonic’ approach to type error prevention or handling?

Python 3.5 brings in a type definition for definition arguments but sadly that does not help us when writing for Maya.

An alternative could be to use good function doc strings which outline the expected type etc, then write a decorator which compares the parameter variables to the expected types and raise an exception if something is amiss - that would keep it online with auto generated doc’s etc but would come at a runtime cost which would probably add up quite considerably.

Other than that, you could write a simple type check function and call it within the function.

Just to be slightly anal, Python is a strongly typed dynamic language… Worse than the PEP8 mafia? :x:

Seriously, for the problem with not knowing when PyMel/Maya return an array, you guess it would be fine to use an assert. I once had some utility that checked if something was an iterable (but not a string type). Likely when I was still doing Maya code on a regular basis :slight_smile:

In general I just try to use clear variable names (eg. use “node_name” or simply “name” when i expect a string, “node” when i mean an object representing actual node, also names like node_list or node_map for various collections). If you combine that with type hinting from doc strings and use an ide like pycharm, I think you’ll be fine. Often I don’t get around to doing the whole doc string thing and I’m not really bothered or confused anyway, even looking at code I haven’t looked at in a while.

The pythonic mantra is derived from the Robustness principle – you’re supposed to be clear about what you’re sending out but as tolerant as you can of what comes in. In most cases that boils down to ‘duck typing’ - using the presence of properties or methods you need: worrying about whether the object does what you need it to do and not it’s ‘type’ is way to stay sane.

From an implementation perspective there’s 3 levels of response:

When you really care, you can add ‘type safety’ – or more precisely, some kind of invariant checking – via decorators. Effectively you just add a decorator that describes the argument list you expect and raises an exception when the incoming arguments are the wrong kind. This is extra work – since you have to create and maintain the system – but its a good way to stop things when bad stuff has happened, capture a stack trace, and point yourself at the culprit who is somewhere farther down in the stack.

For most applications, it’s just easier to add the invariant checks you need inside your functions. You know the most about your expected conditions when you’re writing a function, so it’s nice to be able to just add a few lines right that raise meaningful errors if your expectations aren’t met. If you can be sure that conditions are right in the first few lines you can proceed without worrying – you end up saving a lot of much more complex in-line error checking in the guts of your actual work.

And of course for a lot of things you can just let it fail. If you’re expecting a string and you get something else, you will get one of a handful of errors - a few months of python and you’ll know them all. Raising your own exception is good when you want to tell your future self something that s/he might not know. It’s probably not worth the effort to write a few lines to say “argument X is None, should be string” when you’re going to get “NoneType has no attribute ‘upper’” anyway.

In a language where passing None instead a string is going to raise and exception (instead of a C++ style hard crash or BSOD when you somehow pass the wrong kind of memory bucket) type errors are just improperly policed logic errors. I kept stats on my error logs for two years, and only 6 percent of my errors were type errors – and almost all of them were actually caused by None Type has no XXX problems, which would have required null checks in the runtime code anyway. Implementing type safety would have saved maybe 1-2% of my bugs – and let’s not get into a flame war on all the bugs created by casting and all the workarounds you need to do what you need to do when ‘string’ and ‘unicode’ and ‘char’ are all different things :slight_smile: