what is 'self'? (plus link to Trey's article on callability)
If we're ever at a loss for what to talk about, in this context, of Python teachers, I could recommend a list of default topics, one of which would always be... What is this 'self'? The self appears right when we introduce the class construct (keyword class). And then we say things like "Python provides it" or "Python fills it in". What is "it" then? "It" is "the instance". For some, things are starting to get muddy. For coders familiar with OOP style grammars i.e. they're coming from another language such as JavaScript or Java (quite different I realize), they've got a sense of it, from keyword 'this'. However Python is widely touted as a gateway language into OOP style thinking, so we get a lot of first timers. We owe them a clear picture of self. One andragogical technique comes straight from the docs: instance = C() C.method(instance) # <-- needs instance as argument is equivalent to: instance = C() instance.method() # <-- instance is "subject" as in subject.verb() ... of course we may add additional arguments at will, but lets keep it simple. In OOP, all the selves share the same template or class code, so how is Python to distinguish one self from another? It's not like each self is carrying around a copy of the method code. The model is more like having DNA in common (a shared genetic core). As in: class Creature: def eat(self, food): # <--- needs to know which self self.stomach.append(food) With: instance.eat("spaghetti") # <-- self known to Python we have enough info to figure out which self, and Python obligingly brings it forward, to fill the placeholder, usually 'self'. With: Creature.eat(instance, "spaghetti") # self supplied by the caller we're not giving enough distinguishing information with generic Creature, and so instance has to be provided, as the self. Further andragogical elaboration involves showing what I mean by "usually 'self'" i.e. self is not a keyword but a placeholder. Here I tend to go for a Chinese character substitute, e.g. for "Ego", to remind the learner of Unicode in addition. Emoji can't be Python names or I might go for one of them. * * * Another good article by Trey Hunner just came out. He's one of the more prolific Python teachers *that I know about* (an important caveat as I'm always missing *a lot*): https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/ He takes up the issue of "callables that are functions" versus "callables that are actually types (classes)". That can help with fine tuning one's sense of what a "type" is, plus callability in general is a bridge to decorators, and this article definitely goes there. Kirby
When you access an attribute of a class object (a dict), the class attribute is looked up by __get__(). the class's instance of that function attribute is 'bound'; it receives 'self' (an object reference) as its first argument. If you write your own __get__ (e.g. with functools.partial or functools.wrap), or try and assign a function to a class [instance], or create a @staticmethod or a @classmethod, you can more fully understand how methods receive self as their first argument. - https://docs.python.org/3/howto/descriptor.html#functions-and-methods - https://python-reference.readthedocs.io/en/latest/docs/dunderdsc/ : """ Descriptor Protocol In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol: __get__(), __set__(), and __delete__(). If any of those methods are defined for an object, it is said to be a descriptor. The default behavior for attribute access is to get, set, or delete the attribute from an object’s dictionary. For instance, a.x has a lookup chain starting with a.__dict__[‘x’], then type(a).__dict__[‘x’], and continuing through the base classes of type(a) excluding metaclasses. However, if the looked-up value is an object defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead. Where this occurs in the precedence chain depends on which descriptor methods were defined and how they were called. Note that descriptors are only invoked for new style objects or classes (ones that subclass object() or type()). The starting point for descriptor invocation is a binding, a.x. How the arguments are assembled depends on a: Direct Call The simplest and least common call is when user code directly invokes a descriptor method: x.__get__(a). Instance Binding If binding to a new-style object instance, a.x is transformed into the call: type(a).__dict__[‘x’].__get__(a, type(a)). Class Binding If binding to a new-style class, A.x is transformed into the call: A.__dict__[‘x’].__get__(None, A). Super Binding If a is an instance of super, then the binding super(B, obj).m() searches obj.__class__.__mro__ for the base class A immediately preceding B and then invokes the descriptor with the call: A.__dict__[‘m’].__get__(obj, obj.__class__). """ On Tuesday, April 16, 2019, kirby urner <kirby.urner@gmail.com> wrote:
If we're ever at a loss for what to talk about, in this context, of Python teachers, I could recommend a list of default topics, one of which would always be...
What is this 'self'?
The self appears right when we introduce the class construct (keyword class). And then we say things like "Python provides it" or "Python fills it in". What is "it" then? "It" is "the instance". For some, things are starting to get muddy.
For coders familiar with OOP style grammars i.e. they're coming from another language such as JavaScript or Java (quite different I realize), they've got a sense of it, from keyword 'this'.
However Python is widely touted as a gateway language into OOP style thinking, so we get a lot of first timers. We owe them a clear picture of self.
One andragogical technique comes straight from the docs:
instance = C() C.method(instance) # <-- needs instance as argument
is equivalent to:
instance = C() instance.method() # <-- instance is "subject" as in subject.verb()
... of course we may add additional arguments at will, but lets keep it simple.
In OOP, all the selves share the same template or class code, so how is Python to distinguish one self from another?
It's not like each self is carrying around a copy of the method code. The model is more like having DNA in common (a shared genetic core).
As in:
class Creature:
def eat(self, food): # <--- needs to know which self self.stomach.append(food)
With:
instance.eat("spaghetti") # <-- self known to Python
we have enough info to figure out which self, and Python obligingly brings it forward, to fill the placeholder, usually 'self'.
With:
Creature.eat(instance, "spaghetti") # self supplied by the caller
we're not giving enough distinguishing information with generic Creature, and so instance has to be provided, as the self.
Further andragogical elaboration involves showing what I mean by "usually 'self'" i.e. self is not a keyword but a placeholder.
Here I tend to go for a Chinese character substitute, e.g. for "Ego", to remind the learner of Unicode in addition. Emoji can't be Python names or I might go for one of them.
* * *
Another good article by Trey Hunner just came out. He's one of the more prolific Python teachers *that I know about* (an important caveat as I'm always missing *a lot*):
https://treyhunner.com/2019/04/is-it-a-class-or-a-function-its-a-callable/
He takes up the issue of "callables that are functions" versus "callables that are actually types (classes)".
That can help with fine tuning one's sense of what a "type" is, plus callability in general is a bridge to decorators, and this article definitely goes there.
Kirby
Direct Call The simplest and least common call is when user code directly invokes a descriptor method: x.__get__(a).
Should this be `a.__get__(x)`? On Tuesday, April 16, 2019, Wes Turner <wes.turner@gmail.com> wrote:
When you access an attribute of a class object (a dict), the class attribute is looked up by __get__(). the class's instance of that function attribute is 'bound'; it receives 'self' (an object reference) as its first argument.
If you write your own __get__ (e.g. with functools.partial or functools.wrap), or try and assign a function to a class [instance], or create a @staticmethod or a @classmethod, you can more fully understand how methods receive self as their first argument.
- https://docs.python.org/3/howto/descriptor.html#functions-and-methods - https://python-reference.readthedocs.io/en/latest/docs/dunderdsc/ :
""" Descriptor Protocol In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol: __get__(), __set__(), and __delete__(). If any of those methods are defined for an object, it is said to be a descriptor.
The default behavior for attribute access is to get, set, or delete the attribute from an object’s dictionary. For instance, a.x has a lookup chain starting with a.__dict__[‘x’], then type(a).__dict__[‘x’], and continuing through the base classes of type(a) excluding metaclasses.
However, if the looked-up value is an object defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead. Where this occurs in the precedence chain depends on which descriptor methods were defined and how they were called. Note that descriptors are only invoked for new style objects or classes (ones that subclass object() or type()).
The starting point for descriptor invocation is a binding, a.x. How the arguments are assembled depends on a:
Direct Call The simplest and least common call is when user code directly invokes a descriptor method: x.__get__(a).
Instance Binding If binding to a new-style object instance, a.x is transformed into the call: type(a).__dict__[‘x’].__get__(a, type(a)).
Class Binding If binding to a new-style class, A.x is transformed into the call: A.__dict__[‘x’].__get__(None, A).
Super Binding If a is an instance of super, then the binding super(B, obj).m() searches obj.__class__.__mro__ for the base class A immediately preceding B and then invokes the descriptor with the call: A.__dict__[‘m’].__get__(obj, obj.__class__). """
On Tuesday, April 16, 2019, kirby urner <kirby.urner@gmail.com> wrote:
If we're ever at a loss for what to talk about, in this context, of Python teachers, I could recommend a list of default topics, one of which would always be...
What is this 'self'?
The self appears right when we introduce the class construct (keyword class). And then we say things like "Python provides it" or "Python fills it in". What is "it" then? "It" is "the instance". For some, things are starting to get muddy.
For coders familiar with OOP style grammars i.e. they're coming from another language such as JavaScript or Java (quite different I realize), they've got a sense of it, from keyword 'this'.
However Python is widely touted as a gateway language into OOP style thinking, so we get a lot of first timers. We owe them a clear picture of self.
One andragogical technique comes straight from the docs:
instance = C() C.method(instance) # <-- needs instance as argument
is equivalent to:
instance = C() instance.method() # <-- instance is "subject" as in subject.verb()
... of course we may add additional arguments at will, but lets keep it simple.
In OOP, all the selves share the same template or class code, so how is Python to distinguish one self from another?
It's not like each self is carrying around a copy of the method code. The model is more like having DNA in common (a shared genetic core).
As in:
class Creature:
def eat(self, food): # <--- needs to know which self self.stomach.append(food)
With:
instance.eat("spaghetti") # <-- self known to Python
we have enough info to figure out which self, and Python obligingly brings it forward, to fill the placeholder, usually 'self'.
With:
Creature.eat(instance, "spaghetti") # self supplied by the caller
we're not giving enough distinguishing information with generic Creature, and so instance has to be provided, as the self.
Further andragogical elaboration involves showing what I mean by "usually 'self'" i.e. self is not a keyword but a placeholder.
Here I tend to go for a Chinese character substitute, e.g. for "Ego", to remind the learner of Unicode in addition. Emoji can't be Python names or I might go for one of them.
* * *
Another good article by Trey Hunner just came out. He's one of the more prolific Python teachers *that I know about* (an important caveat as I'm always missing *a lot*):
https://treyhunner.com/2019/04/is-it-a-class-or-a-function- its-a-callable/
He takes up the issue of "callables that are functions" versus "callables that are actually types (classes)".
That can help with fine tuning one's sense of what a "type" is, plus callability in general is a bridge to decorators, and this article definitely goes there.
Kirby
Or ``a.__get__('x')`` ? On Tuesday, April 16, 2019, Wes Turner <wes.turner@gmail.com> wrote:
Direct Call The simplest and least common call is when user code directly invokes a descriptor method: x.__get__(a).
Should this be `a.__get__(x)`?
On Tuesday, April 16, 2019, Wes Turner <wes.turner@gmail.com> wrote:
When you access an attribute of a class object (a dict), the class attribute is looked up by __get__(). the class's instance of that function attribute is 'bound'; it receives 'self' (an object reference) as its first argument.
If you write your own __get__ (e.g. with functools.partial or functools.wrap), or try and assign a function to a class [instance], or create a @staticmethod or a @classmethod, you can more fully understand how methods receive self as their first argument.
- https://docs.python.org/3/howto/descriptor.html#functions-and-methods - https://python-reference.readthedocs.io/en/latest/docs/dunderdsc/ :
""" Descriptor Protocol In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol: __get__(), __set__(), and __delete__(). If any of those methods are defined for an object, it is said to be a descriptor.
The default behavior for attribute access is to get, set, or delete the attribute from an object’s dictionary. For instance, a.x has a lookup chain starting with a.__dict__[‘x’], then type(a).__dict__[‘x’], and continuing through the base classes of type(a) excluding metaclasses.
However, if the looked-up value is an object defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead. Where this occurs in the precedence chain depends on which descriptor methods were defined and how they were called. Note that descriptors are only invoked for new style objects or classes (ones that subclass object() or type()).
The starting point for descriptor invocation is a binding, a.x. How the arguments are assembled depends on a:
Direct Call The simplest and least common call is when user code directly invokes a descriptor method: x.__get__(a).
Instance Binding If binding to a new-style object instance, a.x is transformed into the call: type(a).__dict__[‘x’].__get__(a, type(a)).
Class Binding If binding to a new-style class, A.x is transformed into the call: A.__dict__[‘x’].__get__(None, A).
Super Binding If a is an instance of super, then the binding super(B, obj).m() searches obj.__class__.__mro__ for the base class A immediately preceding B and then invokes the descriptor with the call: A.__dict__[‘m’].__get__(obj, obj.__class__). """
On Tuesday, April 16, 2019, kirby urner <kirby.urner@gmail.com> wrote:
If we're ever at a loss for what to talk about, in this context, of Python teachers, I could recommend a list of default topics, one of which would always be...
What is this 'self'?
The self appears right when we introduce the class construct (keyword class). And then we say things like "Python provides it" or "Python fills it in". What is "it" then? "It" is "the instance". For some, things are starting to get muddy.
For coders familiar with OOP style grammars i.e. they're coming from another language such as JavaScript or Java (quite different I realize), they've got a sense of it, from keyword 'this'.
However Python is widely touted as a gateway language into OOP style thinking, so we get a lot of first timers. We owe them a clear picture of self.
One andragogical technique comes straight from the docs:
instance = C() C.method(instance) # <-- needs instance as argument
is equivalent to:
instance = C() instance.method() # <-- instance is "subject" as in subject.verb()
... of course we may add additional arguments at will, but lets keep it simple.
In OOP, all the selves share the same template or class code, so how is Python to distinguish one self from another?
It's not like each self is carrying around a copy of the method code. The model is more like having DNA in common (a shared genetic core).
As in:
class Creature:
def eat(self, food): # <--- needs to know which self self.stomach.append(food)
With:
instance.eat("spaghetti") # <-- self known to Python
we have enough info to figure out which self, and Python obligingly brings it forward, to fill the placeholder, usually 'self'.
With:
Creature.eat(instance, "spaghetti") # self supplied by the caller
we're not giving enough distinguishing information with generic Creature, and so instance has to be provided, as the self.
Further andragogical elaboration involves showing what I mean by "usually 'self'" i.e. self is not a keyword but a placeholder.
Here I tend to go for a Chinese character substitute, e.g. for "Ego", to remind the learner of Unicode in addition. Emoji can't be Python names or I might go for one of them.
* * *
Another good article by Trey Hunner just came out. He's one of the more prolific Python teachers *that I know about* (an important caveat as I'm always missing *a lot*):
https://treyhunner.com/2019/04/is-it-a-class-or-a-function-i ts-a-callable/
He takes up the issue of "callables that are functions" versus "callables that are actually types (classes)".
That can help with fine tuning one's sense of what a "type" is, plus callability in general is a bridge to decorators, and this article definitely goes there.
Kirby
Perfect to bring in the Descriptor protocol, right when we're talking about 'self' and also (per Trey's article), the different species of callable, i.e. functions, methods (functions inside classes) and the classes themselves (also callables, passing through to __init__, no need for keyword 'new' as in Java or Javascript). The code for a Property class in: https://docs.python.org/3/howto/descriptor.html#properties is about my deepest dive in an introductory course (and a longish one at that). We looked at it last week. Tonight I look a little more at Descriptors so all this edu-sig work counts as warming up. This isn't "cheat sheet" level Python, that's for sure. The actual property class (a built-in) isn't actually implemented in pure Python. The code in the docs is like "if it were" (written in Python) and to prove they're not kidding, when I introduce one of those Circle types (ala Raymond Hettinger) with co-varying attributes (change the radius, area and circumference change; change the circumference, radius and area change... etc.) wherein at the top I go: from modelproperty import Property as property # <-- overwrite builtin property with doc simulation class Circle: def __init__(radius = 1): self.radius = 1 # trigger setter (this won't store directly to self.__dict__['radius'] thanks to @property) @property def radius(self): # one could do the recomputations upon getting but... return self._radius @radius.setter def radius(self, r): # it makes the most sense to update other dimensions when setting, no? self._radius = r self._area = pi * r * r self._circumference = 2 * pi * r and so on. Everything works the same. Even though we're using doc code instead. With Property in view, one can see how circle.radius = 3 is triggering circle.radius.__set__(3), because radius is now the name of a Property instance (thanks to decorator syntax), and the radius.__set__ method invokes a stored method fset The first @property is swallowing the immediately following radius method whole, and storing it in fget (a property attribute). The invocation of @radius.setter then calls a Property method: def setter(self, fset): return type(self)(self.fget, fset, self.fdel, self.__doc__) which cleverly returns a whole new Property instance, by keeping self.fget as it was, while taking in fset as the new kid on the block. The newer Property is now armed with what it needs, to make radius play well with the others (area and circumference). Kirby On Tue, Apr 16, 2019 at 12:37 PM Wes Turner <wes.turner@gmail.com> wrote:
When you access an attribute of a class object (a dict), the class attribute is looked up by __get__(). the class's instance of that function attribute is 'bound'; it receives 'self' (an object reference) as its first argument.
If you write your own __get__ (e.g. with functools.partial or functools.wrap), or try and assign a function to a class [instance], or create a @staticmethod or a @classmethod, you can more fully understand how methods receive self as their first argument.
- https://docs.python.org/3/howto/descriptor.html#functions-and-methods - https://python-reference.readthedocs.io/en/latest/docs/dunderdsc/ :
""" Descriptor Protocol In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol: __get__(), __set__(), and __delete__(). If any of those methods are defined for an object, it is said to be a descriptor.
The default behavior for attribute access is to get, set, or delete the attribute from an object’s dictionary. For instance, a.x has a lookup chain starting with a.__dict__[‘x’], then type(a).__dict__[‘x’], and continuing through the base classes of type(a) excluding metaclasses.
However, if the looked-up value is an object defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead. Where this occurs in the precedence chain depends on which descriptor methods were defined and how they were called. Note that descriptors are only invoked for new style objects or classes (ones that subclass object() or type()).
The starting point for descriptor invocation is a binding, a.x. How the arguments are assembled depends on a:
Direct Call The simplest and least common call is when user code directly invokes a descriptor method: x.__get__(a).
Instance Binding If binding to a new-style object instance, a.x is transformed into the call: type(a).__dict__[‘x’].__get__(a, type(a)).
Class Binding If binding to a new-style class, A.x is transformed into the call: A.__dict__[‘x’].__get__(None, A).
Super Binding If a is an instance of super, then the binding super(B, obj).m() searches obj.__class__.__mro__ for the base class A immediately preceding B and then invokes the descriptor with the call: A.__dict__[‘m’].__get__(obj, obj.__class__). """
participants (2)
-
kirby urner
-
Wes Turner