diff --git a/special-method-names.html b/special-method-names.html index 328b52a..8c1e874 100644 --- a/special-method-names.html +++ b/special-method-names.html @@ -16,6 +16,7 @@ td{vertical-align:top} th:first-child{width:10%;text-align:center} th,td,td pre{margin:0} td pre{padding:0;border:0} +td a:link, td a:visited{border:0} @@ -24,7 +25,7 @@ td pre{padding:0;border:0}
Difficulty level: ♦♦♦♦♦
-❝ FIXME ❞
— FIXME +❝ My specialty is being right when other people are wrong. ❞
— George Bernard Shaw
+If you’ve read the introduction to classes, you’ve already seen the most common special method: the
__init__()method. The majority of classes I write end up needing some initialization. + +
| Notes + | You Want… + | So You Write… + | And Python Calls… + |
|---|---|---|---|
| ① + | to initialize an instance + | x = MyCustomClass()
+ | x.__init__()
+ |
| ② + | the “official” representation as a string + | repr(x)
+ | x.__repr__()
+ |
| ③ + | the “informal” value as a string + | str(x)
+ | x.__str__()
+ |
| ④ + | the “informal” value as a byte array + | bytes(x)
+ | x.__bytes__()
+ |
| ⑤ + | the value as a formatted string + | format(x)
+ | x.__format__(format_spec)
+ |
__init__() method is called after the instance is created. If you want to control the actual creation process, use the __new__() method.
+__repr__() method should return a string that is a valid Python expression.
+__str__() method is also called when you print(x).
+bytes type was introduced.
+-__lt__ - covered in fractions.py -__le__ - covered in fractions.py -__eq__ - covered in ordereddict.py, fractions.py -__ne__ -__gt__ - covered in fractions.py -__ge__ - covered in fractions.py -__bool__ - covered in fractions.py ++In the Iterators chapter, you saw how to build an iterator from the ground up using the
__iter__()and__next__()methods. -(__cmp__ is gone) -
| Notes + | You Want… + | So You Write… + | And Python Calls… + |
|---|---|---|---|
| ① + | to iterate through a sequence + | iter(seq)
+ | seq.__iter__()
+ |
| ② + | to get the next value from an iterator + | next(seq)
+ | seq.__next__()
+ |
| ③ + | to create an iterator in reverse order + | reversed(seq)
+ | seq.__reversed__()
+ |
__iter__() method is called whenever you create a new iterator. It’s a good place to initialize the iterator with initial values.
+__next__() method is called whenever you retrieve the next value from an iterator.
+__reversed__() method is uncommon. It takes an existing sequence and returns an iterator that yields the items in the sequence in reverse order, from last to first.
++Computed Attributes
+ +FIXME not sure of the wording/depth required here because I don't yet know if I'm going to cover these in a previous chapter. Let's assume I'm not, and I can move the examples later if need be. + +
| Notes + | You Want… + | So You Write… + | And Python Calls… + |
|---|---|---|---|
| ② + | to get a computed attribute (unconditionally) + | x.my_property
+ | x.__getattribute__("my_property")
+ |
| ① + | to get a computed attribute (fallback) + | x.my_property
+ | x.__getattr__("my_property")
+ |
| ③ + | to set an attribute + | x.my_property = value
+ | x.__setattr__("my_property", value)
+ |
| ④ + | to delete an attribute + | del x.my_property
+ | x.__delattr__("my_property")
+ |
| ⑤ + | to list all attributes and methods + | dir(x)
+ | x.__dir__()
+ |
__getattribute__() method, Python will call it on every reference to any attribute or method name (except special method names, since that would cause an unpleasant infinite loop).
+__getattr__() method, Python will call it only after looking for the attribute in all the normal places. If an instance x defines an attribute foo, x.foo will not call x.__getattr__("foo"); it will simply return the already-defined value of x.foo.
+__setattr__() method is called whenever you assign a value to an attribute.
+__delattr__() method is called whenever you delete an attribute.
+__dir__() method is useful if you define a __getattr__() or __getattribute__() method. Normally, calling dir(x) would only list the regular attributes and methods. If your __getattr()__ method handles a foo attribute dynamically, dir(x) would not list foo as one of the available attributes. Overriding the __dir__() method allows you to list foo as an available attribute, which is helpful for other people who wish to use your class without digging into the internals of it.
+The distinction between the __getattr__() and __getattribute__() methods is subtle but important. I can explain it with two examples:
+
+
+>>> class Dynamo: +... def __getattr__(self, key): +... if key == "foo": ① +... return "Hi, I'm a custom value." +... else: +... raise AttributeError ② +... +>>> dyn = Dynamo() +>>> dyn.foo ③ +"Hi, I'm a custom value." +>>> dyn.foo = "Overridden!" +>>> dyn.foo ④ +'Overridden!'+
__getattr()__ method as a string. If the name is "foo", the method returns a value. (In this case, it’s just a hard-coded string, but you would normally do some sort of computation and return the result.)
+__getattr()__ method needs to raise an AttributeError exception, otherwise your code will silently fail when accessing undefined attributes. (Technically, if the method doesn’t raise an exception or explicitly return a value, it returns None, the Python null value. This means that all attributes not explicitly defined will be None, which is almost certainly not what you want.)
+__getattr__() method is called to provide a computed value.
+__getattr__() method will no longer be called to provide a value for dyn.foo, because dyn.foo is already defined on the instance.
+On the other hand, the __getattribute__() method is absolute and unconditional.
+
+
+>>> class SuperDynamo: +... def __getattribute__(self, key): +... if key == 'foo': +... return "Hi, I'm a custom value." +... else: +... raise AttributeError +... +>>> dyn = SuperDynamo() +>>> dyn.foo ① +"Hi, I'm a custom value." +>>> dyndyn.foo = "Overridden!" +>>> dyn.foo ② +"Hi, I'm a custom value."+
__getattribute__() method is called to provide a value for dyn.foo.
+__getattribute__() method is still called to provide a value for dyn.foo. If present, the __getattribute__() method is called unconditionally for every attribute and method lookup, even for attributes that you explicitly set after creating an instance.
+++ + +☞If your class defines a
__getattribute__()method, you probably also want to define a__setattr__()method and coordinate between them to keep track of attribute values. Otherwise, any attributes you set after creating an instance will disappear into a black hole. +
-__call__ -+
FIXME + +
| Notes + | You Want… + | So You Write… + | And Python Calls… + |
|---|---|---|---|
| ① + | to “call” an instance like a function + | my_instance()
+ | my_instance.__call__()
+ |
len(seq)
-seq.__len__()
+seq.__len__()
x in seq
-seq.__contains__(x)
+seq.__contains__(x)
-
-
+FIXME + +
| Notes + | You Want… + | So You Write… + | And Python Calls… + |
|---|---|---|---|
| + | to get a value by its key + | x[key]
+ | x.__getitem__("key")
+ |
| + | to set a value by its key + | x[key] = value
+ | x.__setitem__("key", value)
+ |
| + | to delete a key-value pair + | del x[key]
+ | x.__delitem__("key")
+ |
| + | to provide a default value for missing keys + | x[nonexistent_key]
+ | x.__missing__("nonexistent_key")
+ |
-__iter__ (*) - covered in iterators.html -__next__ (*) - covered in iterators.html -__reversed__ - covered in ordereddict.py -
x + y
-x.__add__(y)
+x.__add__(y)
x - y
-x.__sub__(y)
+x.__sub__(y)
x * y
-x.__mul__(y)
+x.__mul__(y)
x / y
-x.__truediv__(y)
+x.__truediv__(y)
x // y
-x.__floordiv__(y)
+x.__floordiv__(y)
x % y
-x.__mod__(y)
+x.__mod__(y)
divmod(x, y)
-x.__divmod__(y)
+x.__divmod__(y)
x ** y
-x.__pow__(y)
+x.__pow__(y)
x << y
-x.__lshift__(y)
+x.__lshift__(y)
x >> y
-x.__rshift__(y)
+x.__rshift__(y)
and
x & y
-x.__and__(y)
+x.__and__(y)
xor
x ^ y
-x.__xor__(y)
+x.__xor__(y)
or
x | y
-x.__or__(y)
+x.__or__(y)
-I broke this section out from the previous one because comparisons are not strictly the purview of numbers. Many datatypes can be compared — strings, lists, even dictionaries. If you’re creating your own class and it makes sense to compare your objects to other objects, you can use the following special methods to implement comparisons. + +
| Notes + | You Want… + | So You Write… + | And Python Calls… + |
|---|---|---|---|
| + | equality + | x == y
+ | x.__eq__(y)
+ |
| + | inequality + | x != y
+ | x.__ne__(y)
+ |
| + | less than + | x < y
+ | x.__lt__(y)
+ |
| + | less than or equal to + | x <= y
+ | x.__le__(y)
+ |
| + | greater than + | x > y
+ | x.__gt__(y)
+ |
| + | greater than or equal to + | x >= y
+ | x.__ge__(y)
+ |
| + | truth value in a boolean context + | if x:
+ | x.__bool__()
+ |
see http://docs.python.org/3.0/library/pickle.html: @@ -472,33 +680,52 @@ __reduce_ex__ (*)Classes That Can Be Used in a
-withBlock-__enter__ see http://docs.python.org/3.0/library/stdtypes.html#typecontextmanager -__exit__ +Python 3 supports the
withstatement, which allows you to access an object’s properties and methods without explicitly referencing the object every time. Awithblock defines a runtime context; you “enter” the context when you execute thewithstatement, and you “exit” the context after you execute the last statement in the block. -relevant excerpt from io.py: +Any class can be used in a
withblock; no special methods are required. The Python interpreter will automatically set up the runtime context and dispatch all the property and method lookups to your class. However, if you want your class to do something special upon entering or exiting a runtime context, you can define the following special methods. - def __enter__(self) -> "IOBase": # That's a forward reference - """Context management protocol. Returns self.""" - self._checkClosed() - return self +
| Notes + | You Want… + | So You Write… + | And Python Calls… + |
|---|---|---|---|
| + | do something special when entering a with block
+ | with x:
+ | x.__enter__()
+ |
| + | do something special when leaving a with block
+ | with x:
+ | x.__exit__()
+ |
This is how the [FIXME-xref] with file idiom works.
-relevant excerpt from http://www.python.org/doc/3.0/reference/datamodel.html#with-statement-context-managers
+
# excerpt from io.py:
+def _checkClosed(self, msg=None):
+ """Internal: raise an ValueError if file is closed
+ """
+ if self.closed:
+ raise ValueError("I/O operation on closed file."
+ if msg is None else msg)
-object.__enter__(self)
- Enter the runtime context related to this object. The with statement will bind this method’s return value to the target(s) specified in the as clause of the statement, if any.
-object.__exit__(self, exc_type, exc_value, traceback)
- Exit the runtime context related to this object. The parameters describe the exception that caused the context to be exited. If the context was exited without an exception, all three arguments will be None.
+def __enter__(self) -> "IOBase":
+ """Context management protocol. Returns self."""
+ self._checkClosed() ①
+ return self ②
-If an exception is supplied, and the method wishes to suppress the exception (i.e., prevent it from being propagated), it should return a true value. Otherwise, the exception will be processed normally upon exit from this method.
+def __exit__(self, *args) -> None:
+ """Context management protocol. Calls close()"""
+ self.close() ③
+__enter__() and an __exit__() method. The __enter__() method checks that the file is open; if it’s not, the _checkClosed() method raises an exception.
+__enter__() method should almost always return self — this is the object that the with block will use to dispatch properties and methods.
+with block, the file object automatically closes. How? In the __exit__() method, it calls self.close().
++☞The
__exit__()method will always be called, even if an exception is raised inside thewithblock. In fact, if an exception is raises, the exception information will be passed to the__exit__()method. See With Statement Context Managers for more details. +