-1

This is a question about the general principle of introspection. When you are inside of a method, how can we make it possible to tell what class we are currently in?

We want something like as follows:

class FigNewton:
    def baz(self):
        current_class = MAGIC()

What would MAGIC be? The following is unacceptable, as we are working in an environment where call-time globals are not trusted. Globals at definition-time are trusted, but not globals at time-of-method-call.

class FigNewton:
    def baz(self):
        current_class = FigNewton

Why are globals not trusted at time-of-call? Because of shenanigans like the following:

class FigNewton:
    def baz(self):
        current_class = FigNewton
        print("banana")
        print("current_class ==", current_class.__name__)

import itertools
import string
print = lambda *args, print=print:\
    print(
        sum(
            map(
                    lambda stryng:\
                        int(''.join(
                            itertools.takewhile(
                                lambda ch: ch in string.ascii_lowercase
                            ,
                                stryng
                            )
                       ), base=36)
                ,
                    map(str, args)
                )
            , )
        )

print("apple")

obj = FigNewton()
FigNewton = "apple"
obj.baz()

The output is:

17995730
683010982
27999997387

Instead of the expected:

apple
banana
current_class == FigNewton

Below is more code demonstrating the problem:

class K0:
    print=print
    type=type
    def foo(self, *args):
        self.print(40 * "--")
        self.print('args == ', args)
        self.print("K0 version of foo is executing.")
        self.print("Are all references to class K0 lost?")
        self.print("Well, global label `K0` is ", self.type(K0).__qualname__, K0)
        # K0.__getattribute__(self, "whatever") ## ERROR!!!
        tsqn = self.type(self).__qualname__
        self.print(
            "type(self) is ", tsqn,
            ". Is that K0? ", ("yes" if tsqn == "K0" else "no"),
            sep=""
        )
        self.print(40 * "--")

##########################################################
def test(seed_class):
    Ks = [seed_class]
    for idx in (1, 2, 3):
        K = type("K{}".format(idx), (Ks[-1],), dict())
        Ks.append(K)

    class K4(Ks[-1]):
        def foo(self):
            print("K10 version of foo is executing")
            print("type(self) is ", type(self))

    # Begin messing up global namespace
    global K0
    K0 = 0
    # End messing up global namespace
    Ks.pop(0)
    for K in Ks:
        obj = K()
        obj.foo(1, 2, 3)
    return None
##########################################################
test(K0)

The output is:

--------------------------------------------------------------------------------
args ==  (1, 2, 3)
K0 version of foo is executing.
Are all references to class K0 lost?
Well, global label `K0` is  int 0
type(self) is K1. Is that K0? no
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
args ==  (1, 2, 3)
K0 version of foo is executing.
Are all references to class K0 lost?
Well, global label `K0` is  int 0
type(self) is K2. Is that K0? no
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
args ==  (1, 2, 3)
K0 version of foo is executing.
Are all references to class K0 lost?
Well, global label `K0` is  int 0
type(self) is K3. Is that K0? no
--------------------------------------------------------------------------------
Toothpick Anemone
  • 4,290
  • 2
  • 20
  • 42

2 Answers2

2

In the CPython reference interpreter, you can get the class a method was defined in by simply referencing __class__:

class FigNewton:
    def baz(self):
        current_class = __class__  # Assigns FigNewton, even if instance of subclass is used to invoke method

To my knowledge, this is an implementation detail of CPython (it's used to support no-arg super() calls, and I don't think it's mentioned anywhere other than in passing in the changelog and What's New documentation), so don't rely on it on other interpreters.

If you want the runtime type (so it reports the subclass type even when called in a method defined in the parent), use type(self) (or equivalently, self.__class__):

class FigNewton:
    def baz(self):
        current_class = type(self)  # Assigns FigNewton or a subclass thereof if instance of subclass is used to invoke method
ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • I want the parent class (or possibly parent-parent-parent class), not type(self). I want the class which the method is defined inside of, not the run-time type. – Toothpick Anemone Nov 01 '19 at 02:28
  • 1
    `__class__` is also in the [PEP](https://www.python.org/dev/peps/pep-3135/#specification) for 0-arg `super`, and in the [data model documentation](https://docs.python.org/3/reference/datamodel.html#creating-the-class-object). That said, I would consider its implementation-detail-ness somewhat fuzzy. It's not as clearly mandatory as having ints, but not as clearly optional as having the small int cache. – user2357112 Nov 01 '19 at 02:28
  • @martineau: If you're getting `__class__` and `self.__class__` mixed up, well, `__class__` is its own weird thing, and it behaves as this answer describes. If you're just proposing `self.__class__` as an alternative to `type(self)`, the answer already mentions that. – user2357112 Nov 01 '19 at 02:29
  • I meant the latter — and obvious missed that in your answer. Apologies. – martineau Nov 01 '19 at 02:31
  • @SamuelMuldoon: Yeah, then `__class__` (the raw name, not the attribute of `self`) has you covered. – ShadowRanger Nov 01 '19 at 10:48
-1

The following is one kludgy solution, but it works:

class FigNewton:
    def baz(self):
        current_class = type(self).FigNewton
FigNewton.FigNewton = FigNewton
Toothpick Anemone
  • 4,290
  • 2
  • 20
  • 42
  • 2
    Yeah, but if you're worried about someone reassigning `FigNewton`, it's not clear why you wouldn't also be worried about someone reassigning `FigNewton.FigNewton`. – user2357112 Nov 01 '19 at 02:31
  • (In fact, this approach introduces the possibility of having `FigNewton.FigNewton` shadowed by subclass definitions of `FigNewton`. This may be more likely than getting a global stomped, since people have a tendency to think literally everything should be okay to override in a subclass.) – user2357112 Nov 01 '19 at 02:33
  • @user2357112 I think it's more likely, relatively speaking, that someone would accidentally write `K0 = something` than they would accidentally write `K0.K0 = something` or accidentally override `K0` in a subclass of `K0` – Toothpick Anemone Nov 01 '19 at 11:49
  • Oh. I see now that you're an alt of [*that* guy](https://stackoverflow.com/users/6676101/toothpick-anemone). Look, all these attempted technical solutions you keep asking about require you to change how you write all your code, in the hopes that doing so might allow you to write buggy code without fixing it. It's a bad approach. – user2357112 Nov 02 '19 at 01:10
  • If you're having chronic problems with stomping over your variables, you need better tools for detecting the problem, rather than trying to make clobbering your variables okay. If you're writing scripts, use linters and other static checkers. If you're in interactive mode, maybe set a `sys.interactivehook` that monitors your globals and gives you a warning and an undo button if you screw up. – user2357112 Nov 02 '19 at 01:13
  • @user2357112 You say that instead of avoiding global variables, I should simply use some tool to help ensure that I never shadow a global name. It is much easier to shadow globals than you think. For example, `func = lambda x: print(kwargs)` In that example, `kwargs` is global. I want the lambda function to print the `kwargs` that exists when the function is defined not when the function is called. When the lambda function is called, `kwargs` may be completely different. `kwargs` is an extremely common label used in python programming. It gets "stomped" as you put it, all of the time. – Toothpick Anemone Nov 02 '19 at 11:31
  • I've never seen anybody use the name `kwargs` as a global. If you're doing that, that's really weird. – user2357112 Nov 02 '19 at 11:36
  • Are you under the impression that calling that `func` from inside `def func2(**kwargs): func(3)` would print the `kwargs` of the `func2` call or something? It doesn't matter what locals are in scope when `func` gets called. `func` will use the global `kwargs` from the global namespace where `func` was defined. It doesn't matter how many zillions of functions use `kwargs` as an argument name; it won't affect your (weird) global. – user2357112 Nov 02 '19 at 11:44