Struggling with good method name conventions

I would be curious about opinions for how to come up with good conventions for method names, especially with regard to two aspects:

  1. when to use parentheses like obj.size() vs. make it look like an attribute obj.size (or obj.shape vs. obj.shape() etc. )

(Under the hood this would be a function in both cases and it would not be possible to assign a value to obj.size)

On the one hand, obj.size is shorter and the empty parentheses are really redundant.

On the other hand, if one has to use parentheses sometimes but not always in the API, it is easy to forget when to do it how, unless there is some totally simply rule that can always be applied to figure it out.

I remember that e.g. I have sometimes assumed that it would be used like an attribute when it would have needed parentheses which meant that I assigned a function instead of the value to some variable, potentially only noticing this a long time later.

  1. when to indicate a boolean function with a prefix is or is_

For example in my api I have obj1.within(obj2) if obj1 is an instance of SomeObject. Makes perfect sense without “is”. But there is also a class “ObjectsSet” which has a within method, objset.within(obj2) but this would return another object set with all the objects which are within obj2.

So in this case I tend to use the name “iswithin” for SomeObject and “within” for ObjectSet but in general, the method name often already indicates that this might be a boolean even without the “is” or “is_” (the underscore is maybe an additional hassle to type so I would tend to not use it).

What are your opinions on this, how do you decide on these issues in your own APIs?

Hi John,

You asked:

“1) when to use parentheses like obj.size() vs. make it look like an
attribute obj.size (or obj.shape vs. obj.shape() etc. )”

If the calculation is potentially expensive, then make it a method.

If it is always cheap, and “feels like” an attribute of an object, then
use property to make it look like an attribute.

Examples:

dog.tail feels like an attribute (a dog has a tail, which belongs to
the dog) so it should be an attribute even if it has to be computed each
time, assuming it is cheap to do so.

dog.count_fleas() may have to do a significant calculation, and it
doesn’t feel like an attribute (fleas aren’t part of the dog), so it
should be a method.

Thanks for this answer! I have read that heuristic before but it looks odd to me as a useful criterion, especially from the point of view of an API user who would maybe not be able to judge if the calculation is expensive or not. This may depend on the algorithm, the data available, and other things and change over time.
So if it is hard for the API user to quickly determine if parentheses should be added or not, they will have to look it up and waste time or potentially make mistakes.

Logically the whole thing is such a mess that I tend to more radical solutions and stick to one of these two:

  • make all methods that have zero arguments and will never have arguments look like an attribute (without parentheses)
  • make everything that returns a value have parentheses.

Of these I currently tend towards the first solution: why would we actually need parentheses anyways?

The decision of whether to use a method is not up to the API user, it is
up to the API designer, who should have a good idea of the range of
possible implementations.

You are concerned about the users being able to “quickly determine if
parentheses should be added or not”, but I think you are making a
mountain out of a mole-hill:

  • If the user is familiar with the API, they will know from experience.

  • If they are not familiar, they will have to look it up anyway, to find
    out what API they need to call. Is it str.upper or .uppercase or Upper
    or convert_to_UPPER or something else? That will tell them whether it is
    a method that needs parentheses or not.

  • If they are using an IDE or editor with code auto-completion, the
    editor will add the parentheses for them.

  • Getting help is only a few key strokes away if you have a Python
    interactive iterpreter open, which you should have:

    ‘’.upper
    <built-in method upper of str object at 0x7f8cbea663b0>

Now I know I need parentheses.

Everything is hard if you are not familiar with the API:

Is is widget.mogrify(a, b) or mogrified(widget, a, b) or
widget.mogrified(b, a) or something else? Worrying especially about
the need for parentheses or not is unnecessary.

You say:

“make all methods that have zero arguments and will never have arguments
look like an attribute (without parentheses)”

Things that look like attributes imply that:

(1) They are cheap. I don’t need to save the result for re-use.

for paragraph in document:
    para = format(paragraph, page.width)

That’s great, unless calculating the page width is slow, then your whole
program is slow.

(2) It also implies that I can set the attribute:

page.width = 500

If that was written page.width() I would know not even to try.

(3) Attributes should be things, not actions. A zero-argument method
is still an action, and should have parentheses:

# Yes, methods are actions.
dog.bark()
engine.start()

# No, looking up an attribute should not have side-effects.
# Actions should be methods, not attributes.
dog.bark
engine.start

You say:

“make everything that returns a value have parentheses.”

That’s were we get Java getters and setters. They are horrible:

# Python
print(sys.ps1)
sys.ps1 = '>>> '

# Java-style getters and setters
print(sys.get_ps1())
sys.set_ps1('>>> ')

“Of these I currently tend towards the first solution: why would we
actually need parentheses anyways?”

Because referring to a function or method is not the same as calling
that function or method.

If callables automatically got called without parentheses, you couldn’t
write this:

isinstance(value, float)

because the reference to float would automatically call it and give you
0.0, and you can’t call isinstance(value, 0.0).

You couldn’t pass functions or methods as key functions:

sorted(strings, str.upper)
# would automatically call str.upper and raise an exception

or as callback functions. Dependency injection would be horrible:

# Easy in Python
def myfunction(arg, randomness=random.random):
    pass

value = myfunction(20)  # Use the default source of randomness.
value = myfunction(20, myrandomness)  # Inject the dependency.

Functional programming techniques would be horrible:

map(func, values)  # would automatically call func and fail

It would make testing harder:

# This would fail:
for func in [str.lower, str.upper, str.index, str.find]:
    test(func, value, expected)

Methods and functions are objects. Grabbing a reference to them is not
the same as calling them.

Ruby allows you to leave out parentheses when calling zero argument
functions, and it is a real pain. It means that things which are
trivially easy in Python are hard and annoying in Ruby.

2 Likes

Thank you, you brought up some great points about why methods should still have those parentheses even if they will always be empty! That distinction between thing and action as also helpful, though it is obviously often not clear: “width” could be a thing associated with some object or something that requires a lot of time to calculate (although I tend to use memoization or caching in such cases anyways).

So you kind of convinced me that in most cases I actually do want parentheses. As far as I understand your main reservation against this is that we would get “java style getters and setters”. So I modify my rule to: I want parentheses unless the same property can be set to a value as well. For example “obj.size()” if size is a read-only value (which may or may not require some computation) but “obj.size” if it is also possible to do “obj.size = 10”.
If it is very clearly something that is an attribute/thing where we never need to pass a reference to the function it should probably not have parentheses either.

Thanks again for helping me understand all the aspects related to these choices! I just think that making the API easy to use with decisions I won’t regret later is important for any users of the API.

Cheers, john

1 Like