View in #code_tips_and_tricks on Slack
@ldunham1: Question: How do you feel about using None as a 3rd option for a Boolean argument?
if bar is True:
elif bar is False:
@ldunham1 pinned a message to this channel.
[July 25th, 2018 12:56 AM] ldunham1: Question: How do you feel about using None as a 3rd option for a Boolean argument?
if bar is True:
elif bar is False:
@dangrover: To what end? Other than error handling.
@passerby: Don’t see why, a Boolean is value type and is supposed to be either true or false
@zhalktis: I sometimes resort to a similar pattern in maxscript; using ‘undefined’ in cases where a boolean value couldn’t be determined. Complicates code somewhat since then you end up testing for it all over the place, or everything explodes. (Not sure if Python silently accepting None as Falsey by accident would be any better)
For such cases, raising exceptions would probably make more sense in a reasonable language, but mxs exception handling leaves a lot to be desired. Could be an option in Python, though.
Unless you mean treating None as a ‘valid’ value. That sounds “too surprising”.
@jeff_hanna: Don’t fall in to the anti-pattern of the Tri-State Boolean!
Especially in Python where None can be used in non-equality checks.
def foo(bar = None):
if not bar or bar is False:
@passerby: in most languages if you wanted that you should use a enum
offers type safety that way too
in python i would likely just compare against ints i gave names
if its for exceptions reasons, like return null if it fails, i would personally just raise a exception
_sentinel = object()
if bar is True:
elif bar is False:
If you’re going to do something like this, you’d probably want to use a third default value that won’t reasonably passed in by the user
@covinator: I agree with passerby on this - enums! Err on the side of explicitness. Avoid inference with design.
@bob.w: Though yes, uses enums. if you’re on py3.4+ they are in the standard library. otherwise
pip install enum34
enum.IntEnum is great, you can treat it like an
int but it actually has a nice readable
repr so debugging isn’t half as dumb.
@alanweider: Looks like our code base is going to include both pyqt4 and pyqt5. We use a base class to define all our tools both inside and out of DCC apps. Would setting a class variable to nudge the use of one library or another depending on context make the most sense to ensure the correct libraries are loaded in this base class?
@bob.w: Take a look at
@padraig: use qt.py in that case
@bob.w: it acts as a wrapper so that you can write stuff without entirely knowing which backend you have.
@italic_: @italic_ pinned a message to this channel.
[July 25th, 2018 11:29 AM] bob.w: Take a look at
@alanweider: ah this is fantastic! Thanks
@padraig: you could also get a junior to do it manually they can learn from the code
@alanweider: absolutely. that is always an option
so all it takes here I suppose is adjusting the preferred order based on context
None is a legit option when the truth is actual indeterminacy – but that’s a rare case. You want to preserve the typical Python semantic value of
def foo(bar = None):
if bar is None:
print "I don't know"
None is great for knowing “i asked and nobody gave me an answer”
def foo (bar = None):
if bar is None:
None is very much the goto sentinel value. Where it gets squishy is when its a valid value and not just a standin for do the default.
if XXX is True is usually regarded as an antipattern in Python, so that people can write custom truth functions for different kinds of objects
@bob.w: Also, be careful, as in Py2,
True = False is completely valid.
So at that point
thing is True could be horribly wrong
@alanweider: oh gosh that’s right
@bob.w: A real monster would do
True, False = False, True
@ldunham1: Thanks for the replies, you’ve all covered cases for and against, although the general consensus errs towards enums. This is something I’m considering, although my hesitation is the addition of specific enum classes in various places and
decreasing comfortability of use for mid-level developers.
Casing point would be using knowledge of existing instances where this happens (i think of some Mel commands - the existence of a flag may result in behaviour derived from its Boolean value).
Whilst not necessarily the best way to handle it I’ve fallen into traps of too many enums.
You guys are monsters for even considering such a thing…
@bob.w: Py3 solves the
True, False = False, True problem by making them keywords
@jeff_hanna: False = .2
@ldunham1: Although, everything considered, I think I’m avoiding use of enums for the wrong reasons. (btw still on python 2.7 due to Autodesk versions)
@passerby: How do you fall into traps with enums
@bob.w: But I guess that’s what happens when you add
False in a point release.
@ldunham1: Too many
@passerby: It’s a typesafe way to do multiple choice
@ldunham1: Making it a pain in the ass for other Devs to comfortably use them when needed if pretty much everything else relies on standard types.
I’m also talking about use in a core set of common DCC utilities
So they’re used alot
@theodox: If you want python style enums, the usual hack is:
red = "red"
blue = "blue"
green = "green"
if (something == AnEnum.red) :
@passerby: I feel checking if a bool is none is more confusing then extra enum types
@bob.w: So the
enum library actually lets you use other types as a mixin. You can have a
StrEnum, or an
IntEnum. I make a bunch of enums based on our C-bindings and they get passed through just fine as an
@ldunham1: Cheers, although I think I typically omit the object inheritance to lighten the class
For Enum types I’ll use for non-boolean types.
@theodox: You can also just put the constants into a module, and import the module:
# constants mod
RED = 'red'
BLUE = 'blue'
if something == constants.RED:
@ldunham1: So then, my issue is related to using a common type of Enum to replace the behaviour whilst being clear about what it should do. Specifically refering to the use of the True/False/None
Although actually, thinking about providing an anti-example may have given me an answer
Or a variation of such.
@theodox: it’s a convention, a lot depends on what kind of use patterns you expect
@passerby: I know anyone familiar with a statically typed Lang will never expect a bool to be None since it’s a value type and not a ref or pointer
TRUE = True
FALSE = False
INVALD = None
@ldunham1: Yeah, thats the principle argument against atm. Its not standard/expected or generally, very clear
@bob.w - yeah, thats what im thinking
@bob.w: Then the function expects a
Test type, or you know a better name
@theodox: I would make the named constants into strings, however; otherwise debugging the code will be as unclear as the original example was
@bob.w: So the
enum library I linked earlier, actually gives a nice repr, with both the name of the flag, and the value
def find_attrs(node, user_defined=None):
if user_defined is None:
if user_defined is True:
if user_defined is False:
@theodox: but hes’ in 2.7, no?
@bob.w: its part of the standard library in 3.4+ and on pypi otherwise
@ldunham1: yeah, although I think we have a back ported version
@bob.w: Completely compatible, just wasn’t introduced until after the feature freeze went in
So the backport is pip installable at least.
And because it can represent an
int or a
str or whatever, it can be pretty easy to replace magic constants, or to act as a more readable wrapper for some C-exposed enum.
I use it a LOT for that last one. As we’ve got a lot of C-code that expects some enum, and we have to throw ints over the wall.
@theodox: yeah, I’d only bother if you needed to match somebody else’s enum structure
@bob.w: and debugging is pretty crappy when you go hey whats
thing.flag and get back
@ldunham1: This is all great. Really appreciate the help/advice. Although it probably seemed like a nonsense question, its from relatively good intentions. Just trying to make sure various levels of devs (maya backgrounds) feel comfortable with implementations and systems, without feeling like things are more hassle than they need to be.
@theodox: It’s also worth stepping back and considering if the api design is making too many assumptions – it’s much easier to test and maintain code if the number of expectations in each individual function is lower
In the face of ambiguity, refuse the temptation to guess, as the man says
special cases aren't special enough to break the rules
@ldunham1: Very fair points something I believe I should spend a little more time considering
@bob.w: I’m so bad at considering both those. At least when I’m in ‘lets put out the fire mode’
@ldunham1: There’s a mode other than that?!
@bob.w: I’ve heard tell of the mythical time where you can actually architect your code.
@ldunham1: Pfft fairytales are for kids Bob.
@bob.w: its probably similar to the ‘I’ve hidden under my desk so artists can’t find me’ time.
@ldunham1: I’ve seriously considered booking a meeting room once a week and remote working there just so I can get on with tasks.
Managed to get the hires in now so it’s much better
def find_attrs(node, include_base = True, include_custom = True):
if include_base and include_custom:
if include base:
return  # user apparently doesn't want anything
that makes the calling code have to be explicit about what it wants
@ldunham1: Yeah, that was an option I considered also, but there are a few more args to consider and I was wary or bloating the number of arts.
That being said, I think you guys were right anyway
Got so caught up in this idea of generality and ease of use that I missed some pretty important things
Explicit is better than implicit especially
@bob.w: Also, sometimes it makes sense to just have 2-3 functions.
No flags, no magic values.
@ldunham1: That’s true, but I’d expect alot of repetition when combined with additional common flags? Prefix, suffix, keyable etc
@theodox: the more the code reads like what it does, the better
an extra line of readable code is always better than a long string of position-and-convention driven yadda yadda
@bob.w: when in doubt. Do the opposite of the
ls( [object [object...]] , [absoluteName=boolean], [allPaths=boolean], [assemblies=boolean], [cameras=boolean], [containerType=string], [containers=boolean], [dagObjects=boolean], [defaultNodes=boolean], [dependencyNodes=boolean], [exactType=string], [excludeType=string], [flatten=boolean], [geometry=boolean], [ghost=boolean], [head=int], [hilite=boolean], [intermediateObjects=boolean], [invisible=boolean], [leaf=boolean], [lights=boolean], [live=boolean], [lockedNodes=boolean], [long=boolean], [materials=boolean], [modified=boolean], [noIntermediate=boolean], [nodeTypes=boolean], [objectsOnly=boolean], [orderedSelection=boolean], [partitions=boolean], [persistentNodes=boolean], [planes=boolean], [preSelectHilite=boolean], [readOnly=boolean], [recursive=boolean], [referencedNodes=boolean], [references=boolean], [renderGlobals=boolean], [renderQualities=boolean], [renderResolutions=boolean], [renderSetups=boolean], [selection=boolean], [sets=boolean], [shapes=boolean], [shortNames=boolean], [showNamespace=boolean], [showType=boolean], [tail=int], [templated=boolean], [textures=boolean], [transforms=boolean], [type=string], [undeletable=boolean], [untemplated=boolean], [uuid=boolean], [visible=boolean])
This is terrible.
@ldunham1: Haha completely agreed
@theodox: I was in the middle of typing out an example based on
@ldunham1: But I guess I presumed a very refined subset was an ideal middle ground of Devs used that level of accessibility.
Not arguing my original point anymore, just clarifying my initial thinking that made me question in the first place
But yeah, I think I will probably want to refine a couple of the functions to match up with the rest now.
Pulling away from my comfortable zone of Maya API and just being more explicit
@bob.w: So one thing that could work as well, is you have a
_ private function with a bunch of silly flags, mostly because you don’t want to duplicate code all over the place. But the public API, that is a group of more refined functions that you don’t have to think about when using.
@bob.w: So maybe a half dozen
list_selected etc… that all are backed by
@ldunham1: That’s actually what I’m doing for a large chunk of the functionality in common maya, _list_relatives for example,
@bob.w: But consumers of the API, they shouldn’t have to interact with a monster like
@theodox: In the case of ls, you could try minq:
@ldunham1: Especially when using the same API in max, mobu and fbx
I will take a look!
@theodox: oh – if you’re outside of maya it’s maya only
but the general idea would be pretty easy to copy to different platforms
the hard part for that kind of problem set it is mapping between similar-but-different concepts in different environments; the thing that makes FBX so icky
@ldunham1: The API was written for Maya Devs to easily use max, mobu and fbx. The ls behaviour was a bit of a pain, but we’ll worth the payoff when then same API works in each application
Yes, been there.
But now a very large chunk of the common operations are done and working as expected, it’s now about refining into something more.
@bob.w: Sounds wonderful, and hideously complicated.
@ldunham1: Haha, definitely complicated at points, but big emphasis on usability and convenience for Devs. I’ve also made sacrifices with optimisation in favour or transparency or simplicity for some of the more problematic methods.
Some core functionality coverage for querying data, but then the rest of it falls under - I typically want to do this operation/pattern regardless of app (copying/validating skinning, baking animation, constraining, exporting etc)
So provide a layer of common behaviour through common API
Will definitely admit it’s not perfect, or perhaps even the best approach. As expected, sometime it just won’t work as you want / need. So they get special consideration and perhaps exposed common at a higher level
@theodox: I’d imagine you’ll run into nasty stuff like different naming and hierarchy behavior for objects and so on
@ldunham1: For that, we pick a single naming convention that can work for all and stick with it. Each DCC implementation can choose to handle exceptions however it needs to in order to complete the task given to it
Sometimes it really doesn’t work, but they seem to be specific cases in which we can just provide a DCC specific functions to deal with it, and add the layer of abstraction.
So far things seem solvable and it’s been pretty nice to use. We do have a design to approach situations where we cannot get around it, but as I mentioned, for most of the common operations we perform - real general, workflow stuff, it’s working as expected
Phew, damn I sound defensive!
I posted a few times asking if anyone has done something like this on a similar scale, I don’t remember hearing too much back. I may end up seeing it biting me on the ass at some point!
@theodox: Good gdc talk at the end of it though!
@bob.w: Seriously, that is a huge undertaking. Way more than I would want to take on.
@theodox: An interesting problem though
@ldunham1: Haha after Mike’s talk regarding plugin based development (forward facing rigs), it felt like a natural thing to do.
I started it a few years ago, most of it has been personal time, up until it started to be used in production
@bob.w: The most terrifying point in any libraries life. When someone else starts to use it.
@ldunham1: Haha, that’s been some.of the more exciting times! I LOVE code reviews and feedback
Some of the biggest improvements in this project and my own personal development have come from them. Sometimes I’m too damn eager
@theodox: This sounds like a project that screams “unit test me”… in multiple environments…
@ldunham1: Hahaha, absolutely!