When running dir() to DelphiVCL library or any DelphiVCL methods and properties in all previous sections, you might find many of Python’s built-in objects and properties. You can also read 7 Built-In Function Objects Inside The DelphiVCL Library which has some other built-in object functions which relate to the ones in this article.
In this post, you’ll learn more about built-in function objects inside the DelphiVCL library and their descriptions. Learning more about built-in function objects will allow you to easily build GUIs with Python Development Tools.
What does the __getattribute__ do?
Called unconditionally to implement attribute accesses for instances of the class. If the class also defines __getattr__(), the latter will not be called unless __getattribute__() either call it explicitly or raises an AttributeError. This method should return the (computed) attribute value or raise an AttributeError exception. To avoid infinite recursion in this method, its implementation should always call the base class method with the same name to access any attributes it needs, for example, object.__getattribute__(self, name).
In addition to bypassing any instance attributes in the interest of correctness, implicit special method lookup generally also bypasses the __getattribute__() method even of the object’s metaclass. Run this example in your PyScripter IDE:
print("Metaclass getattribute invoked")
class C(object, metaclass=Meta):
print("Class getattribute invoked")
c = C()
Here are the outputs:
Bypassing the __getattribute__() machinery in this fashion provides significant scope for speed optimisations within the interpreter, at the cost of some flexibility in the handling of special methods (the special method must be set on the class object itself in order to be consistently invoked by the interpreter).
What is the purpose of __getitem__?
Called to implement evaluation of self[key]. For sequence types, the accepted keys should be integers and slice objects. Note that the special interpretation of negative indexes (if the class wishes to emulate a sequence type) is up to the __getitem__() method. If the key is of an inappropriate type, TypeError may be raised; if of a value outside the set of indexes for the sequence (after any special interpretation of negative values), IndexError should be raised. For mapping types, if the key is missing (not in the container), KeyError should be raised.
What can we do with __gt__?
__gt__ is one of the so-called “rich comparison” methods. The correspondence between operator symbols and method names is as follows:
- x<y calls x.__lt__(y),
- x<=y calls x.__le__(y),
- x==y calls x.__eq__(y),
- x!=y calls x.__ne__(y),
- x>y calls x.__gt__(y), and
- x>=y calls x.__ge__(y).
There are no swapped-argument versions of these methods (to be used when the left argument does not support the operation but the right argument does); rather, __lt__() and __gt__() are each other’s reflection, __le__() and __ge__() are each other’s reflection, and __eq__() and __ne__() are their own reflection. If the operands are of different types, and the right operand’s type is a direct or indirect subclass of the left operand’s type, the reflected method of the right operand has priority, otherwise, the left operand’s method has priority. Virtual subclassing is not considered.
What does __hash__ do?
Called by built-in function hash() and for operations on members of hashed collections including set, frozenset, and dict. __hash__() should return an integer. The only required property is that objects which compare equal have the same hash value; it is advised to mix together the hash values of the components of the object that also play a part in comparison of objects by packing them into a tuple and hashing the tuple. For example:
return hash((self.name, self.nick, self.color))
If a class does not define an __eq__() method it should not define a __hash__() operation either; if it defines __eq__() but not __hash__(), its instances will not be usable as items in hashable collections. If a class defines mutable objects and implements an __eq__() method, it should not implement __hash__(), since the implementation of hashable collections requires that a key’s hash value is immutable (if the object’s hash value changes, it will be in the wrong hash bucket).
User-defined classes have __eq__() and __hash__() methods by default; with them, all objects compare unequal (except with themselves) and x.__hash__() returns an appropriate value such that x == y implies both that x is y and hash(x) == hash(y).
A class that overrides __eq__() and does not define __hash__() will have its __hash__() implicitly set to None. When the __hash__() method of a class is None, instances of the class will raise an appropriate TypeError when a program attempts to retrieve their hash value, and will also be correctly identified as unhashable when checking isinstance(obj, collections.abc.Hashable).
If a class that overrides __eq__() needs to retain the implementation of __hash__() from a parent class, the interpreter must be told this explicitly by setting __hash__ = <ParentClass>.__hash__.
If a class that does not override __eq__() wishes to suppress hash support, it should include __hash__ = None in the class definition. A class that defines its own __hash__() that explicitly raises a TypeError would be incorrectly identified as hashable by an isinstance(obj, collections.abc.Hashable) call.
Note: By default, the __hash__() values of str and bytes objects are “salted” with an unpredictable random value. Although they remain constant within an individual Python process, they are not predictable between repeated invocations of Python.
This is intended to protect against a denial-of-service caused by carefully chosen inputs that exploit the worst-case performance of a dict insertion, O(n^2) complexity. See this link for details.
Changing hash values affects the iteration order of sets. Python has never made guarantees about this ordering (and it typically varies between 32-bit and 64-bit builds).
More example on __hash__():
print(1 .__hash__() == hash(1))
print(type(1).__hash__(1) == hash(1))
print(type(int).__hash__(int) == hash(int))
Here are the outputs:
How does __init__ work?
Called after the instance has been created (by __new__()), but before it is returned to the caller. The arguments are those passed to the class constructor expression. If a base class has an __init__() method, the derived class’s __init__() method, if any, must explicitly call it to ensure proper initialization of the base class part of the instance; for example: super().__init__([args…]).
Because __new__() and __init__() work together in constructing objects (__new__() to create it, and __init__() to customize it), no non-None value may be returned by __init__(); doing so will cause a TypeError to be raised at runtime.
What does __init_subclass__ do?
This method is called whenever the containing class is subclassed. cls is then the new subclass. If defined as a normal instance method, this method is implicitly converted to a class method.
Keyword arguments that are given to a new class are passed to the parent’s class __init_subclass__. For compatibility with other classes using __init_subclass__, one should take out the needed keyword arguments and pass the others over to the base class, as in:
def __init_subclass__(cls, /, default_name, **kwargs):
cls.default_name = default_name
class AmericanPhilosopher(Philosopher, default_name="John Dewey"):
The default implementation object.__init_subclass__ does nothing, but raises an error if it is called with any arguments.
How does __iter__ work?
This method is called when an iterator is required for a container. If a class sets __iter__() to None, the class is not iterable, so calling iter() on its instances will raise a TypeError (without falling back to __getitem__()).
This method should return a new iterator object that can iterate over all the objects in the container. For mappings, it should iterate over the keys of the container. Iterator objects also need to implement this method; they are required to return themselves.