OOP is about code Re-Use
About looking up attributes in trees
2x python features that can be used to enforce constraints from derived
class to
base
class:
object -> (list, dict, tuple, set)
automatically
installs this class under the object class
.
class MyClass(object):
as it is done automatically in python 3
.SUMMARY:
type
.3.3.3.1. Metaclasses
constructed using type()
.body is executed in a new namespace
class name
is bound locally
to the result of type(name, bases, namespace)
.class creation process
can be customized
by passing the metaclass keyword argument
in the class definition line
, or by inheriting from an existing class
that included such an argument.MyClass
and MySubclass
are instances
of Meta
:class Meta(type):
pass
class MyClass(metaclass=Meta):
pass
class MySubclass(MyClass):
pass
When a class definition is executed, the following steps occur:
type()
is used;metaclass
is given and it is not an instance of type()
, then it is used directly as the metaclass;type()
is given as the explicit metaclass, or bases are defined, then the most derived metaclass is used.metaclass
(if any) and the metaclasses (i.e. type(cls)
) of all specified base classes
.metaclasses
.none
of the candidate metaclasses meets that criterion, then the class definition will fail with TypeError
.__prepare__
attribute, it is called as namespace = metaclass.__prepare__(name, bases, **kwds)
(where the additional keyword arguments, if any, come from the class definition).__prepare__
method should be implemented as a classmethod.__prepare__
is passed in to __new__
, but when the final class object is created the namespace is copied into a new dict.__prepare__
attribute, then the class namespace is initialised as an empty ordered mapping.PEP 3115 - Metaclasses in Python 3000
__prepare__
namespace hookexec(body, globals(), namespace)
.exec()
is that lexical scoping allows the class body (including any methods) to reference names
from the current and outer scopes
when the class definition occurs inside a function.__class__
reference described in the next section.Once the class namespace has been populated by executing the class body
, the class object
is created
by calling metaclass(name, bases, namespace, **kwds)
(the additional keywords passed here are the same as those passed to __prepare__
).
This class object is the one that will be referenced by
the zero-argument
form of super()
.
class is an implicit closure reference
created by the compiler if any methods in a class body refer to either __class__
or super
.
This allows the zero argument form of super()
to correctly identify the class being defined based on lexical scoping, while the class or instance that was used to make the current call is identified based on the first argument passed to the method.
CPython implementation detail:
__class__
cell is passed to the metaclass as a __classcell__
entry in the class namespace.type.__new__
call in order for the class to be initialised correctly.RuntimeError in Python 3.8
.When using the default metaclass type, or any metaclass that ultimately calls type.__new__
, the following additional customization steps are invoked after creating the class object:
type.__new__
method collects all of the attributes in the class namespace that define a __set_name__()
method;__set_name__
methods are called with the class being defined and the assigned name of that particular attribute;__init_subclass__()
hook is called on the immediate parent of the new class in its method resolution order.type.__new__
, the object provided as the namespace parameter is copied to a new ordered mapping and the original object is discarded.
__dict__
attribute of the class object.Links:
SUMMARY:
from abc import ABC
class.from abc import ABC, abstractmethod
class AbstractClassName(ABC):
@abstractmethod
def abstract_method_name(self):
pass
def concrete_method(self):
pass
Their current form in AbstractBag is appropriate for unordered collections,
but most collection classes are likely to be linear rather than unordered.
Note that the classes trend from the more general to the more specific in character, as your eyes move down through the hierarchy.
Now when a new collection type, such as ListInterface, comes along, you can create an abstract class for it, place that under
AbstractCollection, and begin with some data and methods ready to hand. The concrete
implementations of lists would then go under the abstract list class.
To create the AbstractCollection class, you copy code as usual from another module, in this case, AbstractBag.
You can now perform the following steps:
- You then remove the isEmpty
, __len__
, count and __add__
methods from AbstractBag.
- The implementation of AbstractCollection and the modification of AbstractBag are left
SUMMARY:
subclass
generally is a more specialized version
of its superclass
.superclass
is
also called the parent
of its subclasses
.subclass inherits all the methods and variables from its parent class
, as well as any of its ancestor classes.subclass specializes the behavior of its superclass
by modifying
its methods or adding
new methods
.call
a method in its superclass
by using the superclass name
as a prefix
to the method
.abstract class
serves as a repository of data
and methods
that are common
to a set of other classes
.not abstract
, they are
called concrete classes
.Abstract classes
are not instantiated
.Note also that AbstractSet, unlike AbstractBag, is not a subclass of AbstractCollection.
The reason for this is that AbstractSet does not introduce any new instance variables for data, but just defines additional methods peculiar to all sets.
You now explore the array-based implementation to clarify who is inheriting what from whom in this hierarchy.
Methods:
__and__()
__or__()
__sub__()
issubset
You can view a dictionary as a set of key/value pairs called entries.
However, a dictionary’s interface is rather different from that of a set.
As you know from using Python’s dict type, values are inserted or replaced at given keys using the subscript operator [].
The method pop removes a value at a given key, and the methods keys and values return iterators on a dictionary’s set of keys and collection of values, respectively.
The __iter__
method supports a for loop over a dictionary’s keys.
The method get allows you to access a value at a key or recover by returning a default value if the key is not present.
The common collection methods are also supported.
super()
is single inheritance.class SendEmailGetSet:
def __init__(self):
self.init_email = ''
@property
def email_addr(self):
return self.init_email
@email_addr.setter
def email_addr(self, new_email_addr):
self.init_email = new_email_addr
if __name__ == "__main__":
e = SendEmailGetSet()
print('email: ', e.email_addr)
e.email_addr = 'my_email@gmail.com'
print('email: ', e.email_addr)
class SendEmailGet:
def __init__(self):
self._addr = 'your_email@gmail.com'
@property
def email_addr(self):
return self._addr
if __name__ == "__main__":
e = SendEmailGet()
email = e.email_addr
print('email: ', email)
DESCRIPTION:
Are created by calling a class object.
Has a namespace implemented as a dictionary which is the first place in which attribute references are searched.
attribute by that name
, the search continues with the class attributes.__self__
attribute is the instance.See section Implementing Descriptors
for another way
in which attributes
of a class retrieved
via its instances may differ from the objects actually stored in the class’s __dict__
.
If no class attribute is found, and
the objects class has
a __getattr__()
method, that is called to satisfy the lookup.
Attribute assignments and deletions update the instance’s dictionary, never a class’s dictionary.
__setattr__()
or __delattr__()
method, this is called instead of updating the instance dictionary directly.Class instances can pretend
to be numbers
, sequences
, or mappings
if they have methods with certain special names.
See section Special method names.
Special attributes:
__dict__
is the attribute dictionary.__class__
is the instance’s class.from functools import wraps, partial
'''1. DEBUGS FUNCTIONS'''
def debug(func=None, *, prefix=''):
'''
https://youtu.be/sPiWg5jSoZI?list=PLYV45E82CBTECjpsWX5btprBVa9lWm1vZ&t=1201
- debugs functions by printing out nice error repr
'''
if func is None:
return partial(debug, prefix=prefix)
@wraps(func)
def wrapper(*args, **kwargs):
print(func.__qualname__)
return func(*args, **kwargs)
return wrapper
'''2. DEBUGS CLASSES (just methods, not @classmethods or @staticmethods)'''
def debugmethods(cls):
for key, val in vars(cls).items():
if callable(val):
setattr(cls, key, debug(val))
return cls
# https://youtu.be/sPiWg5jSoZI?list=PLYV45E82CBTECjpsWX5btprBVa9lWm1vZ&t=1387
@debugmethods
class Spam:
def a(self):
pass
def b(self):
pass
'''3. Meta Class that propogates through entire class hierarchy.
Can think of it almost as a genetic mutation.
https://youtu.be/sPiWg5jSoZI?list=PLYV45E82CBTECjpsWX5btprBVa9lWm1vZ&t=1589'''
class debugmeta(type):
def __new__(cls, clsname, bases, clsdict):
clsobj = super().__new__(cls, clsname, bases, clsdict)
clsobj = debugmethods(clsobj)
return clsobj
class Base(metaclass=debugmeta):
pass
class Ham(Base):
def a(self):
pass
def b(self):
pass
if __name__ == "__main__":
s = Spam()
s.a
x = Ham()
x.a
This produces the following output.
>>> import meta_programming_utube as mu
>>> q = mu.Ham
>>> q = mu.Ham()
>>> q.a
<bound method Ham.a of <meta_programming_utube.Ham object at 0x7f84952a3b50>>
>>> q.av
Traceback (most recent call last):
File "<input>", line 1, in <module>
q.av
AttributeError: 'Ham' object has no attribute 'av'
>>> q.be
Traceback (most recent call last):
File "<input>", line 1, in <module>
q.be
AttributeError: 'Ham' object has no attribute 'be'
>>>
__init__()
__new__()
), but before it is returned to the caller.
__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;
super().__init__([args...]).
__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__()
;
__call__()
DESCRIPTION:
Called to create a new instance of class cls. __new__()
is a static method (special-cased so you need not declare it as such) that takes the class of which an instance was requested as its first argument.
__new__()
should be the new object instance.
(usually an instance of cls)
.Typical implementations create a new instance of the class by invoking the superclass’s __new__()
method using super().__new__(cls[, ...])
with appropriate arguments and then modifying the newly-created instance as necessary before returning it.
If __new__()
is invoked during object construction and it returns an instance of cls, then the new instance’s __init__()
method will be invoked like __init__(self[, ...])
, where self is the new instance and the remaining arguments are the same as were passed to the object constructor.
If __new__()
does not return an instance of cls, then the new instance’s __init__()
method will not be invoked.
__new__()
is intended mainly to allow subclasses of immutable types (like int, str, or tuple) to customize instance creation.
__del__()
__del__()
method to postpone destruction of the instance by creating a new reference to it. This is called object resurrection. It is implementation-dependent whether del() is called a second time when a resurrected object is about to be destroyed;
__del__()
methods are called for objects that still exist when the interpreter exits.__str__()
object.__repr__()
in that there is no expectation that __str__()
return a valid Python expression: a more convenient or concise representation can be used.object.__repr__()
.class StringEx:
'''
in: se = StringEx('red')
print(se)
out: color is: red
'''
def __init__(self, color: str):
self.color = color
def __str__(self):
return f'color is: {self.color}'
se = StringEx('red')
print(se)
# out: color is: red
__repr__()
__repr__()
but not __str__()
, then __repr__()
is also used when an “informal” string representation of instances of that class is required.class ReprEx:
'''
in: re = ReprEx('red')
print(re)
out: color is: red
'''
def __init__(self, color: str):
self.color = color
def __repr__(self):
return f'color is: {self.color}'
# re = ReprEx('red')
# print(re)
# out: color is: red
__bytes__()
DESCRIPTION:
__format__()
DESCRIPTION:
str.format()
method, to produce a “formatted” string representation of an object.__format__()
, however most classes will either delegate formatting to one of the built-in types, or use a similar formatting option syntax.must be
a string
object.__format__
method of object itself raises a TypeError
if passed any non-empty string.object.__format__(x, '')
is now equivalent to str(x)
rather than format(str(x), '')
.__lt__(self, other)
__le__(self, other)
__eq__(self, other)
__ne__(self, other)
__gt__(self, other)
__ge__(self, other)
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)
.bool()
on the value to determine if the result is true
or false
.__eq__()
by using is, returning NotImplemented in the case of a false comparison: True if x is y else NotImplemented
.
__ne__()
, by default it delegates to __eq__()
and inverts the result unless it is NotImplemented.(x<y or x==y)
does not imply x<=y
.
functools.total_ordering()
.__hash__()
for some important notes on creating hashable objects which support custom comparison operations and are usable as dictionary keys.__lt__()
and __gt__()
are each other’s reflection, __le__()
and __ge__()
are each other’s reflection, and __eq__()
and __ne__()
are their own reflection.__hash__(self)
Summary:
__eq__()
method it should not define a __hash__()
operation either;
__eq__()
but not __hash__()
, its instances will not be usable as items in hashable collections.__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
).Description:
hash()
and for operations on members of hashed collections including set, frozenset, and dict.__hash__()
should return an integer.Example:
def __hash__(self):
return hash((self.name, self.nick, self.color))
__eq__()
and __hash__()
methods by default;
x.__hash__()
returns an appropriate value such that x == y
implies both that x is y and hash(x) == hash(y)
.__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)
.__eq__()
needs to retain the implementation of __hash__()
from a parent class, the interpreter must be told this explicitly by setting __hash__ = <ParentClass>.__hash__.
__eq__()
wishes to suppress hash support, it should include __hash__ = None
in the class definition.__hash__()
that explicitly raises a TypeError would be incorrectly identified as hashable by an isinstance(obj, collections.abc.Hashable)
call.Note:
__hash__()
values of str
and bytes
objects are “salted”
with an unpredictable random value.O(n2)
complexity.
PYTHONHASHSEED
.object.__bool__(self)
built-in
operation bool()
;
__len__()
is called, if it is defined
, and the object is considered true
if its result is nonzero
.__len__()
nor __bool__()
, all its instances are considered true
.object.__getattr__(self, name)
SUMMARY
.
name syntax.DESCRIPTION:
AttributeError
__getattribute__()
raises an AttributeError
because name is not an instance attribute or an attribute in the class tree for self;
__get__()
of a name property raises AttributeError
).return
the (computed) attribute value or raise
an AttributeError
exception.Note:
__getattr__()
is not called.__getattr__()
and __setattr__()
).__getattr__()
would have no way to access other attributes of the instance.__getattribute__()
method below for a way to actually get total control over attribute access.object.__getattribute__(self, name)
__getattr__()
, the latter will not be called unless __getattribute__()
either calls it explicitly or raises an AttributeError.object.__getattribute__(self, name)
.object.__getattr__
with arguments obj and name.object.__setattr__(self, name, value)
__setattr__()
wants to assign to an instance attribute, it should call the base class method with the same name, for example, object.__setattr__(self, name, value)
.For certain sensitive attribute assignments, raises an auditing event object.__setattr__
with arguments obj, name, value.
object.__delattr__(self, name)
__setattr__()
but for attribute deletion instead of assignment.object.__delattr__
with arguments obj and name.object.__dir__(self)
dir()
is called on the object.sequence must
be returned
.dir()
converts the returned sequence to a list
and sorts it
.__dict__
.object.__get__(self, instance, owner=None)
AttributeError
exception.__get__()
is callable with one or two arguments.__getattribute__()
implementation always passes in both arguments whether they are required or not.object.__set__(self, instance, value)
__set__()
or __delete__()
changes the kind of descriptor to a “data descriptor”.
object.__delete__(self, instance)
delete
the attribute
on an instance
instance of the owner class
.__objclass__
is interpreted by the inspect module as specifying the class
where this object was defined
dynamic class attributes
).callables
, it may indicate that an instance of the given type (or a subclass)
is expected or required as the first positional argumentobject.__slots__
string
iterable
sequence of strings
__slots__
reserves space for the declared variables
__dict__
and __weakref__
for each instance.Notes:
__slots__
, the __dict__
and __weakref__
attribute of the instances will always be accessible
.__dict__
variable, instances cannot be assigned new variables not listed in the __slots__
definition.dynamic assignment
of new variables is desired, then add __dict__
to the sequence of strings in the __slots__
declaration.__weakref__
variable for each instance, classes defining __slots__
do not support weak references
to its instances.__weakref__
to the sequence of strings in the __slots__
declaration.__slots__
are implemented at the class level by creating descriptors
for each variable name
.__slots__
;
__slots__
declaration is not limited to the class where it is defined.__slots__
declared in parents
are available in child classes
.
__dict__
and __weakref__
unless they also define __slots__
only contain names of any additional slots
).class defines
a slot
also defined in a base class
, the instance variable defined by the base class slot is inaccessible (except by retrieving its descriptor directly from the base class).
Nonempty
__slots__
does not work for classes derived from variable-length
built-in types such as int
, bytes
and tuple
.__slots__
.__slots__
, the dictionary keys will be used as the slot names.
inspect.getdoc()
and displayed in the output of help()
.__class__
assignment works only if both classes have the same __slots__
.raise TypeError
.__slots__
then a descriptor is created for each of the iterator’s values.
__slots__
attribute will be an empty iterator.__init_subclass__()
is called on that class.subclasses
.class decorators
, but where class decorators only affect the specific class they’re applied to, __init_subclass__
solely applies to future subclasses
of the class defining
the method
.classmethod object.__init_subclass__(cls)
cls
is then the new subclass
.normal instance method
, this method is implicitly
converted to a class method
.__init_subclass__
.__init_subclass__
, one should take out the needed keyword arguments and pass the others over to the base class, as in:class Philosopher:
def __init_subclass__(cls, /, default_name, **kwargs):
super().__init_subclass__(**kwargs)
cls.default_name = default_name
class AustralianPhilosopher(Philosopher, default_name="Bruce"):
pass
object.__init_subclass__
does nothing, but raises an error if it is called with any arguments.metaclass
is consumed by the rest of the type machinery
, and is never passed
to __init_subclass__
implementations.metaclass
(rather than the explicit hint) can be accessed as type(cls)
.object.__set_name__(self, owner, name)
class A:
x = C() # Automatically calls: x.__set_name__(A, 'x')
__set_name__()
will not be called automatically.__set_name__()
can be called directly:class A:
pass
c = C()
A.x = c # The hook is not called
c.__set_name__(A, 'x') # Manually invoke the hook
object.__call__(self[, args...])
https://stackoverflow.com/questions/3369640/when-is-using-call-a-good-idea
SUMMARY:
Called under the hood whenever you use something with parentheses func(10)
.
However, __call__
has quite a bit of competition in the Python world:
A regular named method, whose behavior can sometimes be a lot more easily deduced from the name. Can convert to a bound method, which can be called like a function.
A closure, obtained by returning a function that's defined in a nested block.
A lambda, which is a limited but quick way of making a closure.
Generators and coroutines, whose bodies hold accumulated state much like a functor can.
I'd say the time to use call is when you're not better served by one of the options above. Check the following criteria, perhaps:
Your object has state.
There is a clear "primary" behavior for your class that's kind of silly to name. E.g. if you find yourself writing run() or doStuff() or go() or the ever-popular and ever-redundant doRun(), you may have a candidate.
Your object has state that exceeds what would be expected of a generator function.
Your object wraps, emulates, or abstracts the concept of a function.
Your object has other auxilliary methods that conceptually belong with your primary behavior.
One example I like is UI command objects. Designed so that their primary task is to execute the comnand, but with extra methods to control their display as a menu item, for example, this seems to me to be the sort of thing you'd still want a callable object for.
DESCRIPTION:
“called”
as a function;
x(arg1, arg2, ...)
roughly translates to type(x).__call__(x, arg1, ...)
.container objects
.mappings (like dictionaries)
, but can represent other containers as well.sequence
or to emulate a mapping
;
sequence
, the allowable keys should be integers k
for which 0 <= k < N
where N
is the length of the sequence, or slice objects, which define a range of items
.keys()
, values()
, items()
, get()
, clear()
, setdefault()
, pop()
, popitem()
, copy()
, and update()
behaving similar to those for Python’s standard dictionary objects
.collections.abc
module provides a MutableMapping abstract base class to help create those methods from a base set of __getitem__()
, __setitem__()
, __delitem__()
, and keys().append()
, count()
, index()
, extend()
, insert()
, pop()
, remove()
, reverse()
and sort()
, like Python standard list objects.sequence types
should implement addition (meaning concatenation)
and multiplication (meaning repetition)
by defining the methods __add__()
, __radd__()
, __iadd__()
, __mul__()
, __rmul__()
and __imul__()
described below;
numerical operators
.mappings
and sequences
implement the __contains__()
method to allow efficient use of the in operator;
keys
;values
.__iter__()
method to allow efficient iteration through the container;
mappings
, __iter__()
should iterate through the object’s keys
;sequences
, it should iterate through the values
.object.__len__(self)
len()
.integer >= 0
.__bool__()
method and whose __len__()
method returns zero is considered to be false
in a Boolean context
.sys.maxsize
some features (such as len())
may raise OverflowError
.raising OverflowError
by truth value testing, an object must define a __bool__()
method.object.__length_hint__(self)
operator.length_hint()
.integer >= 0
.NotImplemented
, which is treated the same as if the __length_hint__
method didn’t exist at all.object.__getitem__(self, key)
SUMMARY:
["item"]
syntax.__getitem__
or __iter__
.__iter__
first, which returns an object that supports the iteration protocol with a __next__
method:
__iter__
is found by inheritance search, Python falls back on the __getitem__
indexing method,DESCRIPTION:
self[key]
.integers
and slice objects
.
__getitem__()
method.TypeError
may be raised
;
IndexError
should be raised
.key
is missing
(not in the container), KeyError
should be raised
.IndexError
will be raised for illegal indexes
to allow proper detection of the end of the sequence.__class_getitem__()
may be called instead of __getitem__()
.
__class_getitem__
vs __getitem__
for more details.object.__setitem__(self, key, value)
SUMMARY:
dict
DESCRIPTION:
self[key]
.__getitem__()
.__getitem__()
method.object.__delitem__(self, key)
self[key]
.__getitem__()
.__getitem__()
method.object.__missing__(self, key)
dict.__getitem__()
to implement self[key]
for dict subclasses when key is not in the dictionary.object.__iter__(self)
object.__reversed__(self)
new iterator object
that iterates over all the objects in the container in reverse order
.__reversed__()
method is not provided, the reversed() built-in will fall back to using the sequence protocol (__len__()
and __getitem__()
).__reversed__()
if they can provide an implementation that is more efficient than the one provided by reversed()
.(in and not in)
are normally implemented as an iteration through a container.
object.__contains__(self, item)
membership test operators
.
return true
if item is in self
, false otherwise
.mapping objects
, this should consider the keys
of the mapping rather than
the values
or the key-item pairs.__contains__()
:
__iter__()
,__getitem__()
object.add(self, other)
object.sub(self, other)
object.mul(self, other)
object.matmul(self, other)
object.truediv(self, other)
object.floordiv(self, other)
object.mod(self, other)
object.divmod(self, other)
object.pow(self, other[, modulo])
object.lshift(self, other)
object.rshift(self, other)
object.and(self, other)
object.xor(self, other)
object.__or__(self, other)
__truediv__()
.
__pow__()
should be defined to accept an optional third argument if the ternary version of the built-in pow() function is to be supported.object.radd(self, other)
object.rsub(self, other)
object.rmul(self, other)
object.rmatmul(self, other)
object.rtruediv(self, other)
object.rfloordiv(self, other)
object.rmod(self, other)
object.rdivmod(self, other)
object.rpow(self, other[, modulo])
object.rlshift(self, other)
object.rrshift(self, other)
object.rand(self, other)
object.rxor(self, other)
object.__ror__(self, other)
pow()
will not try calling __rpow__()
object.iadd(self, other)
object.isub(self, other)
object.imul(self, other)
object.imatmul(self, other)
object.itruediv(self, other)
object.ifloordiv(self, other)
object.imod(self, other)
object.ipow(self, other[, modulo])
object.ilshift(self, other)
object.irshift(self, other)
object.iand(self, other)
object.ixor(self, other)
object.__ior__(self, other)
x
is an instance
of a class
with an __iadd__()
method,
x += y
is equivalent to x = x.__iadd__(y)
.x.__add__(y)
and y.__radd__(x)
are considered, as with the evaluation of x + y
.a_tuple[i] += [‘item’]
raise an exception when the addition works?), but this behavior is in fact part of the data model.object.neg(self)
object.pos(self)
object.abs(self)
object.__invert__(self)
object.__float__(self)
complex()
, int()
and float()
.object.__index__(self)
convert
the numeric
object to an integer
object (such as in slicing
, or in the built-in bin()
, hex()
and oct()
functions).numeric
object is an integer type
.integer
.__int__()
, __float__()
and __complex__()
are not defined then corresponding built-in functions int()
, float()
and complex()
fall back to __index__()
.object.round(self[, ndigits])
object.trunc(self)
object.floor(self)
object.__ceil__(self)
round()
and math
functions trunc()
, floor()
and ceil()
.__round__()
all these methods should return the value of the object truncated to an Integral (typically an int)
.int()
falls back to __trunc__()
if neither __int__()
nor __index__()
is defined.