To a first approximation, if you care about object identity, you are
probably doing it wrong. There are exceptions of course, but generally
speaking object identity is not that important, especially for methods.
Why do you care that two method look-ups return the same object or not?
There is nothing in the language that promises that looking up a method
will always return the same object. Methods are wrappers around
functions, and are created on the fly, when and as needed. They could be
cached, but that would likely cost memory, for little or no benefit.
(I’m not sure whether anyone has actually investigated the cost versus
benefit of caching methods, or whether this is just an artifact of a
historical design. But in either case, what we have now is that method
objects are created as needed. Even if that changes in the future, it
doesn’t help you.)
But putting aside the design question of whether method objects should
be cached or not, you seem to have some misconceptions. Firstly, you
asked:
“a function is the same as itself, but a method is not?”
Methods are identical to themselves, like every other object:
a = instance.method
a is a # True
What differs is that the method lookup instance.method
may or may not
return the same object each time. There is no language promise either
way.
If you care about the nuts and bolts, you might like to read about the
descriptor protocol, which is the machinery behind a whole lot of
stuff in Python, such as properties (computed attributes), super(), and
various flavours of method.
https://docs.python.org/3/howto/descriptor.html
Descriptors are considered pretty advanced stuff, although perhaps not
as advanced as metaclasses. YMMV.
You also implied that Python functions are not “truly first-class”
objects, as in Lua. They are. Like every other object, they have a
class, they have identity, state and behaviour. You can pass them as
arguments, and return them from function calls. You can create new
functions on the fly. Aside from the syntactic forms (def statements and
lambda expressions), there is a FunctionType with a constructor, and
with a bit of work you can create new functions using that.
What are the missing, to be considered first class citizens?
You suggested:
“I am having to assign the methods to dummy names and then use
reflection to make references to them in a tuple created in the
initializer.”
I’m not really sure I understand what you are doing there. But to the
degree that I understand it, it sounds like you are making something
which is very easy far more complicated than it needs to be. Perhaps if
you show some code, we can be more helpful.
As far as methods, here are two ways to use them. Try these, and feel
free to ask any questions you may have.
a = []
b = []
bound_methods = (a.append, b.append)
bound_methods[0]('hello')
bound_methods[1]('world')
print(a, b)
# prints ['hello'] ['world']
Bound methods already know the instance that they apply to, so you don’t
need to provide an argument for the “self” parameter.
Or you can use unbound methods, which are actually just the raw,
unwrapped function object.
unbound_methods = (list.append, list.reverse)
unbound_methods[0](a, 101)
unbound_methods[0](b, 202)
unbound_methods[1](b)
print(a, b)
# prints ['hello', 101] [202, 'world']
Unbound methods don’t know what instance to operate on, so you have to
explicitly pass an instance which will be bound to the “self” parameter.