Skip to content

Commit fe22b56

Browse files
committed
write the first half of the MOP section
1 parent 513f74a commit fe22b56

1 file changed

Lines changed: 138 additions & 11 deletions

File tree

objmodel/chapter.md

Lines changed: 138 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -498,20 +498,147 @@ def _make_boundmethod(meth, self):
498498
The rest of the code does not need to be changed at all.
499499

500500

501-
Meta-object protocol
501+
Meta-object protocols
502502
----------------------
503503

504-
- common approach in more dynamic languages
505-
- pioneered in Lisp, Smalltalk, but common in most modern dynamically typed
506-
languages (Python, Ruby, Javascript, Lua, ...)
507-
- Primitive operations overridable by user code
508-
- hooks to modify how exactly the object machinery does things
509-
- Often use normal inheritance of the meta-hooks to get the base behavior (ie
510-
OBJECT has a __setattr__ with the default behavior)
504+
In addition to "normal" methods that are called directly by the program, many
505+
dynamic languages support *special methods*. These are methods that aren't meant
506+
to be called directly but will be called by the object system. In Python those
507+
special methods usually have names that start and end with two underscores, e.g.
508+
``__init__``. Special methods can be used to override primitive operations and
509+
provide custom behaviour for them instead. Thus they are hooks that tell the
510+
object model machinery how exactly to do certain things. Python's object model
511+
has dozens of special methods. XXX find the list
512+
513+
Historically meta-object protocols have been introduced by Smalltalk but even
514+
more strongly by the object systems for Common Lisp, such as CLOS, which is also
515+
where the name was coined (XXX footnote: The Art of the Meta-Object Protocol).
516+
517+
In this chapter we will add three such meta-hooks to our object model. They are
518+
used to fine-tune what exactly happens when reading and writing attributes. The
519+
special methods we will add first are ``__getattr__`` and ``__setattr__``, which
520+
follow closely the behaviour of Python's namesakes.
521+
522+
523+
Customizing Reading and Writing and Attribute
524+
++++++++++++++++++++++++++++++++++++++++++++++
525+
526+
The method ``__getattr__`` is called by the object model when the attribute that
527+
is being looked up currently is not found by normal means, i.e. neither on the
528+
instance nor on the class. It gets the name of the attribute being looked up as
529+
an argument. An equivalent of the ``__getattr__`` special method was part of
530+
early Smalltalk systems under the name ``doesNotUnderstand:`` (footnote: A.
531+
Goldberg, Smalltalk-80: The Language and its Implementation. Addison-Wesley,
532+
1983, page 61.)
533+
534+
The case of ``__setattr__`` is a bit different. Since setting an attribute
535+
always creates it, ``__setattr__`` is always called when setting an attribute.
536+
To make sure that a ``__setattr__`` method always exists, the ``OBJECT`` class
537+
has a definition of ``__setattr__``. This base implementation simply does what
538+
setting an attribute did so far, which is write the attribute into the object's
539+
dictionary. This also makes it possible for a user-defined ``__setattr__`` to
540+
call the base ``OBJECT.__setattr__`` in some cases.
541+
542+
A test for these two special methods is the following:
543+
544+
````python
545+
def test_getattr():
546+
# Python code
547+
class A(object):
548+
def __getattr__(self, name):
549+
if name == "fahrenheit":
550+
return self.celsius * 9. / 5. + 32
551+
raise AttributeError(name)
552+
553+
def __setattr__(self, name, value):
554+
if name == "fahrenheit":
555+
self.celsius = (value - 32) * 5. / 9.
556+
else:
557+
# call the base implementation
558+
object.__setattr__(self, name, value)
559+
obj = A()
560+
obj.celsius = 30
561+
assert obj.fahrenheit == 86 # test __getattr__
562+
obj.celsius = 40
563+
assert obj.fahrenheit == 104
564+
565+
obj.fahrenheit = 86
566+
assert obj.celsius == 30 # test __setattr__
567+
assert obj.fahrenheit == 86
568+
569+
# Object model code
570+
def __getattr__(self, name):
571+
if name == "fahrenheit":
572+
return self.read_attr("celsius") * 9. / 5. + 32
573+
raise AttributeError(name)
574+
def __setattr__(self, name, value):
575+
if name == "fahrenheit":
576+
self.write_attr("celsius", (value - 32) * 5. / 9.)
577+
else:
578+
# call the base implementation
579+
OBJECT.read_attr("__setattr__")(self, name, value)
580+
581+
A = Class("A", OBJECT, {"__getattr__": __getattr__, "__setattr__": __setattr__}, TYPE)
582+
obj = Instance(A)
583+
obj.write_attr("celsius", 30)
584+
assert obj.read_attr("fahrenheit") == 86 # test __getattr__
585+
obj.write_attr("celsius", 40)
586+
assert obj.read_attr("fahrenheit") == 104
587+
obj.write_attr("fahrenheit", 86) # test __setattr__
588+
assert obj.read_attr("celsius") == 30
589+
assert obj.read_attr("fahrenheit") == 86
590+
````
591+
592+
To pass these tests, the ``Base.read_attr`` and ``Base.write_attr`` methods
593+
needs to be changed as follows:
594+
595+
```` python
596+
class Base(object):
597+
...
598+
599+
def read_attr(self, fieldname):
600+
""" read field 'fieldname' out of the object """
601+
result = self._read_dict(fieldname)
602+
if result is not MISSING:
603+
return result
604+
result = self.cls._read_from_class(fieldname)
605+
if _is_bindable(result):
606+
return _make_boundmethod(result, self)
607+
if result is not MISSING:
608+
return result
609+
meth = self.cls._read_from_class("__getattr__")
610+
if meth is not MISSING:
611+
return meth(self, fieldname)
612+
raise AttributeError(fieldname)
613+
614+
def write_attr(self, fieldname, value):
615+
""" write field 'fieldname' into the object """
616+
meth = self.cls._read_from_class("__setattr__")
617+
return meth(self, fieldname, value)
618+
````
619+
620+
Reading an attribute is changed to call the ``__getattr__`` method with the
621+
fieldname as an argument instead of raising an error, if the method exists. Note
622+
that ``__getattr__`` (and indeed all special methods in Python) is looked up on
623+
the class only, instead of recursively calling
624+
``self.read_attr('__getattr__')``. The reason for that is that the latter would
625+
lead to an infinite recursion of ``read_attr`` if ``__getattr__`` is not defined
626+
on the object.
627+
628+
Writing an attribute is fully deferred to the ``__setattr__`` method. To make
629+
this work, ``OBJECT`` needs to have a ``__setattr__`` method that calls the
630+
default behaviour, as follows:
631+
632+
````python
633+
def OBJECT__setattr__(self, fieldname, value):
634+
self._write_dict(fieldname, value)
635+
OBJECT = Class("object", None, {"__setattr__": OBJECT__setattr__}, None)
636+
````
637+
638+
The behaviour of ``OBJECT__setattr__`` is like the previous behaviour of
639+
``write_attr``. With these modifications, the new test passes.
511640

512641

513-
- concretely: override what reading and writing an attribute means (__getattr__
514-
and __setatttr__)
515642
- override what binding a "method" means with __get__
516643

517644

@@ -678,7 +805,7 @@ at a fixed offset, getting rid of all dictionary lookups completely.
678805
Potential Extensions
679806
----------------------
680807

681-
- support for constructors and __new__
808+
- support for constructors, __getattribute__, __set__
682809
- Distinction between implementation code and user code
683810
- More meta methods
684811
- Multiple inheritance (easy!)

0 commit comments

Comments
 (0)