2

I can use a function's attribute to set a status flag, such as

def Say_Hello():
    if Say_Hello.yet == True:
        print('I said hello already ...')
    elif Say_Hello.yet == False:
        Say_Hello.yet = True
        print('Hello!')

Say_Hello.yet = False

if __name__ == '__main__':
    Say_Hello()
    Say_Hello()

and the output is

Hello!
I said hello already ...

However, when trying to put the function in a class, like

class Speaker:
    def __init__(self):
        pass

    def Say_Hello(self):
        if self.Say_Hello.yet == True:
            print('I said hello already ...')
        elif self.Say_Hello.yet == False:
            self.Say_Hello.yet = True
            print('Hello!')

    Say_Hello.yet = False

if __name__ == '__main__':
    speaker = Speaker()
    speaker.Say_Hello()
    speaker.Say_Hello()

There is this error:

Traceback (most recent call last):

  File "...func_attribute_test_class_notworking.py", line 16, in <module>
     speaker.Say_Hello()
  File "...func_attribute_test_class_notworking.py", line 9, in Say_Hello
    self.Say_Hello.yet = True
AttributeError: 'method' object has no attribute 'yet'

What is the proper way to use function's attribute in a class?

martineau
  • 119,623
  • 25
  • 170
  • 301
Jen-Feng Hsu
  • 161
  • 11
  • As I said in a comment to [my answer](https://stackoverflow.com/a/19327712/355230) to the question [Access a function variable outside the function without using “global”](https://stackoverflow.com/questions/19326004/access-a-function-variable-outside-the-function-without-using-global), it's "…essentially it's the same thing as using global variables" — and that's why using it on a class method doesn't work. @chepner's answer below shows ways to fix that. – martineau Nov 24 '20 at 18:18

1 Answers1

4

Speaker.Say_Hello and speaker.Say_Hello are two different objects. The former is the function defined in the body of the class statement:

>>> Speaker.Say_Hello
<function Speaker.Say_Hello at 0x1051e5550>

while the latter is an instance of method:

>>> speaker.Say_Hello
<bound method Speaker.Say_Hello of <__main__.Speaker object at 0x10516dd60>>

Further, every time you access speaker.Say_Hello, you get a different instance of method.

>>> speaker.Say_Hello is speaker.Say_Hello
False

You should just use self instead. Function attributes are more of an accidental feature that isn't specifically prohibited rather than something you should use, anyway.

class Speaker:
    def __init__(self):
        self._said_hello = False

    def Say_Hello(self):
        if self._said_hello:
            print('I said hello already ...')
        else:
            self._said_hello = True
            print('Hello!')

If you want to track if any instance of Speaker has called its Say_Hello method, rather than tracking this for each instance separately, use a class attribute.

class Speaker:
    _said_hello = False

    def Say_Hello(self):
        if self._said_hello:
            print('Someone said hello already ...')
        else:
            type(self)._said_hello = True
            print('Hello!')
martineau
  • 119,623
  • 25
  • 170
  • 301
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Please explain what you meant by "Further, every time you access `speaker.Say_Hello`, you get a *different* instance of method"? I ask because every time I print its value via `print(speaker.Say_hello)` I get exactly the same thing: namely something like `>` always the same address. Also `speaker.Say_Hello == speaker.Say_Hello` → `True`. – martineau Aug 11 '21 at 16:38
  • 1
    They are equal, but not identical. `speaker.Say_Hello is speaker.Say_Hello` should return false. At a guess, `method` objects compare equal if they contain references to the same objects and functions. Note that the address shown is the address of the instance object that the `method` references, not the address of the `method` itself. – chepner Aug 11 '21 at 16:44
  • `speaker.Say_Hello` is an instance of `method` that retains a reference to `speaker` and to `Speaker.Say_Hello`. When you call `speaker.Say_Hello()`, it uses those two references to make the call `Speaker.Say_Hello(speaker)`. – chepner Aug 11 '21 at 16:45
  • Thanks for the clarification. Am surprised it creates the bound method every time it's referenced. Not exactly what I would call "intuitive" (or efficient). `;¬)` – martineau Aug 11 '21 at 16:50
  • 1
    It all boils down to the descriptor protocol. It's not `Speaker` that creates the bound method, but the `__get__` method of the `function`-valued class attribute. In its fully expanded form, `speaker.Say_Hello` is short for `type(speaker).Say_Hello.__get__(speaker, type(speaker))`. `__get__` could theoretically be memoized so that the same `method` instance is returned for each use of `speaker` and `Say_Hello`. – chepner Aug 11 '21 at 16:53
  • And as an aside, the descriptor protocol is what makes class methods and static methods work differently from instance methods, based on the definitions of `function.__get__`, `classmethod.__get__`, and `staticmethod.__get__`. – chepner Aug 11 '21 at 16:57
  • (If you can't tell, I think the descriptor protocol is *really* nice. It's also the machinery behind `property`.) – chepner Aug 11 '21 at 16:58
  • I know about the descriptor protocol and *basically* how it works — just didn't realize the performance penalty being paid for the *niceness*. Now I can appreciate the significance of the code optimization that might be provided by doing `method = speaker.Say_Hello` then calling `method()` (if it's going to be done repeatedly, say in a loop) even more. – martineau Aug 11 '21 at 17:06