Changing the name of `__get__` to `__bind__`

it is not intuitive that __get__ would be used to bind a function to an object, and then we will have a bound method.

it goes against,
Explicit is better than implicit.
as to someone who is using __get__ for the first time, the intuition is that it would be used to retrieve a value, but one has to specifically go through the documentation to find that __get__ is used for binding purpose.

If descriptors were only callables that bind as methods when accessed as an attribute, then perhaps __bind__() would be a reasonable name for the method. But the descriptor protocol (i.e. __get__, __set__, and __delete__) is a means of implementing a computed attribute in general, which is not necessarily about binding a callable to the instance or type. For example, the __get__() method of a property named x might return the instance attribute _x.

1 Like

does it mean, that __get__ acts differently based on whether it works on a non-callable vs a callable?

I have recently come across it, so I do not know about it in much detail, but from what I have seen,

def func(self):
  return self
type(func)

gives,

function
class A:
  pass
a = A()

when we do,

func = func.__get__(a, A)
func

now, our function is bound to the instance a

<bound method func of <__main__.A object at 0x7f0285a4eb90>>

here, the __get__ is probably not intuitive for what it does, and should be renamed to __bind__

the other functionality that I am aware of is,

class Descriptor:
  def __set__(self, instance, value):
    instance.__dict__[self.name] = value

  def __get__(self, instance, owner):
    print(self, instance, owner, sep='\n')
    return instance.__dict__[self.name]
  
  def __set_name__(self, cls, name):
    self.name = name
class B:
  x = Descriptor()
b = B()

here, __get__ is used for accessing the x attribute of instance b.

b.x = 10
b.x

gives,

<__main__.Descriptor object at 0x7f0285a71ed0> 
<__main__.B object at 0x7f0285a71310> 
<class '__main__.B'>
10

here, __get__ is used for retrieving the value.

but how are these two functionalities related to each other, as they appear to be two completely different tasks achieved by the same __get__.

A descriptor implements a computed attribute, which is something that has a wide variety of possible uses. A function is a descriptor that creates a bound method when it’s accessed as an attribute of an instance of a type on which it’s set.

In practical terms, doing this would cause massive amounts of code to break. This would result in a huge amount of work for people with existing Python codebases (not least the standard library). Have you thought about whether the benefits of the change justify that, or how to mitigate the impact?

1 Like

You should read the descriptor guide:

https://docs.python.org/3/howto/descriptor.html

Descriptors are used to implement regular bound methods, class methods, static methods, properties, slots and so other kinds of members.

is there a concept of alias, so, both,

__get__
__bind__

do the same thing.
which would not break any code.
and to implement the binding thing that I wrote above, one could use __bind__ also.
but that would again break the rule,

There should be one and preferably only one obvious way to do it.

or if another functionality could be added to __bind__, which is commonly used, so that it is not just an alias.

If you need to manually bind a function as a method, use types.MethodType instead of the function object’s __get__() method. For example:

>>> obj = 42
>>> method = types.MethodType(lambda s: print(s), obj)
>>> method.__self__ is obj
True
>>> method()
42

yes, that could be a way to bind a function as a method.
but it is not found in most of the implementations. for example, I feel like the use of __get__ is a problem below.

class instance_method:
  def __init__(self, func):
    self.func = func
  
  def __get__(self, inst, cls):
    if inst is None:
      raise TypeError(f'{self.func.__name__} is only valid on instance')
    return self.func.__get__(inst, cls) # this use of __get__ is not intuitive that it is used for binding purpose
  # return self.func.__bind__(inst, cls)  this would appear to be much better
class GoodClass:
  @instance_method
  def simple_method(self):
    print('simple_method')
  
  def normal_method(self):
    print('normal method')
x = GoodClass()
GoodClass.simple_method()
TypeError: simple_method is only valid on instance