What do parenthesis do when calling classes and methods?

What is happening under the hood to lead to very different outputs when calling a method with the parentheses in different places?

In other words, why does a return the string and d a TypeError?

class Rules:
  def washing_brushes(self):
    return "Point bristles towards the basin while washing your brushes."

a = Rules().washing_brushes()
b = Rules.washing_brushes
c = Rules().washing_brushes 
d = Rules.washing_brushes()

Where a, b, c, and d return, respectively:

Point bristles towards the basin while washing your brushes.
<function Rules.washing_brushes at 0x7f0b76032d08>
<bound method Rules.washing_brushes of <__main__.Rules object at 0x7f0b76082f98>>
TypeError: washing_brushes() missing 1 required positional argument: 'self'

That’s not very under-the-hood though is it?

An expression describes a value

so for example the expression

1

evaluates to 1

similarly, a function

f

evaluates to … f

What would you do with a function? Call it, presumably. You now have a different expression, you did something with f

f()

The result is whatever f returns, and f probably returned something other than itself (though it could, nothing stopping it from doing so)

So when you’re deciding whether or not to call something … did you want the thing itself, or its return value?

The dots in your expressions aren’t any different from your parenthesis, you could omit those as well, they too, are operations on values

x.something

asks x for its something attribute

x

doesn’t, that’s x itself

so, if you have some value, then, what operations does that support, and which operations do you want to do to it? you wouldn’t pick operations at random would you? or if you do, don’t expect a particularly meaningful outcome

you’ve got a class.
what would you like to do with it?
usually what you’d do with a class is to create an instance of it. for example:

an_empty_list = list()

calling a class results in an instance of it.

lists_class = list

so now you’ve got two values, list() and list … those are really different things.
you could ask for attributes from them

list().something
list.something

what attribute does a list have? what attributes does the list class have?

what makes sense to do with the list class’s append attribute, and what about a list instance’s append attribute?

if you call a list’s append attribute what’ll happen is that the value you provide is added to that list

but if you do the same with the list class, then what list would this add to? it wouldn’t, there’s no list, only the idea of list, a type

23 Likes

If you have a function that returns a list:

def f():
    return []

then f.append doesn’t seem like a great idea, f is a function, it doesn not have an append attribute
but you could call f, you would then have a list: f() and if you have a list then you could ask that list for its append attribute: f().append and if you had something you wanted to append to that list then… you could call it f().append(f)

f().append
f.append
9 Likes

Okay, I think I understand:

  • a returns the string because I have called (created an instance of) Rules, then called washing_brushes on Rules, which returns whatever the washing_brushes method within the class returns.

  • b returns Rules.washing_brushes itself because I haven’t called anything.

  • c returns something which is basically telling me there is a method within the instance of Rules named washing_brushes.

  • d throws a TypeError because I have tried to call a method on Rules itself. Because there is no instance of Rules in the expression, nothing is passed into washing_brushes’ self parameter.

15 Likes

you and I already did this.

functools.partial

stuff = list()  # []
bound_append = partial(list.append, stuff)  # stuff.append
bound_append(1)
print(stuff)  # [1]
1 Like

And since I made the comparison between partial and method binding, I may as well show that functions can be used in a similar manner to classes - to create instances with fields and methods

Classes do this a bit more efficiently and provide nice things like iteration, indexing, calling, string conversion, operators … and you get a type for the value

from collections import namedtuple


def Counter():
    begin = 0

    def increment():
        nonlocal begin
        begin += 1

    def read():
        return begin

    # namedtuple provides attributes so that seems like an appropriate
    # representation, could be any container, including a function
    return namedtuple("Counter", ["increment", "read"])(increment, read)


c = Counter()
print(c.read())  # 0
c.increment()
c.increment()
c.increment()
print(c.read())  # 3


def Counter():
    begin = 0

    def increment():
        nonlocal begin
        begin += 1

    def read():
        return begin

    def get(msg):
        if msg == "read":
            return read
        if msg == "increment":
            return increment

    # just to show that a function can serve as a container/interface
    return get


c = Counter()
print(c("read")())  # 0
c("increment")()
c("increment")()
c("increment")()
print(c("read")())  # 3

One could also create the methods outside the function and bind them using partial similarly to a class… but those methods would no longer see the function scope, so one might want to use a container to store all the fields and provide this when binding a method

3 Likes

Following your example:

rules = Rules()
washing = partial(Rules.washing_brushes, rules)
print(washing)  # functools.partial(<function Rules.washing_brushes at 0x100e72950>, <__main__.Rules object at 0x100f65190>)
print(b) # <function Rules.washing_brushes at 0x100e72950>
print(c) # <bound method Rules.washing_brushes of <__main__.Rules object at 0x100e322d0>>

I thought washing and c would evaluate to the same thing because washing can also be expressed as Rules().washing_brushes. Why is there a difference?

If you call washing and c they’ll behave the same
There’s no reason why method binding would need to use partial, it probably shouldn’t. partial is a python function, but the binding is something that can be done in C code in the interpreter, it can likely do it far more efficiently and it likely wants to do some things specific to bound methods as well that partial doesn’t do
…So method binding is a highly specialized partial
Method binding is a language feature, while partial is just some function in a library, a toy in comparison, especially toy-like in python since python code doesn’t use it much

1 Like

I read through all your explanation and could you please explain more about these :

functools.partial

stuff = list()  # []
bound_append = partial(list.append, stuff)  # stuff.append  #specially this line
bound_append(1)
print(stuff)  # [1]

And this :

from collections import namedtuple

return namedtuple("Counter", ["increment", "read"])(increment, read) #specially this line


“explain more” really tells me nothing at all about what you want to know. you’d have to phrase a question, dig out what you are looking for.

1 Like

what this 2 line do

bound_append = partial(list.append, stuff)  # stuff.append  #specially this line

return namedtuple("Counter", ["increment", "read"])(increment, read) #specially this line



Take a look at namedtuple’s documentation I guess.

1 Like

in the list class , Why the first letter is not capital? like ‘List’ ?

During instantiation of the class we use braces at the end of the name of the class because we are constructing in term of object. So, in a, we are using correct syntactical way to instantiate the class and save the value to a variable.
But, in b, these are clearly syntax or type_errors. Here, Rules is not class and washing_brushes is not method.
And in c and d, again syntax error.

I understand where you’re coming from but a SyntaxError has a very specific meaning in Python. The examples in the first post are all valid Python syntax even if they don’t all do what you want in this case.

The breakdown in What do parenthesis do when calling classes and methods? - #2 by ionatan covers what’s going in these cases.

1 Like