Spyder has an IPython Console, where lines of code can be input individually. Notice that each cell is numbered, by order of the command:
In [1]: 1
Out[1]: 1
In [2]: 2
Out[2]: 2
In [3]: 3.14
Out[3]: 3.14
Python can use single and double quotations to enclose a str
instance. However notice in the two cases below that, the output Out[4]
and Out[5]
which shows the formal string representation prefers single quotations:
In [4]: 'hello'
Out[4]: 'hello'
In [5]: "hello"
Out[5]: 'hello'
The \
is a special character in a str
and is an instruction to insert an escape character into the str
. \'
for example can be used to incorporate a quotation into the str
. The '
is otherwise recognised as a special character that is used to enclose the str
:
In [6]: 'The string is \'hello\''
Out[6]: "The string is 'hello'"
Notice that the formal representation in the output displays the str
enclosed in double quotations which is more readable. The reason Python supports both single and double quotations to enclose a str
is so
double quotations can be used to enclose a string literal.
If the object
, in this case a str
instance 'hello'
is assigned to the object
name text
using the assignment operator =
, notice that there is no Out[7]
instead, the object
is shown on the Variable Explorer:
In [7]: text = 'hello'
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
text | str | 5 | hello |
Notice that the ''
used to enclose the str
are not shown, only the text data within the str
is displayed, this is consistent to what is displayed when the str
is printed.
Numeric instances can also be assigned to object
names:
In [8]: whole_num = 1
In [9]: floating_point_num = 3.14
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
floating_point_num | float | 1 | 3.14 |
text | str | 5 | hello |
whole_num | int | 1 | 1 |
Notice that each object
has a different Type
.
Think of text
, the object
name as a label to the object
'hello'
. The label is used to retrieve the object
in In [10]
which is retrieved in Out[10]
:
In [10]: text
Out[10]: 'hello'
The assignment operator should be conceptualised from right to left. Essentially the expression on the right is evaluated and then assigned to the object
name on the left. The following line of code will evaluate text
, retrieve the value 'hello'
and this will be given the object
name text2
:
In [11]: text2 = text
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
floating_point_num | float | 1 | 3.14 |
text | str | 5 | hello |
text2 | str | 5 | hello |
whole_num | int | 1 | 1 |
Notice the object
name text
and text2
have the same value, this is because they are essentially two labels on the same object
and therefore the following is True
:
In [12]: text2 is text
Out[12]: True
Notice that text2
and whole_num
are each a different object
:
In [13]: text2 is whole_num
Out[13]: False
The del
keyword can be used to delete an object
name. This deletes the label that references the object
and not the object
itself:
In [14]: del text2
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
floating_point_num | float | 1 | 3.14 |
text | str | 5 | hello |
whole_num | int | 1 | 1 |
The object
, in this case the str
instance 'hello'
still exists because it has the other object
name (label) text
which can be used to retrieve it:
In [15]: text
Out[15]: 'hello'
If this is also deleted:
In [16]: del text
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
floating_point_num | float | 1 | 3.14 |
whole_num | int | 1 | 1 |
Then all object
names (labels or references to the object
) are removed and the object
is considered orphaned and it is automatically cleaned up by Python's garbage collection. When an object
name that does not exist is referenced, a NameError
displays:
In [17]: text
Traceback (most recent call last):
Cell In[17], line 1
text
NameError: name 'text' is not defined
To restart the kernel, clearing all variables and resetting the IPython console back to In [1]
in the main menu select Consoles
→ Restart Kernel
. This also has the keyboard shortcut Ctrl+.
.
It is sometimes more convenient to type exit
in the IPython console which will close the current console and then open up a new console. Input exit
:
In [17]: exit
In [1]:
A function is created using the def
keyword (which is similar to assignment):
In [1]: def
After the def
keyword is the function's object
name:
In [1]: def fun
After the function's object
name are ()
which are used to enclose optional input parameters, in this case none are provided:
In [1]: def fun()
A colon :
is used to begin a multiline code block. Pressing ↵
here will move onto the first line of the code block.
Each line in the code block is indented by 4 spaces:
In [1]: def fun():
line1
line2
Every function has a return statement, which is optionally used to return a value when the function is called:
In [1]: def fun():
return None
The default value is None
. Pressing ↵
here will move onto the next line of the code block.
In [1]: def fun():
return None
Pressing ↵
again at a blank line will execute the code block. The function is assigned to the object
name fun
but the code in the function is not executed. This can be seen on the Variable Explorer (if the Variable Explorer is configured to view modules and callables):
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
fun | function | 1 | function object |
The function can be referenced using its object
name:
In [2]: fun
Out[2]: <function __main__.fun()>
Here Out[2]
shows the function is defined in the __main__
namespace as fun
. Referencing the function does not execute the code in the functions code block, to execute the code in the code bloc, the function is called using parenthesis.
When open parenthesis is used, details about the input parameters and the functions docstring display.
Since the function was defined with no input arguments (also called input parameters) and no documentation was provided, the following docstring displays when the function is input with open parenthesis:
In [3]: fun(
# Docstring popup:
"""
fun()
No documentation available.
"""
As there are no input arguments shown in the docstring, this function should be called without parenthesis:
In [3]: fun()
In this case None
is returned so there is no Out[18]
. Let's make a function that has a return
value:
In [4]: def fun():
return 'hello'
Notice when this is called, there is an Out[4]
showing the return
value 'hello'
:
In [5]: fun()
Out[5]: 'hello'
A function with two input arguments can be created:
In [6]: def fun(input1, input2):
return 'hello'
When open parenthesis is used, details about the input arguments and the functions docstring display. In this case the function has two input arguments and these should be provided when calling the function:
In [7]: fun(
# Docstring popup:
"""
fun(input1, input2)
No documentation available.
"""
If the function is called without these arguments a TypeError
is flagged up:
In [7]: fun()
Traceback (most recent call last):
Cell In[7], line 1
fun()
TypeError: fun() missing 2 required positional arguments: 'input1' and 'input2'
The input arguments can be provided positionally:
In [8]: fun(1, 2)
Out[8]: 'hello'
Or provided as named parameters:
In [9]: fun(input1=1, input2=2)
Out[9]: 'hello'
Positional arguments must always be provided before named arguments:
In [10]: fun(1, input2=2)
Out[10]: 'hello'
Otherwise a SyntaxError
is flagged up:
In [11]: fun(input1=1, 2)
Cell In[11], line 1
fun(input1=1, 2)
^
SyntaxError: positional argument follows keyword argument
To input multiple commands in a single input use Ctrl+↵
. Then use ↵
to execute a cell:
In [12]: one = 1
two = 2
If one of the instance names for example one
is typed followed by a .
, a list of identifiers display:
In [13]: one.
# Available Identifiers Popup:
# -----------------------------
# 🔧 Functions:
# - bit_length()
# - conjugate()
# - from_bytes(bytes, byteorder, *, signed=False)
# - to_bytes(length, byteorder, *, signed=False)
# 📜 Properties:
# - denominator
# - imag
# - numerator
# - real
# 🛠️ Variables:
# - (No additional variables for `int` objects)
# -----------------------------
Data Model identifiers begin and end with a double underscore __
and are colloquially known as dunder identifiers:
In [13]: one.__
# Available Data Model Identifiers Popup:
# -----------------------------
# 🔧 Functions:
# - __abs__()
# - __add__(other)
# - __and__(other)
# - __bool__()
# - __ceil__()
# - __divmod__(other)
# - __eq__(other)
# - __float__()
# - __floor__()
# - __floordiv__(other)
# - __ge__(other)
# - __getattribute__(name)
# - __gt__(other)
# - __hash__()
# - __int__()
# - __invert__()
# - __le__(other)
# - __lshift__(other)
# - __lt__(other)
# - __mod__(other)
# - __mul__(other)
# - __ne__(other)
# - __neg__()
# - __or__(other)
# - __pos__()
# - __pow__(other[, mod])
# - __radd__(other)
# - __rand__(other)
# - __rdivmod__(other)
# - __rfloordiv__(other)
# - __rlshift__(other)
# - __rmod__(other)
# - __rmul__(other)
# - __ror__(other)
# - __round__([ndigits])
# - __rpow__(other)
# - __rrshift__(other)
# - __rshift__(other)
# - __rsub__(other)
# - __rtruediv__(other)
# - __rxor__(other)
# - __sub__(other)
# - __truediv__(other)
# - __xor__(other)
# 📜 Properties:
# - __class__
# - __doc__
# -----------------------------
__add__
(colloquially called dunder add) can be used from the int
instance one
and applied to the int
instance two
:
In [14]: one.__add__(two)
Out[14]: 3
Behind the scenes, __add__
is defined in the int
class and as part of the Python data model, controls the behaviour of the +
operator. As a user, the +
operator should preferentially be used over explictly using the data model method __add__
:
In [15]: one + two
Out[15]: 3
This can also be done using the int
instances 1
and 2
directly:
In [15]: 1 + 2
Out[15]: 3
Note for numbers that the .
is recognised as a decimal point and an attempt to access an identifier from the int
instance will lead to a SyntaxError
:
In [15]: 1.__add__(2)
Cell In[15], line 1
1.__add__(2)
^
SyntaxError: invalid decimal literal
This can be addressed using parenthesis:
In [16]: (1).__add__(2)
Out[16]: 3
The above operator can be used to create a function that has two int
input parameters and returns an int
:
In [16]: def add_nums(num1, num2):
return num1 + num2
This function can be called by supplying num1
to the int
instance 1
and num2
to the int
instance 2
:
In [17]: add_nums(num1=1, num2=2)
Out[17]: 3
The function call returned the value 3
which is not assigned to an object
name. This value assigned by the function call, can be assigned to a variable:
In [18]: return_val = add_nums(num1=1, num2=2)
This displays on the variable explorer:
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
fun | function | 1 | function object |
return_val | int | 1 | 3 |
Use of spacing is important in Python and is used to make the code more readable. Python Enhance Protocol 8, outlines the best practices for spacing. Typically spacing is included around operators and assignments. Compare the following:
# PEP8 Compliant
num = 1 + 2
# PEP8 Violation
num=1+2
However within a function call, spacing is instead used to emphasise the delimiter ,
which separates out the input parameters:
# PEP8 Compliant
add_nums(num1=1, num2=2)
# PEP8 Violation
add_nums(num1 = 1, num2 = 2)
add_nums(num1 = 1,num2 = 2)
add_nums(num1=1,num2=2)
When a function call is assigned to a return value, the assignment operator is emphasised. The assignment to the parameters in the function call are not emphasised in the same way as the delimiter within the function call is instead emphasised:
# PEP8 Compliant
return_val = add_nums(num1=1, num2=2)
# PEP8 Violation
return_val = add_nums(num1 = 1,num2 = 2)
return_val = add_nums(num1 = 1, num2 = 2)
return_val=add_nums(num1=1,num2=2)
When a function is defined, the input parameters can be assigned to a default value:
In [18]: def add_nums(num1=1, num2=2):
return num1 + num2
This means when the function is called without both of these parameters both default values will be taken:
In [19]: add_nums()
Out[19]: 3
Alternatively if only one other parameter is provided, the other will take on it's default value:
In [19]: add_nums(num1=4)
Out[19]: 6
In [20]: add_nums(num2=7)
Out[20]: 8
The first parameter can be provided positionally or as seen earlier as a named parameter:
In [21]: add_nums(4)
Out[21]: 6
The /
is used to specify positional-only parameters. Any parameters to the (left) of the /
is positional only:
In [22]: def add_nums(num1=1, num2=2, /, num3=3, num4=4):
return num1 + num2 + num3 + num4
This means the following is valid:
In [23]: add_nums(4, 5, num4=6)
Out[23]:
But the following is invalid flagging up a TypeError
:
In [24]: add_nums(4, num2=5, num4=6)
Traceback (most recent call last):
Cell In[11], line 1
add_nums(4, num2=5, num4=6)
TypeError: add_nums() got some positional-only arguments passed as keyword arguments: 'num2'
In [25]: exit
So far all the code has been input line by line. Spyder also has a script editor. If a new script file (also known as a module) is created it has the following default code:
# -*- coding: utf-8 -*-
"""
Created on Fri Oct 11 22:20:00 2024
@author: phili
"""
The line at the top of the file is a #
which indicates a comment. Comments are ignored by the Python Interpreter and only serve to give the user information about the code. In this case informing the user that the standard coding: utf-8 is used.
On Linux system files written in Python, often include a shebang at the top of a Python script file:
#!/usr/bin python3
Note that there is no space between #!
. The shebang is displayed in the IDE as a comment but is used by the Linux Terminal to select the Python Interpreter, which in this case is an instruction to use the system Python.
The triple quotations """
enclose a multiline comment, which effectively becomes the modules docstring.
If the variable num
is assigned and the function inch2cm
is defined in the script file:
# -*- coding: utf-8 -*-
# module1.py
"""
Created on Fri Oct 11 22:20:00 2024
@author: phili
"""
num = 1
def inch2cm(inches):
cm = inches * 2.54
return cm
Saved as module1.py
. In order to make this tutorial more readable, the file name is also included as a comment at the top of the script file.
If the script file is run in Spyder. Notice that the IPython console displays:
In [1]: %runfile C:/Users/phili/OneDrive/Documents/module1.py --wdir
And all the code is executed. Therefore the variable num
and function fun
display on the Variable Explorer and can be used in the IPython console, like before:
In[2]: inch2cm(2)
Out[2]: 5.08
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
inch2cm | function | 1 | function object |
num | int | 1 | 1 |
An import
statement can also be used to import module1.py
:
In [3]: import module1
Notice that module1
displays on the Variable Explorer. If the following is typed in the IPython console:
In [4]: module1?
Type: module
String form: <module 'module1' from 'c:\\users\\phili\\onedrive\\documents\\module1.py'>
File: c:\users\phili\onedrive\documents\module1.py
Docstring:
Created on Fri Oct 11 22:20:00 2024
@author: phili
Notice that the file is mentioned and the docstring shown in the triple quotes is mentioned. When module1.
is input, the identifiers from module1
display:
In [5]: module1.
# Available Data Model Identifiers for `module1.`:
# -------------------------------------
# 🔧 Functions:
# - inch2cm(inches)
# 🛠️ Variables:
# - num
# -------------------------------------
The identifiers num
and inch2cm
can be accessed from module1
:
In [5]: module1.num
Out[5]: 1
In [5]: module1.inch2cm(3)
Out[5]: 7.62
Look at module1.num
, notice that the name to the left hand side of the .
is the parent name, in this case the script file, module1
. On the right hand side is the object
name, in this case num
that was assigned within the parent module1
.
This syntax can be conceptualised by using a file path within Windows. For example:
C:\Windows\System32\regedt32.exe
To avoid confusion, file extensions which use the .
are dropped:
C:\Windows\System32\regedt32
Then the separator backslash \
is replaced with a dot .
:
C:.Windows.System32.regedt32
For clarity C:
will be renamed ROOT
:
ROOT.Windows.System32.regedt32
So the regedt32
application is within the System32
folder, which is itself within the Windows
folder which itself is found on ROOT
.
Now for simplification a reference to the Documents
folder will be made. Consider the two files:
~\Documents\text.txt
~\Documents\folder\text.txt
Notice although the file name is the same, these are two separate files as they are found in different locations. Once again dropping the file extension .txt
and replacing the separator \
with a .
:
~.Documents.text
~.Documents.folder.text
For clarity ~
will be renamed USERPROFILE
:
USERPROFILE.Documents.text
USERPROFILE.Documents.folder.text
Returning to the identifiers:
num # '__main__' namespace
module1.num # 'module1' namespace
These identifiers are in separate namespaces (conceptualise the namespace as folder).
Therefore when the identifier num
in the __main__
namespace is reassigned:
In [6]: num = 2
The identifier num
in the module1
namespace remains unchanged:
In [7]: module1.num
Out[7]: 1
The module also has a number of data model identifiers which can be seen if the following is input:
In [8]: module1.__
# Available Identifiers for `module1.`:
# -------------------------------------
# 📜 Properties:
# - __name__ (module's name)
# - __file__ (path to the module file)
# - __package__ (module's package name)
# - __loader__ (object that loads the module)
# - __spec__ (module's specification)
# - __doc__ (module documentation)
# - __cached__ (path to cached bytecode)
__doc__
(dunder doc) is a data model attribute that is automatically generated from the """
in a file.
In [8]: module1.__doc__
Out[8]: '\nCreated on Fri Oct 11 22:20:00 2024\n\n@author: phili\n'
The \
is an instruction to insert an escape character into a str
, in this case \n
which means a newline. If this attribute is printed using the print
function, the escape characters will be processed:
In [9]: print(module1.__doc__)
Created on Fri Oct 11 22:20:00 2024
@author: phili
Notice when this attribute is printed, there is no Out[9]
because there is no return
value.
Each function in the script file, should also have its own docstring. Notice when """
is input under the functions definition, the following template is generated:
def inch2cm(inches):
"""
Parameters
----------
inches : TYPE
DESCRIPTION.
Returns
-------
cm : TYPE
DESCRIPTION.
"""
cm = inches * 2.54
return cm
This can be filled in:
def inch2cm(inches):
"""
Converts inches to cm.
Parameters
----------
inches : float
Dimension in inches
Returns
-------
cm : float
Dimensions in cm
"""
cm = inches * 2.54
return cm
If a new console is started, module1
is imported and the inch2cm
function is input with open parenthesis, this docstring displays:
In [9]: exit
In [1]: import module1
In [2]: module1.inch2cm(
# Docstring popup:
"""
inch2cm(inches)
Converts inches to cm.
Parameters
----------
inches : float
Dimension in inches
Returns
-------
cm : float
Dimension in cm
"""
The data model __file__
attribute (dunder file) gives the physical file path of the module. Earlier the \
was discussed to be a special character in a Python str
which is an instruction to insert an escape character, it is also the default file separator on Windows... Therefore two \\
are seen in the str
, the first \
an instruction to insert an escape character, the second \
stating that the escape character to be inserted is \
itself:
In [2]: module1.__file__
Out[2]: 'c:\\users\\phili\\onedrive\\documents\\module1.py'
When printed the escape characters are processed:
In [3]: print(module1.__file__)
c:\users\phili\onedrive\documents\module1.py
Notice again this attribute is printed, there is no Out[3]
because there is no return
value.
A functions return
statement and a print
statement are often confused by beginners. Compare the two functions:
In [4]: def fun1():
return 'hello'
In [5]: def fun2():
print('hello')
When both functions are called the result is very similar:
In [6]: fun1()
Out[6]: 'hello'
In [7]: fun2()
hello
However notice the Out[6]
and the quotations enclosing the str
, when fun1
is called. Out[7]
does not show because nothing has been returned when fun2
is called. The quotations enclosing the str
have been processed when printing and are not shown.
When assignment is used:
In [8]: text1 = fun1()
In [9]: text2 = fun2()
hello
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
text1 | str | 5 | hello |
text2 | None | 1 | NoneType object |
Notice there is no Out[8]
because the value returned from the fun1
call has been returned to the object
name text1
, this value can be seen in the Variable Explorer. Notice when fun2
is called, because it includes a print
statement, the str
is printed (once again with the escape characters processed). Notice that in the Variable Explorer that text2
has the value None
.
When a return
statement is not supplied, the default value is None
. In other words, the following are equivalent:
def fun2():
print('hello')
def fun2():
print('hello')
return
def fun2():
print('hello')
return None
In [10]: exit
The data model __name__
attribute (dunder name) gives the namespace being used by the module. There is a subtle difference when a module is imported versus when it is executed. When the following script is created:
# module1.py
print(__name__)
print(__file__)
If the script file is directly executed:
In [1]: %runfile C:/Users/phili/OneDrive/Documents/module1.py --wdir
__main__
c:\users\phili\onedrive\documents\module1.py
In [2]: exit
Notice that the namespace is the __main__
namespace.
If a second module is created that imports module1
:
# module2.py
import module1
When module2
is run, all the code in module1
, which is imported, is executed:
%runfile C:/Users/phili/OneDrive/Documents/module2.py --wdir
module1
c:\users\phili\onedrive\documents\module1.py
In [2]: exit
Notice that the namespace for module1
is now module1
because module2
is now the __main__
namespace.
module1
can be updated to:
# module1.py
if __name__ == '__main__':
print('__main__ namespace')
else:
print('module1 namespace')
The if
, else
code blocks essentially perform a check to see if the module is the '__main__'
namespace, i.e. if the module is being executed directly or if the module has been imported:
In [1]: %runfile C:/Users/phili/OneDrive/Documents/module1.py --wdir
'__main__' namespace
In [2]: import module2
module1
'module1' namespace
This check is normally used to run additional test code when developing a module. For example if module1
is updated to:
# module1.py
x = 1
y = 2
z = x + y
if __name__ == '__main__':
print(f'z={z}')
Now notice when this module is directly executed, the additional test code is ran showing the print statement:
In [2]: exit
In [1]: %runfile C:/Users/phili/OneDrive/Documents/module1.py --wdir
z = 3
If the module is instead imported, notice that the additional test code, in this case the print statement is not shown:
In [2]: exit
In [1]: import module1
Generally when a module is imported, any code and object
assigned or defined in the module is assumed to be working as expected and therefore no additional debug statements are required. This is usually the case when using Python's standard modules as an end user.
Note the Python interpreter will usually skip reimporting a module that has already imported. For this reason, the command exit
was used to exit the kernel and open a new kernel:
In [3]: exit
If module1
is updated to:
# module1.py
__all__ = ['num1', 'num3', 'num5']
num1 = 1
num2 = 2
num3 = 3
num4 = 4
num5 = 5
Individual identifiers can be imported from the module into __main__
using the following commands in the IPython Console:
In [1]: from module1 import num1
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
num1 | int | 1 | 1 |
In [2]: exit
In [1]: from module1 import num1, num2
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
num1 | int | 1 | 1 |
num2 | int | 1 | 2 |
The following *
import (* meaning all) can be used:
In [2]: exit
In [1]: from module1 import *
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
num1 | int | 1 | 1 |
num3 | int | 1 | 3 |
num5 | int | 1 | 5 |
Notice in the Variable Explorer, all the identifiers defined in the list __all__
were imported.
*
imports are generally discouraged as it makes it harder to trace where identifiers came from. For example if module1.py
and module2.py
are updated to:
# module1.py
__all__ = ['num1', 'num3', 'num5']
num1 = 1
num2 = 2
num3 = 3
num4 = 4
num5 = 5
# module2.py
__all__ = ['num1', 'num2']
num1 = 6
num2 = 7
num3 = 8
num4 = 9
num5 = 10
And now if the following is input in the IPython console:
In [2]: exit
In [1]: from module1 import *
In [2]: from module2 import *
The Variable Explorer looks like:
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
num1 | int | 1 | 6 |
num2 | int | 1 | 7 |
num3 | int | 1 | 3 |
num5 | int | 1 | 5 |
Notice that the identifier num1
was reassigned when the *
import was used from module2
. This may have produced an error, as the user may have expected to use num1
from module1
instead of module2
.
If instead both modules are imported, there is no ambiguity:
In [3]: exit
In [1]: import module1
In [2]: import module2
In [3]: module1.num1
Out[3]: 1
In [4]: module2.num1
Out[4]: 6
In [5]: exit
A package is a folder with one or more Python modules. Within the package is the initialisation module which is called __init__.py
. If a folder called module3
is created and the file structure of the modules within Documents is:
Documents
-
module1.py
-
module2.py
-
module3
- __init__.py
- submodule1.py
- submodule2
- __init__.py
The three script files within module3 have the following code:
module3
- __init__.py
# __init__.py in module3
print('Module Initialisation File')
module3
- submodule1.py
# submodule1.py in module3
print('submodule1 File')
module3
-
submodule2
- __init__.py
# __init__.py in submodule2 of module3
print('submodule2 Initialisation File')
If module3
is imported in the IPython console, the print statement within the modules initialisation file is shown:
In [1]: import module3
Module Initialisation File
In [2]: exit
If submodule1
is imported in the IPython console, the print statement within the modules initialisation file (__init__.py
) is shown as well as the print statement within the submodule file (submodule1.py
):
In [1]: import module3.submodule1
Module Initialisation File
submodule1 File
In [2]: exit
This can also be imported in the following way:
In [1]: from module3 import submodule1
Module Initialisation File
submodule1 File
In [2]: exit
If submodule2
is imported in the IPython console, the print statement within the modules initialisation file (__init__.py
) is shown as well as the print statement within the initialisation file of the submodule (__init__.py
in the subfolder submodule2):
In [1]: import module3.submodule2
Module Initialisation File
submodule2 Initialisation File
In [2]: exit
Both submodules can be imported from module3
:
In [1]: from module3 import submodule1, submodule2
Module Initialisation File
submodule1 File
submodule2 Initialisation File
Notice the print
statement in each of the three script files is shown.
The data model attribute __package__
(dunder package) gives the package or parent folder the module belongs to, in the case of submodule1
it is module3:
In [2]: submodule1.__package__
Out[2]: 'module3'
In the case of submodule2
it is the submodule2 folder within module3:
In [3]: submodule2.__package__
Out[3]: 'module3.submodule2'
In [4]: exit
The module can be imported to an alias:
In [1]: import module1 as mod1
The alias is normally shorter than the modules name and is used for the sake of brevity when a large number of identifiers are used from the module:
In [2]: mod1.num1
Out[2]: 1
In [3]: mod1.num2
Out[3]: 2
In [4]: mod1.num3
Out[4]: 3
In [5]: mod1.num1 + mod1.num2 + mod3.num3
Out[5]: 6
In [6]: exit
Python contains a number of standard modules which are developed alongside Python by Python core developers. The identifiers modules complement the most commonly used identifiers in builtins
however the module name is used to compartmentalise the identifiers. Some of the most commonly used standard modules include:
In [1]: import builtins, os, sys, collections, re, math, random, datetime, itertools, functools
Standard Modules
['builtins', # builtin Python identifiers
'os', # operating system
'sys', # system module
'collections', # supplementary collections
're', # regular expressions
'math', # random number generation
'datetime', dates and time
'itertools', # iterator tools
'functools', # function tools
]
In most cases, the code for the standard modules is found in the Lib folder:
In [2]: print(os.__file__)
C:\Users\phili\AppData\Local\spyder-6\envs\spyder-runtime\Lib\os.py
In [3]: print(re.__file__)
C:\Users\phili\AppData\Local\spyder-6\envs\spyder-runtime\Lib\re\__init__.py
In [4]: print(random.__file__)
C:\Users\phili\AppData\Local\spyder-6\envs\spyder-runtime\Lib\random.py
In [5]: print(datetime.__file__)
C:\Users\phili\AppData\Local\spyder-6\envs\spyder-runtime\Lib\datetime.py
In [6]: print(functools.__file__)
C:\Users\phili\AppData\Local\spyder-6\envs\spyder-runtime\Lib\functools.py
Notice that the standard module re
is a folder re that contains multiple Python script files including an initialisation script __init__.py
.
The builtins
module and some of the other standard modules are written in the programming language C and as a consequence do not have a Python script file:
In [7]: print(builtins.__file__)
Traceback (most recent call last):
Cell In[7], line 1
print(builtins.__file__)
AttributeError: module 'builtins' has no attribute '__file__'
In [8]: print(sys.__file__)
Traceback (most recent call last):
Cell In[8], line 1
print(sys.__file__)
AttributeError: module 'sys' has no attribute '__file__'
In [9]: print(math.__file__)
Traceback (most recent call last):
Cell In[9], line 1
print(math.__file__)
AttributeError: module 'math' has no attribute '__file__'
In [10]: print(itertools.__file__)
Traceback (most recent call last):
Cell In[10], line 1
print(itertools.__file__)
AttributeError: module 'itertools' has no attribute '__file__'
If the standard module functools.py
is examined (use the show/hide below to quickly glance at the standard module):
functools.py
At this point, you are unlikely to understand everything in the functools.py
however you should pick up:
- The docstring at the top of the file.
__all__
- imports at the top of the module.
def
used to define a function, noting that each function as a docstring and a return value.
"""functools.py - Tools for working with functions and callable objects
"""
# Python module wrapper for _functools C module
# to allow utilities written in Python to be added
# to the functools module.
# Written by Nick Coghlan <ncoghlan at gmail.com>,
# Raymond Hettinger <python at rcn.com>,
# and Łukasz Langa <lukasz at langa.pl>.
# Copyright (C) 2006-2013 Python Software Foundation.
# See C source code for _functools credits/copyright
__all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES',
'total_ordering', 'cache', 'cmp_to_key', 'lru_cache', 'reduce',
'partial', 'partialmethod', 'singledispatch', 'singledispatchmethod',
'cached_property']
from abc import get_cache_token
from collections import namedtuple
# import types, weakref # Deferred to single_dispatch()
from reprlib import recursive_repr
from _thread import RLock
from types import GenericAlias
################################################################################
### update_wrapper() and wraps() decorator
################################################################################
# update_wrapper() and wraps() are tools to help write
# wrapper functions that can handle naive introspection
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
'__annotations__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Update a wrapper function to look like the wrapped function
wrapper is the function to be updated
wrapped is the original function
assigned is a tuple naming the attributes assigned directly
from the wrapped function to the wrapper function (defaults to
functools.WRAPPER_ASSIGNMENTS)
updated is a tuple naming the attributes of the wrapper that
are updated with the corresponding attribute from the wrapped
function (defaults to functools.WRAPPER_UPDATES)
"""
for attr in assigned:
try:
value = getattr(wrapped, attr)
except AttributeError:
pass
else:
setattr(wrapper, attr, value)
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
# Issue #17482: set __wrapped__ last so we don't inadvertently copy it
# from the wrapped function when updating __dict__
wrapper.__wrapped__ = wrapped
# Return the wrapper so this can be used as a decorator via partial()
return wrapper
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Decorator factory to apply update_wrapper() to a wrapper function
Returns a decorator that invokes update_wrapper() with the decorated
function as the wrapper argument and the arguments to wraps() as the
remaining arguments. Default arguments are as for update_wrapper().
This is a convenience function to simplify applying partial() to
update_wrapper().
"""
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
################################################################################
### total_ordering class decorator
################################################################################
# The total ordering functions all invoke the root magic method directly
# rather than using the corresponding operator. This avoids possible
# infinite recursion that could occur when the operator dispatch logic
# detects a NotImplemented result and then calls a reflected method.
def _gt_from_lt(self, other):
'Return a > b. Computed by @total_ordering from (not a < b) and (a != b).'
op_result = type(self).__lt__(self, other)
if op_result is NotImplemented:
return op_result
return not op_result and self != other
def _le_from_lt(self, other):
'Return a <= b. Computed by @total_ordering from (a < b) or (a == b).'
op_result = type(self).__lt__(self, other)
if op_result is NotImplemented:
return op_result
return op_result or self == other
def _ge_from_lt(self, other):
'Return a >= b. Computed by @total_ordering from (not a < b).'
op_result = type(self).__lt__(self, other)
if op_result is NotImplemented:
return op_result
return not op_result
def _ge_from_le(self, other):
'Return a >= b. Computed by @total_ordering from (not a <= b) or (a == b).'
op_result = type(self).__le__(self, other)
if op_result is NotImplemented:
return op_result
return not op_result or self == other
def _lt_from_le(self, other):
'Return a < b. Computed by @total_ordering from (a <= b) and (a != b).'
op_result = type(self).__le__(self, other)
if op_result is NotImplemented:
return op_result
return op_result and self != other
def _gt_from_le(self, other):
'Return a > b. Computed by @total_ordering from (not a <= b).'
op_result = type(self).__le__(self, other)
if op_result is NotImplemented:
return op_result
return not op_result
def _lt_from_gt(self, other):
'Return a < b. Computed by @total_ordering from (not a > b) and (a != b).'
op_result = type(self).__gt__(self, other)
if op_result is NotImplemented:
return op_result
return not op_result and self != other
def _ge_from_gt(self, other):
'Return a >= b. Computed by @total_ordering from (a > b) or (a == b).'
op_result = type(self).__gt__(self, other)
if op_result is NotImplemented:
return op_result
return op_result or self == other
def _le_from_gt(self, other):
'Return a <= b. Computed by @total_ordering from (not a > b).'
op_result = type(self).__gt__(self, other)
if op_result is NotImplemented:
return op_result
return not op_result
def _le_from_ge(self, other):
'Return a <= b. Computed by @total_ordering from (not a >= b) or (a == b).'
op_result = type(self).__ge__(self, other)
if op_result is NotImplemented:
return op_result
return not op_result or self == other
def _gt_from_ge(self, other):
'Return a > b. Computed by @total_ordering from (a >= b) and (a != b).'
op_result = type(self).__ge__(self, other)
if op_result is NotImplemented:
return op_result
return op_result and self != other
def _lt_from_ge(self, other):
'Return a < b. Computed by @total_ordering from (not a >= b).'
op_result = type(self).__ge__(self, other)
if op_result is NotImplemented:
return op_result
return not op_result
_convert = {
'__lt__': [('__gt__', _gt_from_lt),
('__le__', _le_from_lt),
('__ge__', _ge_from_lt)],
'__le__': [('__ge__', _ge_from_le),
('__lt__', _lt_from_le),
('__gt__', _gt_from_le)],
'__gt__': [('__lt__', _lt_from_gt),
('__ge__', _ge_from_gt),
('__le__', _le_from_gt)],
'__ge__': [('__le__', _le_from_ge),
('__gt__', _gt_from_ge),
('__lt__', _lt_from_ge)]
}
def total_ordering(cls):
"""Class decorator that fills in missing ordering methods"""
# Find user-defined comparisons (not those inherited from object).
roots = {op for op in _convert if getattr(cls, op, None) is not getattr(object, op, None)}
if not roots:
raise ValueError('must define at least one ordering operation: < > <= >=')
root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__
for opname, opfunc in _convert[root]:
if opname not in roots:
opfunc.__name__ = opname
setattr(cls, opname, opfunc)
return cls
################################################################################
### cmp_to_key() function converter
################################################################################
def cmp_to_key(mycmp):
"""Convert a cmp= function into a key= function"""
class K(object):
__slots__ = ['obj']
def __init__(self, obj):
self.obj = obj
def __lt__(self, other):
return mycmp(self.obj, other.obj) < 0
def __gt__(self, other):
return mycmp(self.obj, other.obj) > 0
def __eq__(self, other):
return mycmp(self.obj, other.obj) == 0
def __le__(self, other):
return mycmp(self.obj, other.obj) <= 0
def __ge__(self, other):
return mycmp(self.obj, other.obj) >= 0
__hash__ = None
return K
try:
from _functools import cmp_to_key
except ImportError:
pass
################################################################################
### reduce() sequence to a single item
################################################################################
_initial_missing = object()
def reduce(function, sequence, initial=_initial_missing):
"""
reduce(function, iterable[, initial]) -> value
Apply a function of two arguments cumulatively to the items of a sequence
or iterable, from left to right, so as to reduce the iterable to a single
value. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
((((1+2)+3)+4)+5). If initial is present, it is placed before the items
of the iterable in the calculation, and serves as a default when the
iterable is empty.
"""
it = iter(sequence)
if initial is _initial_missing:
try:
value = next(it)
except StopIteration:
raise TypeError(
"reduce() of empty iterable with no initial value") from None
else:
value = initial
for element in it:
value = function(value, element)
return value
try:
from _functools import reduce
except ImportError:
pass
################################################################################
### partial() argument application
################################################################################
# Purely functional, no descriptor behaviour
class partial:
"""New function with partial application of the given arguments
and keywords.
"""
__slots__ = "func", "args", "keywords", "__dict__", "__weakref__"
def __new__(cls, func, /, *args, **keywords):
if not callable(func):
raise TypeError("the first argument must be callable")
if hasattr(func, "func"):
args = func.args + args
keywords = {**func.keywords, **keywords}
func = func.func
self = super(partial, cls).__new__(cls)
self.func = func
self.args = args
self.keywords = keywords
return self
def __call__(self, /, *args, **keywords):
keywords = {**self.keywords, **keywords}
return self.func(*self.args, *args, **keywords)
@recursive_repr()
def __repr__(self):
qualname = type(self).__qualname__
args = [repr(self.func)]
args.extend(repr(x) for x in self.args)
args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items())
if type(self).__module__ == "functools":
return f"functools.{qualname}({', '.join(args)})"
return f"{qualname}({', '.join(args)})"
def __reduce__(self):
return type(self), (self.func,), (self.func, self.args,
self.keywords or None, self.__dict__ or None)
def __setstate__(self, state):
if not isinstance(state, tuple):
raise TypeError("argument to __setstate__ must be a tuple")
if len(state) != 4:
raise TypeError(f"expected 4 items in state, got {len(state)}")
func, args, kwds, namespace = state
if (not callable(func) or not isinstance(args, tuple) or
(kwds is not None and not isinstance(kwds, dict)) or
(namespace is not None and not isinstance(namespace, dict))):
raise TypeError("invalid partial state")
args = tuple(args) # just in case it's a subclass
if kwds is None:
kwds = {}
elif type(kwds) is not dict: # XXX does it need to be *exactly* dict?
kwds = dict(kwds)
if namespace is None:
namespace = {}
self.__dict__ = namespace
self.func = func
self.args = args
self.keywords = kwds
try:
from _functools import partial
except ImportError:
pass
# Descriptor version
class partialmethod(object):
"""Method descriptor with partial application of the given arguments
and keywords.
Supports wrapping existing descriptors and handles non-descriptor
callables as instance methods.
"""
def __init__(self, func, /, *args, **keywords):
if not callable(func) and not hasattr(func, "__get__"):
raise TypeError("{!r} is not callable or a descriptor"
.format(func))
# func could be a descriptor like classmethod which isn't callable,
# so we can't inherit from partial (it verifies func is callable)
if isinstance(func, partialmethod):
# flattening is mandatory in order to place cls/self before all
# other arguments
# it's also more efficient since only one function will be called
self.func = func.func
self.args = func.args + args
self.keywords = {**func.keywords, **keywords}
else:
self.func = func
self.args = args
self.keywords = keywords
def __repr__(self):
args = ", ".join(map(repr, self.args))
keywords = ", ".join("{}={!r}".format(k, v)
for k, v in self.keywords.items())
format_string = "{module}.{cls}({func}, {args}, {keywords})"
return format_string.format(module=self.__class__.__module__,
cls=self.__class__.__qualname__,
func=self.func,
args=args,
keywords=keywords)
def _make_unbound_method(self):
def _method(cls_or_self, /, *args, **keywords):
keywords = {**self.keywords, **keywords}
return self.func(cls_or_self, *self.args, *args, **keywords)
_method.__isabstractmethod__ = self.__isabstractmethod__
_method._partialmethod = self
return _method
def __get__(self, obj, cls=None):
get = getattr(self.func, "__get__", None)
result = None
if get is not None:
new_func = get(obj, cls)
if new_func is not self.func:
# Assume __get__ returning something new indicates the
# creation of an appropriate callable
result = partial(new_func, *self.args, **self.keywords)
try:
result.__self__ = new_func.__self__
except AttributeError:
pass
if result is None:
# If the underlying descriptor didn't do anything, treat this
# like an instance method
result = self._make_unbound_method().__get__(obj, cls)
return result
@property
def __isabstractmethod__(self):
return getattr(self.func, "__isabstractmethod__", False)
__class_getitem__ = classmethod(GenericAlias)
# Helper functions
def _unwrap_partial(func):
while isinstance(func, partial):
func = func.func
return func
################################################################################
### LRU Cache function decorator
################################################################################
_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])
class _HashedSeq(list):
""" This class guarantees that hash() will be called no more than once
per element. This is important because the lru_cache() will hash
the key multiple times on a cache miss.
"""
__slots__ = 'hashvalue'
def __init__(self, tup, hash=hash):
self[:] = tup
self.hashvalue = hash(tup)
def __hash__(self):
return self.hashvalue
def _make_key(args, kwds, typed,
kwd_mark = (object(),),
fasttypes = {int, str},
tuple=tuple, type=type, len=len):
"""Make a cache key from optionally typed positional and keyword arguments
The key is constructed in a way that is flat as possible rather than
as a nested structure that would take more memory.
If there is only a single argument and its data type is known to cache
its hash value, then that argument is returned without a wrapper. This
saves space and improves lookup speed.
"""
# All of code below relies on kwds preserving the order input by the user.
# Formerly, we sorted() the kwds before looping. The new way is *much*
# faster; however, it means that f(x=1, y=2) will now be treated as a
# distinct call from f(y=2, x=1) which will be cached separately.
key = args
if kwds:
key += kwd_mark
for item in kwds.items():
key += item
if typed:
key += tuple(type(v) for v in args)
if kwds:
key += tuple(type(v) for v in kwds.values())
elif len(key) == 1 and type(key[0]) in fasttypes:
return key[0]
return _HashedSeq(key)
def lru_cache(maxsize=128, typed=False):
"""Least-recently-used cache decorator.
If *maxsize* is set to None, the LRU features are disabled and the cache
can grow without bound.
If *typed* is True, arguments of different types will be cached separately.
For example, f(3.0) and f(3) will be treated as distinct calls with
distinct results.
Arguments to the cached function must be hashable.
View the cache statistics named tuple (hits, misses, maxsize, currsize)
with f.cache_info(). Clear the cache and statistics with f.cache_clear().
Access the underlying function with f.__wrapped__.
See: https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)
"""
# Users should only access the lru_cache through its public API:
# cache_info, cache_clear, and f.__wrapped__
# The internals of the lru_cache are encapsulated for thread safety and
# to allow the implementation to change (including a possible C version).
if isinstance(maxsize, int):
# Negative maxsize is treated as 0
if maxsize < 0:
maxsize = 0
elif callable(maxsize) and isinstance(typed, bool):
# The user_function was passed in directly via the maxsize argument
user_function, maxsize = maxsize, 128
wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
return update_wrapper(wrapper, user_function)
elif maxsize is not None:
raise TypeError(
'Expected first argument to be an integer, a callable, or None')
def decorating_function(user_function):
wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
return update_wrapper(wrapper, user_function)
return decorating_function
def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo):
# Constants shared by all lru cache instances:
sentinel = object() # unique object used to signal cache misses
make_key = _make_key # build a key from the function arguments
PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields
cache = {}
hits = misses = 0
full = False
cache_get = cache.get # bound method to lookup a key or return None
cache_len = cache.__len__ # get cache size without calling len()
lock = RLock() # because linkedlist updates aren't threadsafe
root = [] # root of the circular doubly linked list
root[:] = [root, root, None, None] # initialize by pointing to self
if maxsize == 0:
def wrapper(*args, **kwds):
# No caching -- just a statistics update
nonlocal misses
misses += 1
result = user_function(*args, **kwds)
return result
elif maxsize is None:
def wrapper(*args, **kwds):
# Simple caching without ordering or size limit
nonlocal hits, misses
key = make_key(args, kwds, typed)
result = cache_get(key, sentinel)
if result is not sentinel:
hits += 1
return result
misses += 1
result = user_function(*args, **kwds)
cache[key] = result
return result
else:
def wrapper(*args, **kwds):
# Size limited caching that tracks accesses by recency
nonlocal root, hits, misses, full
key = make_key(args, kwds, typed)
with lock:
link = cache_get(key)
if link is not None:
# Move the link to the front of the circular queue
link_prev, link_next, _key, result = link
link_prev[NEXT] = link_next
link_next[PREV] = link_prev
last = root[PREV]
last[NEXT] = root[PREV] = link
link[PREV] = last
link[NEXT] = root
hits += 1
return result
misses += 1
result = user_function(*args, **kwds)
with lock:
if key in cache:
# Getting here means that this same key was added to the
# cache while the lock was released. Since the link
# update is already done, we need only return the
# computed result and update the count of misses.
pass
elif full:
# Use the old root to store the new key and result.
oldroot = root
oldroot[KEY] = key
oldroot[RESULT] = result
# Empty the oldest link and make it the new root.
# Keep a reference to the old key and old result to
# prevent their ref counts from going to zero during the
# update. That will prevent potentially arbitrary object
# clean-up code (i.e. __del__) from running while we're
# still adjusting the links.
root = oldroot[NEXT]
oldkey = root[KEY]
oldresult = root[RESULT]
root[KEY] = root[RESULT] = None
# Now update the cache dictionary.
del cache[oldkey]
# Save the potentially reentrant cache[key] assignment
# for last, after the root and links have been put in
# a consistent state.
cache[key] = oldroot
else:
# Put result in a new link at the front of the queue.
last = root[PREV]
link = [last, root, key, result]
last[NEXT] = root[PREV] = cache[key] = link
# Use the cache_len bound method instead of the len() function
# which could potentially be wrapped in an lru_cache itself.
full = (cache_len() >= maxsize)
return result
def cache_info():
"""Report cache statistics"""
with lock:
return _CacheInfo(hits, misses, maxsize, cache_len())
def cache_clear():
"""Clear the cache and cache statistics"""
nonlocal hits, misses, full
with lock:
cache.clear()
root[:] = [root, root, None, None]
hits = misses = 0
full = False
wrapper.cache_info = cache_info
wrapper.cache_clear = cache_clear
return wrapper
try:
from _functools import _lru_cache_wrapper
except ImportError:
pass
################################################################################
### cache -- simplified access to the infinity cache
################################################################################
def cache(user_function, /):
'Simple lightweight unbounded cache. Sometimes called "memoize".'
return lru_cache(maxsize=None)(user_function)
################################################################################
### singledispatch() - single-dispatch generic function decorator
################################################################################
def _c3_merge(sequences):
"""Merges MROs in *sequences* to a single MRO using the C3 algorithm.
Adapted from https://www.python.org/download/releases/2.3/mro/.
"""
result = []
while True:
sequences = [s for s in sequences if s] # purge empty sequences
if not sequences:
return result
for s1 in sequences: # find merge candidates among seq heads
candidate = s1[0]
for s2 in sequences:
if candidate in s2[1:]:
candidate = None
break # reject the current head, it appears later
else:
break
if candidate is None:
raise RuntimeError("Inconsistent hierarchy")
result.append(candidate)
# remove the chosen candidate
for seq in sequences:
if seq[0] == candidate:
del seq[0]
def _c3_mro(cls, abcs=None):
"""Computes the method resolution order using extended C3 linearization.
If no *abcs* are given, the algorithm works exactly like the built-in C3
linearization used for method resolution.
If given, *abcs* is a list of abstract base classes that should be inserted
into the resulting MRO. Unrelated ABCs are ignored and don't end up in the
result. The algorithm inserts ABCs where their functionality is introduced,
i.e. issubclass(cls, abc) returns True for the class itself but returns
False for all its direct base classes. Implicit ABCs for a given class
(either registered or inferred from the presence of a special method like
__len__) are inserted directly after the last ABC explicitly listed in the
MRO of said class. If two implicit ABCs end up next to each other in the
resulting MRO, their ordering depends on the order of types in *abcs*.
"""
for i, base in enumerate(reversed(cls.__bases__)):
if hasattr(base, '__abstractmethods__'):
boundary = len(cls.__bases__) - i
break # Bases up to the last explicit ABC are considered first.
else:
boundary = 0
abcs = list(abcs) if abcs else []
explicit_bases = list(cls.__bases__[:boundary])
abstract_bases = []
other_bases = list(cls.__bases__[boundary:])
for base in abcs:
if issubclass(cls, base) and not any(
issubclass(b, base) for b in cls.__bases__
):
# If *cls* is the class that introduces behaviour described by
# an ABC *base*, insert said ABC to its MRO.
abstract_bases.append(base)
for base in abstract_bases:
abcs.remove(base)
explicit_c3_mros = [_c3_mro(base, abcs=abcs) for base in explicit_bases]
abstract_c3_mros = [_c3_mro(base, abcs=abcs) for base in abstract_bases]
other_c3_mros = [_c3_mro(base, abcs=abcs) for base in other_bases]
return _c3_merge(
[[cls]] +
explicit_c3_mros + abstract_c3_mros + other_c3_mros +
[explicit_bases] + [abstract_bases] + [other_bases]
)
def _compose_mro(cls, types):
"""Calculates the method resolution order for a given class *cls*.
Includes relevant abstract base classes (with their respective bases) from
the *types* iterable. Uses a modified C3 linearization algorithm.
"""
bases = set(cls.__mro__)
# Remove entries which are already present in the __mro__ or unrelated.
def is_related(typ):
return (typ not in bases and hasattr(typ, '__mro__')
and not isinstance(typ, GenericAlias)
and issubclass(cls, typ))
types = [n for n in types if is_related(n)]
# Remove entries which are strict bases of other entries (they will end up
# in the MRO anyway.
def is_strict_base(typ):
for other in types:
if typ != other and typ in other.__mro__:
return True
return False
types = [n for n in types if not is_strict_base(n)]
# Subclasses of the ABCs in *types* which are also implemented by
# *cls* can be used to stabilize ABC ordering.
type_set = set(types)
mro = []
for typ in types:
found = []
for sub in typ.__subclasses__():
if sub not in bases and issubclass(cls, sub):
found.append([s for s in sub.__mro__ if s in type_set])
if not found:
mro.append(typ)
continue
# Favor subclasses with the biggest number of useful bases
found.sort(key=len, reverse=True)
for sub in found:
for subcls in sub:
if subcls not in mro:
mro.append(subcls)
return _c3_mro(cls, abcs=mro)
def _find_impl(cls, registry):
"""Returns the best matching implementation from *registry* for type *cls*.
Where there is no registered implementation for a specific type, its method
resolution order is used to find a more generic implementation.
Note: if *registry* does not contain an implementation for the base
*object* type, this function may return None.
"""
mro = _compose_mro(cls, registry.keys())
match = None
for t in mro:
if match is not None:
# If *match* is an implicit ABC but there is another unrelated,
# equally matching implicit ABC, refuse the temptation to guess.
if (t in registry and t not in cls.__mro__
and match not in cls.__mro__
and not issubclass(match, t)):
raise RuntimeError("Ambiguous dispatch: {} or {}".format(
match, t))
break
if t in registry:
match = t
return registry.get(match)
def singledispatch(func):
"""Single-dispatch generic function decorator.
Transforms a function into a generic function, which can have different
behaviours depending upon the type of its first argument. The decorated
function acts as the default implementation, and additional
implementations can be registered using the register() attribute of the
generic function.
"""
# There are many programs that use functools without singledispatch, so we
# trade-off making singledispatch marginally slower for the benefit of
# making start-up of such applications slightly faster.
import types, weakref
registry = {}
dispatch_cache = weakref.WeakKeyDictionary()
cache_token = None
def dispatch(cls):
"""generic_func.dispatch(cls) -> <function implementation>
Runs the dispatch algorithm to return the best available implementation
for the given *cls* registered on *generic_func*.
"""
nonlocal cache_token
if cache_token is not None:
current_token = get_cache_token()
if cache_token != current_token:
dispatch_cache.clear()
cache_token = current_token
try:
impl = dispatch_cache[cls]
except KeyError:
try:
impl = registry[cls]
except KeyError:
impl = _find_impl(cls, registry)
dispatch_cache[cls] = impl
return impl
def _is_union_type(cls):
from typing import get_origin, Union
return get_origin(cls) in {Union, types.UnionType}
def _is_valid_dispatch_type(cls):
if isinstance(cls, type):
return True
from typing import get_args
return (_is_union_type(cls) and
all(isinstance(arg, type) for arg in get_args(cls)))
def register(cls, func=None):
"""generic_func.register(cls, func) -> func
Registers a new implementation for the given *cls* on a *generic_func*.
"""
nonlocal cache_token
if _is_valid_dispatch_type(cls):
if func is None:
return lambda f: register(cls, f)
else:
if func is not None:
raise TypeError(
f"Invalid first argument to `register()`. "
f"{cls!r} is not a class or union type."
)
ann = getattr(cls, '__annotations__', {})
if not ann:
raise TypeError(
f"Invalid first argument to `register()`: {cls!r}. "
f"Use either `@register(some_class)` or plain `@register` "
f"on an annotated function."
)
func = cls
# only import typing if annotation parsing is necessary
from typing import get_type_hints
argname, cls = next(iter(get_type_hints(func).items()))
if not _is_valid_dispatch_type(cls):
if _is_union_type(cls):
raise TypeError(
f"Invalid annotation for {argname!r}. "
f"{cls!r} not all arguments are classes."
)
else:
raise TypeError(
f"Invalid annotation for {argname!r}. "
f"{cls!r} is not a class."
)
if _is_union_type(cls):
from typing import get_args
for arg in get_args(cls):
registry[arg] = func
else:
registry[cls] = func
if cache_token is None and hasattr(cls, '__abstractmethods__'):
cache_token = get_cache_token()
dispatch_cache.clear()
return func
def wrapper(*args, **kw):
if not args:
raise TypeError(f'{funcname} requires at least '
'1 positional argument')
return dispatch(args[0].__class__)(*args, **kw)
funcname = getattr(func, '__name__', 'singledispatch function')
registry[object] = func
wrapper.register = register
wrapper.dispatch = dispatch
wrapper.registry = types.MappingProxyType(registry)
wrapper._clear_cache = dispatch_cache.clear
update_wrapper(wrapper, func)
return wrapper
# Descriptor version
class singledispatchmethod:
"""Single-dispatch generic method descriptor.
Supports wrapping existing descriptors and handles non-descriptor
callables as instance methods.
"""
def __init__(self, func):
if not callable(func) and not hasattr(func, "__get__"):
raise TypeError(f"{func!r} is not callable or a descriptor")
self.dispatcher = singledispatch(func)
self.func = func
def register(self, cls, method=None):
"""generic_method.register(cls, func) -> func
Registers a new implementation for the given *cls* on a *generic_method*.
"""
return self.dispatcher.register(cls, func=method)
def __get__(self, obj, cls=None):
def _method(*args, **kwargs):
method = self.dispatcher.dispatch(args[0].__class__)
return method.__get__(obj, cls)(*args, **kwargs)
_method.__isabstractmethod__ = self.__isabstractmethod__
_method.register = self.register
update_wrapper(_method, self.func)
return _method
@property
def __isabstractmethod__(self):
return getattr(self.func, '__isabstractmethod__', False)
################################################################################
### cached_property() - computed once per instance, cached as attribute
################################################################################
_NOT_FOUND = object()
class cached_property:
def __init__(self, func):
self.func = func
self.attrname = None
self.__doc__ = func.__doc__
self.lock = RLock()
def __set_name__(self, owner, name):
if self.attrname is None:
self.attrname = name
elif name != self.attrname:
raise TypeError(
"Cannot assign the same cached_property to two different names "
f"({self.attrname!r} and {name!r})."
)
def __get__(self, instance, owner=None):
if instance is None:
return self
if self.attrname is None:
raise TypeError(
"Cannot use cached_property instance without calling __set_name__ on it.")
try:
cache = instance.__dict__
except AttributeError: # not all objects have __dict__ (e.g. class defines slots)
msg = (
f"No '__dict__' attribute on {type(instance).__name__!r} "
f"instance to cache {self.attrname!r} property."
)
raise TypeError(msg) from None
val = cache.get(self.attrname, _NOT_FOUND)
if val is _NOT_FOUND:
with self.lock:
# check if another thread filled cache while we awaited lock
val = cache.get(self.attrname, _NOT_FOUND)
if val is _NOT_FOUND:
val = self.func(instance)
try:
cache[self.attrname] = val
except TypeError:
msg = (
f"The '__dict__' attribute on {type(instance).__name__!r} instance "
f"does not support item assignment for caching {self.attrname!r} property."
)
raise TypeError(msg) from None
return val
__class_getitem__ = classmethod(GenericAlias)
For datascience there are a number of third-party modules, that are developed independently from Python, although commonly included in Python environments such as the Python environment preinstalled by the Spyder IDE. These are typically imported using an alias:
In [11]: import numpy as np
In [12]: import pandas as pd
In [13]: import matplotlib.pyplot as plt
Third-Party Modules
['numpy', # numeric python library
'pandas', # python and data analysis library
'matplotlib' # matrix plotting library
]
The terms module, library, and package are often used interchangably however library and module have a slight distinction when it comes to scale.
- library is preferred over module when the parent folder contains a large number of subfolders and script files, like in the case of these datascience libraries.
- module is preferred over library when there are a small number of subfolders and script files or just a single script file.
The term package refers to either a library or module and is usually used in context of using a package manager such as conda. Conceptualise the package being the folder or single module stored on a server as a zip file. The package manager downloads the package and then extracts it to the site-packages subfolder of Lib. In this context package is used to describe the zip
folder but also used to describe the extracted contents of the folder.
If the __file__
attribute of each library is examined:
In [14]: print(np.__file__)
C:\Users\phili\AppData\Local\spyder-6\envs\spyder-runtime\Lib\site-packages\numpy\__init__.py
In [15]: print(pd.__file__)
C:\Users\phili\AppData\Local\spyder-6\envs\spyder-runtime\Lib\site-packages\pandas\__init__.py
In [16]: print(plt.__file__)
C:\Users\phili\AppData\Local\spyder-6\envs\spyder-runtime\Lib\site-packages\matplotlib\pyplot.py
Notice that in the case of np
and pd
the entire library is imported and the import refers to the libraries intialisation file __init__.py
whereas in the case of plt
, a module within the matplotlib folder called pyplot.py
is imported.
When modules are imported within a script file, the Python Enhanced Protocol 8 states that they should be placed at the top of the script file (under the script files docstring and import comments as seen when functool
was examined). Python ENhanced Protocol 8 states that two groupings should be made consisting of standard module imports and third-party modules:
from collections import namedtuple
import re
import os, sys
from datetime import timedelta, datetime
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
It is preferred to have each module imported on a separate line, grouped alphabetically:
import os #1.1
import re #1.1
import sys #1.1
from collections import namedtuple #1.2
from datetime import datetime, timedelta #1.2
import matplotlib.pyplot as plt #2.1
import numpy as np #2.1
import pandas as pd #2.1
Notice that:
- The standard module imports denoted #1.1 and #1.2 are before the third party imports denoted #2.1.
- The import module or module.submodule denoted #1.1 are grouped before the from module import submodule imports denoted #1.2.
- Within #1.2 that collections is listed before datetime (alphabetically). Since multiple identifiers are imported from datetime, these are listed alphabetically.
- Within #2.1, matplotlib.pyplot (m) is listed before numpy (n) and pandas (p)
Because the str
is a commonly used builtins
class, instantiation has a short form:
In [17]: exit
In [1]: text = 'Python'
The shorthand form doesn't explicitly mention the class
type, and assumes the user knows it is a str
and this assumption can be made because the str
is one of the most commonly used builtins
classes. Explicitly the str
can also be instantiated using the long form, which is more typical for custom classes:
In [2]: text = str('Python')
Initialisation, that is creating a new instance of a class has two steps, construction and initialising:
-
Calling the class
str()
is an instruction to use thestr
classes constructor__new__
to create a new instance, which is internally known within the classes namespace during construction asself
. Because assignment is used totext
,self
becomestext
in the__main__
namespace. -
The data provided within the class call
'Python'
is used to initialise the instance with the provided instance data.
Because text
is a str
instance it has a number of str
related methods. This can be seen when the following is input:
In [3]: text.
# -------------------------------
# Available Identifiers for `text`:
# -------------------------------------
# 🔧 Functions:
# - capitalize() : Capitalizes the first character of the string.
# - casefold() : Returns a casefolded copy of the string for
# case-insensitive comparisons.
# - center(width, fillchar=' ') : Centers the string in a field of a given width.
# - count(substring, start=0, end=len(text)) : Counts occurrences of a substring.
# - encode(encoding='utf-8', errors='strict') : Encodes the string using the specified encoding.
# - endswith(
# suffix,
# start=0,
# end=len(text) : Checks if the string ends with the specified suffix.
# - find(substring, start=0, end=len(text)) : Returns the lowest index of the substring.
# - format(*args, **kwargs) : Formats the string using placeholders.
# - index(substring, start=0, end=len(text)) : Like find(), but raises ValueError if not found.
# - isalnum() : Checks if all characters are alphanumeric.
# - isalpha() : Checks if all characters are alphabetic.
# - isdecimal() : Checks if all characters are decimal digits.
# - isdigit() : Checks if all characters are digits.
# - isidentifier() : Checks if the string is a valid identifier.
# - islower() : Checks if all characters are lowercase.
# - isspace() : Checks if all characters are whitespace.
# - istitle() : Checks if the string is title cased.
# - isupper() : Checks if all characters are uppercase.
# - join(iterable) : Concatenates the string with elements of an iterable.
# - ljust(width, fillchar=' ') : Left-justifies the string in a field of given width.
# - lower() : Returns a lowercase copy of the string.
# - lstrip(chars=None) : Removes leading whitespace or specified characters.
# - replace(old, new, count=-1) : Replaces occurrences of a substring with another.
# - rfind(substring, start=0, end=len(text)) : Returns the highest index of the substring.
# - rstrip(chars=None) : Removes trailing whitespace or specified characters.
# - split(sep=None, maxsplit=-1) : Splits the string into a list.
# - startswith(
# prefix,
# start=0,
# end=len(text) : Checks if the string starts with the specified prefix.
# - upper() : Returns an uppercase copy of the string.
# - title() : Converts the first character of each word to uppercase.
# - zfill(width) : Pads the string on the left with zeros.
# -------------------------------------
The str
classes methods upper
and lower
can be used. These methods work on the provided instance data and do not require any additional parameters:
In [3]: text.upper()
Out[3]: 'PYTHON'
In [4]: text.lower()
Out[4]: 'python'
There are also data model identifiers which can be seen when the following is input:
In [5]: text.__
# -------------------------------
# Available Data Model Identifiers for `text`:
# -------------------------------------
# 🛠️ Data Model Functions:
# - __add__(other) : Implements addition (concatenation) for strings.
# - __contains__(substring) : Checks if substring is in the string.
# - __eq__(other) : Checks if two strings are equal.
# - __ge__(other) : Checks if the string is greater than or equal to another.
# - __getattribute__(name) : Accesses attributes of the string.
# - __getitem__(index) : Gets the character at the specified index.
# - __gt__(other) : Checks if the string is greater than another.
# - __iter__() : Returns an iterator for the string.
# - __len__() : Returns the length of the string.
# - __le__(other) : Checks if the string is less than or equal to another.
# - __lt__(other) : Checks if the string is less than another.
# - __mod__(other) : Implements string formatting (using % operator).
# - __mul__(count) : Implements repetition for strings (using * operator).
# - __ne__(other) : Checks if two strings are not equal.
# - __repr__() : Returns a string representation of the string.
# - __radd__(other) : Implements addition (concatenation)
# for strings on the right.
# - __rmod__(other) : Implements string formatting on the right side.
# - __rmul__(count) : Implements repetition for strings on the right side.
# - __str__() : Returns the string itself.
# - __truediv__(other) : Implements division, generally not defined for strings.
#
# 🛠️ Data Model Properties:
# - __class__ : The type of the string.
# - __doc__ : The documentation string for the string class.
# -------------------------------------
The data model __add__
(dunder add) for a str
is set up to follow a Sequence
based data model:
In [6]: text.__add__('Spyder')
Out[6]: 'PythonSpyder'
__add__
(dunder add) defines the behaviour of the +
operator which is preferentially used:
In [7]: text + 'Spyder'
Out[7]: 'PythonSpyder'
Notice in the example above, a space wasn't concatenated, if this is desired, it has to also be explictly concatenated:
In [8]: text + ' ' + 'Spyder'
Out[8]: 'Python Spyder'
The collections
module contains a number of classes which are used to subclass a builtins
class. For example UserString
which behaves identically to a str
but requires to be explicitly instantiated:
In [9]: from collections import UserString
In [10]: text = UserString('Python')
In [11]: text
Out[11]: 'Python'
In [12]: text.lower()
Out[12]: 'python'
In [13]: text + UserString('Spyder')
Out[13]: 'PythonSpyder'
Note the same identifiers are displayed when text.
is input as the UserString
class is consistent to the str
class:
In [13]: text.
# -------------------------------
# Available Identifiers for `text`:
# -------------------------------------
# 🔧 Functions:
# - capitalize() : Capitalizes the first character of the string.
# - casefold() : Returns a casefolded copy of the string for
# case-insensitive comparisons.
# - center(width, fillchar=' ') : Centers the string in a field of a given width.
# - count(substring, start=0, end=len(text)) : Counts occurrences of a substring.
# - encode(encoding='utf-8', errors='strict') : Encodes the string using the specified encoding.
# - endswith(
# suffix,
# start=0,
# end=len(text) : Checks if the string ends with the specified suffix.
# - find(substring, start=0, end=len(text)) : Returns the lowest index of the substring.
# - format(*args, **kwargs) : Formats the string using placeholders.
# - index(substring, start=0, end=len(text)) : Like find(), but raises ValueError if not found.
# - isalnum() : Checks if all characters are alphanumeric.
# - isalpha() : Checks if all characters are alphabetic.
# - isdecimal() : Checks if all characters are decimal digits.
# - isdigit() : Checks if all characters are digits.
# - isidentifier() : Checks if the string is a valid identifier.
# - islower() : Checks if all characters are lowercase.
# - isspace() : Checks if all characters are whitespace.
# - istitle() : Checks if the string is title cased.
# - isupper() : Checks if all characters are uppercase.
# - join(iterable) : Concatenates the string with elements of an iterable.
# - ljust(width, fillchar=' ') : Left-justifies the string in a field of given width.
# - lower() : Returns a lowercase copy of the string.
# - lstrip(chars=None) : Removes leading whitespace or specified characters.
# - replace(old, new, count=-1) : Replaces occurrences of a substring with another.
# - rfind(substring, start=0, end=len(text)) : Returns the highest index of the substring.
# - rstrip(chars=None) : Removes trailing whitespace or specified characters.
# - split(sep=None, maxsplit=-1) : Splits the string into a list.
# - startswith(
# prefix,
# start=0,
# end=len(text) : Checks if the string starts with the specified prefix.
# - upper() : Returns an uppercase copy of the string.
# - title() : Converts the first character of each word to uppercase.
# - zfill(width) : Pads the string on the left with zeros.
# -------------------------------------
A subclass of UserString
can be created in a script file called custom_text.py
:
# custom_text.py
from collections import UserString
class CustomText(UserString):
"""A custom text class that has an additional methods prepend and prepend_num
and the additional attribute about"""
def prepend(self):
"""prepends similar to IPython Prompt"""
return '[ ]: ' + self
def prepend_num(self, num):
"""prepends number similar to IPython Prompt"""
return '[' + str(num) + ']: ' + self
num = 1
total = 1
This can be used as before:
In [14]:from custom_text import CustomText
In [15]: text = CustomText('Python')
In [16]: text.lower()
Out[16]: 'python'
In [17]: text + 'Spyder'
Out[17]: 'PythonSpyder'
Note the same identifiers are displayed when text.
is input, however the two additions prepend
and prepend_num
are also shown:
In [18]: text.
# -------------------------------
# Available Identifiers for `text`:
# -------------------------------------
# 🔧 Functions:
# - capitalize() : Capitalizes the first character of the string.
# - casefold() : Returns a casefolded copy of the string for
# case-insensitive comparisons.
# - center(width, fillchar=' ') : Centers the string in a field of a given width.
# - count(substring, start=0, end=len(text)) : Counts occurrences of a substring.
# - encode(encoding='utf-8', errors='strict') : Encodes the string using the specified encoding.
# - endswith(
# suffix,
# start=0,
# end=len(text) : Checks if the string ends with the specified suffix.
# - find(substring, start=0, end=len(text)) : Returns the lowest index of the substring.
# - format(*args, **kwargs) : Formats the string using placeholders.
# - index(substring, start=0, end=len(text)) : Like find(), but raises ValueError if not found.
# - isalnum() : Checks if all characters are alphanumeric.
# - isalpha() : Checks if all characters are alphabetic.
# - isdecimal() : Checks if all characters are decimal digits.
# - isdigit() : Checks if all characters are digits.
# - isidentifier() : Checks if the string is a valid identifier.
# - islower() : Checks if all characters are lowercase.
# - isspace() : Checks if all characters are whitespace.
# - istitle() : Checks if the string is title cased.
# - isupper() : Checks if all characters are uppercase.
# - join(iterable) : Concatenates the string with elements of an iterable.
# - ljust(width, fillchar=' ') : Left-justifies the string in a field of given width.
# - lower() : Returns a lowercase copy of the string.
# - lstrip(chars=None) : Removes leading whitespace or specified characters.
# - prepend() : Prepends a prompt similar to IPython.
# - prepend_num(num) : Prepends a number similar to IPython prompt.
# - replace(old, new, count=-1) : Replaces occurrences of a substring with another.
# - rfind(substring, start=0, end=len(text)) : Returns the highest index of the substring.
# - rstrip(chars=None) : Removes trailing whitespace or specified characters.
# - split(sep=None, maxsplit=-1) : Splits the string into a list.
# - startswith(
# prefix,
# start=0,
# end=len(text) : Checks if the string starts with the specified prefix.
# - upper() : Returns an uppercase copy of the string.
# - title() : Converts the first character of each word to uppercase.
# - zfill(width) : Pads the string on the left with zeros.
# -------------------------------------
And the supplementary behaviour can be used:
In [18]: text.prepend()
Out[18]: '[ ]: Python'
In [19]: text.prepend_num(100)
Out[19]: '[100]: Python'
In [20]: text.num
Out[20]: 1
Let's break the class definition down. The keyword class
is used to classify a class:
class
This is followed by the class
name and third-party classes typically uses PascalCase
:
class customText
The parenthesis is used to enclose the parent classes. The child class will inherit all the properties (functions and attributes) defined in the parent class:
class customText(UserString)
The colon is used to begin a code block and everything belonging to the code block is indented by four spaces:
class customText(UserString):
X
X
X
The class is a grouping of functions and attributes. Notice that the code belonging to each function is indented by another 4 spaces:
class customText(UserString):
def fun1():
return None
def fun2():
return None
att1 = 1
att2 = 2
Notice that the structure of the class above is similar to that of a module (shown below), the functions and attributes are in essence grouped together under the classes namespace (and indented appropriately by 4 spaces):
def fun1():
return None
def fun2():
return None
att1 = 1
att2 = 2
Generally functions within a class are supplied the instance data. This instance is internally known as self
and the functions are known as instance methods or methods (as these are the overwhelming majority of methods):
class customText(UserString):
def fun1(self):
# do something with self
return other
def fun2(self, additional_data):
# do something with self and additional_data
return other
att1 = 1
att2 = 2
class CustomText(UserString):
def prepend(self):
return '[ ]: ' + self
def prepend_num(self, num):
return '[' + str(num) + ']: ' + self
num = 1
total = 1
The attribute num
is a class
attribute (defined in the classes namespace) and is going to be constant for all instances of CustomText
:
In [21]: text1 = CustomText('Spyder')
In [22]: text1.num
Out[22]: 1
In [23]: text2 = CustomText('Pandas')
In [24]: text2.num
Out[24]: 1
In [25]: exit
The data model method initialisation method __init__
(dunder init) is used to prompt for intialisation data and provide instance specific data. Attributes defined within the namespace of the __init__
method are instance specific attributes as they depend on the instance data supplied during instantiation. Since instance specific attributes are more common than class attributes defined in the classes namespace, they are more commonly referred to as attributes.
Assuming num
is an instance specific attribute, it needs to be supplied during instantiation, for example as input_num
after the expected input_text
:
# part of custom_text.py
class CustomText2(UserString):
def __init__(self, input_text, input_num):
pass
An instance of a child class is also an instance of the parent class (otherwise known as a super class). super
can be used to retrieve this instance:
# part of custom_text.py
class CustomText2(UserString):
def __init__(self, input_text, input_num):
self_UserString = super(CustomText2, self)
super(CustomText2, self)
returns self_UserString
which is an instance of UserString
and therefore all the methods of UserString
can be accessed. In this case the initialisation method of UserString
can be called and this, recall only expects input_text
:
# part of custom_text.py
class CustomText2(UserString):
def __init__(self, input_text, input_num):
self_UserString = super(CustomText2, self)
self_UserString.__init__(input_text)
Then an instruction needs to be made to do something with the supplied initialisation data, in this case, assigning it to the attribute self.num
:
# part of custom_text.py
class CustomText2(UserString):
def __init__(self, input_text, input_num):
self_UserString = super(CustomText2, self)
self_UserString.__init__(input_text)
self.num = input_num
Normally the supplied initialisation data is made consistent with the attribute names, so this can be simplified to:
# part of custom_text.py
class CustomText2(UserString):
def __init__(self, text, num):
self_UserString = super(CustomText2, self)
self_UserString.__init__(input)
self.num = num
When super
is used the name of the child class CustomText2
and the instance self
are normally implied:
# part of custom_text.py
class CustomText2(UserString):
def __init__(self, text, num):
self_UserString = super()
self_UserString.__init__(input)
self.num = num
And this can be simplified down to:
# part of custom_text.py
class CustomText2(UserString):
def __init__(self, text, num):
super().__init__(text)
self.num = num
The simplified form is much more commonly used but often confuses new programmers who often do not conceptualise where self
went. The behaviour of this instance specific attribute can be seen:
In [1]: from custom_text import CustomText2
In [2]: text1 = CustomText2('hello', 4)
In [3]: text2 = CustomText2('bye', 2)
In [4]: text1
Out[4]: 'hello'
In [5]: text2
Out[5]: 'bye'
In [6]: text1.num
Out[6]: 4
In [7]: text2.num
Out[7]: 2
In [8] exit
In the following class CustomText3
the class attribute total
keeps track of the number of instances. It is accessed in the initialisation method using the class name CustomText3.total
and is updated each time an instance is created. An instance specific attribute self.num
uses CustomText3.total
at the time of instantiation to track the order each instance was instantiated:
# part of custom_text.py
class CustomText3(UserString):
total = 0
def __init__(self, text):
super().__init__(text)
CustomText3.total += 1
self.num = CustomText3.total
This can be tested using:
In [1]: from custom_text import CustomText3
In [2]: text1 = CustomText3('hello')
In [3]: text1.total
Out[3]: 1
In [4]: text1.num
Out[4]: 1
In [5]: text2 = CustomText3('bye')
In [6]: text1.total
Out[6]: 2
In [7]: text2.total
Out[7]: 2
In [8]: CustomText3.total
Out[8]: 2
In [9]: text1.num
Out[9]: 1
In [10]: text2.num
Out[10]: 2
In [11]: exit
So far the following examples have been covered:
- (instance) method
- (instance) attribute
- class attribute
The (instance) method requires no decorator and takes in self
as its first positional parameter and returns some operation which reads data from the instance. Some (instance) methods may require additional data to operate on. An example of (instance) methods was seen:
# part of custom_text.py
class CustomText(UserString):
def prepend(self):
return '[ ]: ' + self
def prepend_num(self, num):
return '[' + str(num) + ']: ' + self
A class method, like the class attribute is also bound to the class opposed to an instance of the class.
The class method requires the decorator @classmethod
and takes in cls
as its first positional parameter. Normally class methods are used as alternative constructors and therefore return a new instance of the class
:
# part of custom_text.py
class CustomText4(UserString):
@classmethod
def from_num(cls, num):
if num==0:
text = 'zero'
if num==1:
text = 'one'
if num==2:
text = 'two'
return cls(text)
In [1]: from custom_text import CustomText5
In [2]: text1 = CustomText5('hello')
In [3]: text1
Out[3]: 'hello'
In [4]: text2 = CustomText5.from_num(1)
In [5]: text2
Out[5]: 'one'
The builtins
class type
returns the class of an instance:
In [6]: type(text2)
Out[6]: custom_text.CustomText4
When a class method is called from an instance, the class is determined and the class method behind the scenes is actually called from the class, in other words the following are equivalent:
In [7]: type(text2).from_num(2)
In [8]: text2.from_num(2)
In [9]: exit
An example of alternative constructors is in the date
class of the datetime
module. Note also that the datetime
class is a child class of date
and hence inherits all its properties. Additional time specific properties are added:
class date:
"""Concrete date type.
Constructors:
__new__()
fromtimestamp()
today()
fromordinal()
...
class datetime(date):
...
The standard module datetime.py
can be examined (use the show/hide below to quickly glance at the standard module):
datetime.py
"""Concrete date/time and related types.
See http://www.iana.org/time-zones/repository/tz-link.html for
time zone and DST data sources.
"""
__all__ = ("date", "datetime", "time", "timedelta", "timezone", "tzinfo",
"MINYEAR", "MAXYEAR", "UTC")
import time as _time
import math as _math
import sys
from operator import index as _index
def _cmp(x, y):
return 0 if x == y else 1 if x > y else -1
MINYEAR = 1
MAXYEAR = 9999
_MAXORDINAL = 3652059 # date.max.toordinal()
# Utility functions, adapted from Python's Demo/classes/Dates.py, which
# also assumes the current Gregorian calendar indefinitely extended in
# both directions. Difference: Dates.py calls January 1 of year 0 day
# number 1. The code here calls January 1 of year 1 day number 1. This is
# to match the definition of the "proleptic Gregorian" calendar in Dershowitz
# and Reingold's "Calendrical Calculations", where it's the base calendar
# for all computations. See the book for algorithms for converting between
# proleptic Gregorian ordinals and many other calendar systems.
# -1 is a placeholder for indexing purposes.
_DAYS_IN_MONTH = [-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
_DAYS_BEFORE_MONTH = [-1] # -1 is a placeholder for indexing purposes.
dbm = 0
for dim in _DAYS_IN_MONTH[1:]:
_DAYS_BEFORE_MONTH.append(dbm)
dbm += dim
del dbm, dim
def _is_leap(year):
"year -> 1 if leap year, else 0."
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
def _days_before_year(year):
"year -> number of days before January 1st of year."
y = year - 1
return y*365 + y//4 - y//100 + y//400
def _days_in_month(year, month):
"year, month -> number of days in that month in that year."
assert 1 <= month <= 12, month
if month == 2 and _is_leap(year):
return 29
return _DAYS_IN_MONTH[month]
def _days_before_month(year, month):
"year, month -> number of days in year preceding first day of month."
assert 1 <= month <= 12, 'month must be in 1..12'
return _DAYS_BEFORE_MONTH[month] + (month > 2 and _is_leap(year))
def _ymd2ord(year, month, day):
"year, month, day -> ordinal, considering 01-Jan-0001 as day 1."
assert 1 <= month <= 12, 'month must be in 1..12'
dim = _days_in_month(year, month)
assert 1 <= day <= dim, ('day must be in 1..%d' % dim)
return (_days_before_year(year) +
_days_before_month(year, month) +
day)
_DI400Y = _days_before_year(401) # number of days in 400 years
_DI100Y = _days_before_year(101) # " " " " 100 "
_DI4Y = _days_before_year(5) # " " " " 4 "
# A 4-year cycle has an extra leap day over what we'd get from pasting
# together 4 single years.
assert _DI4Y == 4 * 365 + 1
# Similarly, a 400-year cycle has an extra leap day over what we'd get from
# pasting together 4 100-year cycles.
assert _DI400Y == 4 * _DI100Y + 1
# OTOH, a 100-year cycle has one fewer leap day than we'd get from
# pasting together 25 4-year cycles.
assert _DI100Y == 25 * _DI4Y - 1
def _ord2ymd(n):
"ordinal -> (year, month, day), considering 01-Jan-0001 as day 1."
# n is a 1-based index, starting at 1-Jan-1. The pattern of leap years
# repeats exactly every 400 years. The basic strategy is to find the
# closest 400-year boundary at or before n, then work with the offset
# from that boundary to n. Life is much clearer if we subtract 1 from
# n first -- then the values of n at 400-year boundaries are exactly
# those divisible by _DI400Y:
#
# D M Y n n-1
# -- --- ---- ---------- ----------------
# 31 Dec -400 -_DI400Y -_DI400Y -1
# 1 Jan -399 -_DI400Y +1 -_DI400Y 400-year boundary
# ...
# 30 Dec 000 -1 -2
# 31 Dec 000 0 -1
# 1 Jan 001 1 0 400-year boundary
# 2 Jan 001 2 1
# 3 Jan 001 3 2
# ...
# 31 Dec 400 _DI400Y _DI400Y -1
# 1 Jan 401 _DI400Y +1 _DI400Y 400-year boundary
n -= 1
n400, n = divmod(n, _DI400Y)
year = n400 * 400 + 1 # ..., -399, 1, 401, ...
# Now n is the (non-negative) offset, in days, from January 1 of year, to
# the desired date. Now compute how many 100-year cycles precede n.
# Note that it's possible for n100 to equal 4! In that case 4 full
# 100-year cycles precede the desired day, which implies the desired
# day is December 31 at the end of a 400-year cycle.
n100, n = divmod(n, _DI100Y)
# Now compute how many 4-year cycles precede it.
n4, n = divmod(n, _DI4Y)
# And now how many single years. Again n1 can be 4, and again meaning
# that the desired day is December 31 at the end of the 4-year cycle.
n1, n = divmod(n, 365)
year += n100 * 100 + n4 * 4 + n1
if n1 == 4 or n100 == 4:
assert n == 0
return year-1, 12, 31
# Now the year is correct, and n is the offset from January 1. We find
# the month via an estimate that's either exact or one too large.
leapyear = n1 == 3 and (n4 != 24 or n100 == 3)
assert leapyear == _is_leap(year)
month = (n + 50) >> 5
preceding = _DAYS_BEFORE_MONTH[month] + (month > 2 and leapyear)
if preceding > n: # estimate is too large
month -= 1
preceding -= _DAYS_IN_MONTH[month] + (month == 2 and leapyear)
n -= preceding
assert 0 <= n < _days_in_month(year, month)
# Now the year and month are correct, and n is the offset from the
# start of that month: we're done!
return year, month, n+1
# Month and day names. For localized versions, see the calendar module.
_MONTHNAMES = [None, "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
_DAYNAMES = [None, "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
def _build_struct_time(y, m, d, hh, mm, ss, dstflag):
wday = (_ymd2ord(y, m, d) + 6) % 7
dnum = _days_before_month(y, m) + d
return _time.struct_time((y, m, d, hh, mm, ss, wday, dnum, dstflag))
def _format_time(hh, mm, ss, us, timespec='auto'):
specs = {
'hours': '{:02d}',
'minutes': '{:02d}:{:02d}',
'seconds': '{:02d}:{:02d}:{:02d}',
'milliseconds': '{:02d}:{:02d}:{:02d}.{:03d}',
'microseconds': '{:02d}:{:02d}:{:02d}.{:06d}'
}
if timespec == 'auto':
# Skip trailing microseconds when us==0.
timespec = 'microseconds' if us else 'seconds'
elif timespec == 'milliseconds':
us //= 1000
try:
fmt = specs[timespec]
except KeyError:
raise ValueError('Unknown timespec value')
else:
return fmt.format(hh, mm, ss, us)
def _format_offset(off):
s = ''
if off is not None:
if off.days < 0:
sign = "-"
off = -off
else:
sign = "+"
hh, mm = divmod(off, timedelta(hours=1))
mm, ss = divmod(mm, timedelta(minutes=1))
s += "%s%02d:%02d" % (sign, hh, mm)
if ss or ss.microseconds:
s += ":%02d" % ss.seconds
if ss.microseconds:
s += '.%06d' % ss.microseconds
return s
# Correctly substitute for %z and %Z escapes in strftime formats.
def _wrap_strftime(object, format, timetuple):
# Don't call utcoffset() or tzname() unless actually needed.
freplace = None # the string to use for %f
zreplace = None # the string to use for %z
Zreplace = None # the string to use for %Z
# Scan format for %z and %Z escapes, replacing as needed.
newformat = []
push = newformat.append
i, n = 0, len(format)
while i < n:
ch = format[i]
i += 1
if ch == '%':
if i < n:
ch = format[i]
i += 1
if ch == 'f':
if freplace is None:
freplace = '%06d' % getattr(object,
'microsecond', 0)
newformat.append(freplace)
elif ch == 'z':
if zreplace is None:
zreplace = ""
if hasattr(object, "utcoffset"):
offset = object.utcoffset()
if offset is not None:
sign = '+'
if offset.days < 0:
offset = -offset
sign = '-'
h, rest = divmod(offset, timedelta(hours=1))
m, rest = divmod(rest, timedelta(minutes=1))
s = rest.seconds
u = offset.microseconds
if u:
zreplace = '%c%02d%02d%02d.%06d' % (sign, h, m, s, u)
elif s:
zreplace = '%c%02d%02d%02d' % (sign, h, m, s)
else:
zreplace = '%c%02d%02d' % (sign, h, m)
assert '%' not in zreplace
newformat.append(zreplace)
elif ch == 'Z':
if Zreplace is None:
Zreplace = ""
if hasattr(object, "tzname"):
s = object.tzname()
if s is not None:
# strftime is going to have at this: escape %
Zreplace = s.replace('%', '%%')
newformat.append(Zreplace)
else:
push('%')
push(ch)
else:
push('%')
else:
push(ch)
newformat = "".join(newformat)
return _time.strftime(newformat, timetuple)
# Helpers for parsing the result of isoformat()
def _is_ascii_digit(c):
return c in "0123456789"
def _find_isoformat_datetime_separator(dtstr):
# See the comment in _datetimemodule.c:_find_isoformat_datetime_separator
len_dtstr = len(dtstr)
if len_dtstr == 7:
return 7
assert len_dtstr > 7
date_separator = "-"
week_indicator = "W"
if dtstr[4] == date_separator:
if dtstr[5] == week_indicator:
if len_dtstr < 8:
raise ValueError("Invalid ISO string")
if len_dtstr > 8 and dtstr[8] == date_separator:
if len_dtstr == 9:
raise ValueError("Invalid ISO string")
if len_dtstr > 10 and _is_ascii_digit(dtstr[10]):
# This is as far as we need to resolve the ambiguity for
# the moment - if we have YYYY-Www-##, the separator is
# either a hyphen at 8 or a number at 10.
#
# We'll assume it's a hyphen at 8 because it's way more
# likely that someone will use a hyphen as a separator than
# a number, but at this point it's really best effort
# because this is an extension of the spec anyway.
# TODO(pganssle): Document this
return 8
return 10
else:
# YYYY-Www (8)
return 8
else:
# YYYY-MM-DD (10)
return 10
else:
if dtstr[4] == week_indicator:
# YYYYWww (7) or YYYYWwwd (8)
idx = 7
while idx < len_dtstr:
if not _is_ascii_digit(dtstr[idx]):
break
idx += 1
if idx < 9:
return idx
if idx % 2 == 0:
# If the index of the last number is even, it's YYYYWwwd
return 7
else:
return 8
else:
# YYYYMMDD (8)
return 8
def _parse_isoformat_date(dtstr):
# It is assumed that this is an ASCII-only string of lengths 7, 8 or 10,
# see the comment on Modules/_datetimemodule.c:_find_isoformat_datetime_separator
assert len(dtstr) in (7, 8, 10)
year = int(dtstr[0:4])
has_sep = dtstr[4] == '-'
pos = 4 + has_sep
if dtstr[pos:pos + 1] == "W":
# YYYY-?Www-?D?
pos += 1
weekno = int(dtstr[pos:pos + 2])
pos += 2
dayno = 1
if len(dtstr) > pos:
if (dtstr[pos:pos + 1] == '-') != has_sep:
raise ValueError("Inconsistent use of dash separator")
pos += has_sep
dayno = int(dtstr[pos:pos + 1])
return list(_isoweek_to_gregorian(year, weekno, dayno))
else:
month = int(dtstr[pos:pos + 2])
pos += 2
if (dtstr[pos:pos + 1] == "-") != has_sep:
raise ValueError("Inconsistent use of dash separator")
pos += has_sep
day = int(dtstr[pos:pos + 2])
return [year, month, day]
_FRACTION_CORRECTION = [100000, 10000, 1000, 100, 10]
def _parse_hh_mm_ss_ff(tstr):
# Parses things of the form HH[:?MM[:?SS[{.,}fff[fff]]]]
len_str = len(tstr)
time_comps = [0, 0, 0, 0]
pos = 0
for comp in range(0, 3):
if (len_str - pos) < 2:
raise ValueError("Incomplete time component")
time_comps[comp] = int(tstr[pos:pos+2])
pos += 2
next_char = tstr[pos:pos+1]
if comp == 0:
has_sep = next_char == ':'
if not next_char or comp >= 2:
break
if has_sep and next_char != ':':
raise ValueError("Invalid time separator: %c" % next_char)
pos += has_sep
if pos < len_str:
if tstr[pos] not in '.,':
raise ValueError("Invalid microsecond component")
else:
pos += 1
len_remainder = len_str - pos
if len_remainder >= 6:
to_parse = 6
else:
to_parse = len_remainder
time_comps[3] = int(tstr[pos:(pos+to_parse)])
if to_parse < 6:
time_comps[3] *= _FRACTION_CORRECTION[to_parse-1]
if (len_remainder > to_parse
and not all(map(_is_ascii_digit, tstr[(pos+to_parse):]))):
raise ValueError("Non-digit values in unparsed fraction")
return time_comps
def _parse_isoformat_time(tstr):
# Format supported is HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]
len_str = len(tstr)
if len_str < 2:
raise ValueError("Isoformat time too short")
# This is equivalent to re.search('[+-Z]', tstr), but faster
tz_pos = (tstr.find('-') + 1 or tstr.find('+') + 1 or tstr.find('Z') + 1)
timestr = tstr[:tz_pos-1] if tz_pos > 0 else tstr
time_comps = _parse_hh_mm_ss_ff(timestr)
tzi = None
if tz_pos == len_str and tstr[-1] == 'Z':
tzi = timezone.utc
elif tz_pos > 0:
tzstr = tstr[tz_pos:]
# Valid time zone strings are:
# HH len: 2
# HHMM len: 4
# HH:MM len: 5
# HHMMSS len: 6
# HHMMSS.f+ len: 7+
# HH:MM:SS len: 8
# HH:MM:SS.f+ len: 10+
if len(tzstr) in (0, 1, 3):
raise ValueError("Malformed time zone string")
tz_comps = _parse_hh_mm_ss_ff(tzstr)
if all(x == 0 for x in tz_comps):
tzi = timezone.utc
else:
tzsign = -1 if tstr[tz_pos - 1] == '-' else 1
td = timedelta(hours=tz_comps[0], minutes=tz_comps[1],
seconds=tz_comps[2], microseconds=tz_comps[3])
tzi = timezone(tzsign * td)
time_comps.append(tzi)
return time_comps
# tuple[int, int, int] -> tuple[int, int, int] version of date.fromisocalendar
def _isoweek_to_gregorian(year, week, day):
# Year is bounded this way because 9999-12-31 is (9999, 52, 5)
if not MINYEAR <= year <= MAXYEAR:
raise ValueError(f"Year is out of range: {year}")
if not 0 < week < 53:
out_of_range = True
if week == 53:
# ISO years have 53 weeks in them on years starting with a
# Thursday and leap years starting on a Wednesday
first_weekday = _ymd2ord(year, 1, 1) % 7
if (first_weekday == 4 or (first_weekday == 3 and
_is_leap(year))):
out_of_range = False
if out_of_range:
raise ValueError(f"Invalid week: {week}")
if not 0 < day < 8:
raise ValueError(f"Invalid weekday: {day} (range is [1, 7])")
# Now compute the offset from (Y, 1, 1) in days:
day_offset = (week - 1) * 7 + (day - 1)
# Calculate the ordinal day for monday, week 1
day_1 = _isoweek1monday(year)
ord_day = day_1 + day_offset
return _ord2ymd(ord_day)
# Just raise TypeError if the arg isn't None or a string.
def _check_tzname(name):
if name is not None and not isinstance(name, str):
raise TypeError("tzinfo.tzname() must return None or string, "
"not '%s'" % type(name))
# name is the offset-producing method, "utcoffset" or "dst".
# offset is what it returned.
# If offset isn't None or timedelta, raises TypeError.
# If offset is None, returns None.
# Else offset is checked for being in range.
# If it is, its integer value is returned. Else ValueError is raised.
def _check_utc_offset(name, offset):
assert name in ("utcoffset", "dst")
if offset is None:
return
if not isinstance(offset, timedelta):
raise TypeError("tzinfo.%s() must return None "
"or timedelta, not '%s'" % (name, type(offset)))
if not -timedelta(1) < offset < timedelta(1):
raise ValueError("%s()=%s, must be strictly between "
"-timedelta(hours=24) and timedelta(hours=24)" %
(name, offset))
def _check_date_fields(year, month, day):
year = _index(year)
month = _index(month)
day = _index(day)
if not MINYEAR <= year <= MAXYEAR:
raise ValueError('year must be in %d..%d' % (MINYEAR, MAXYEAR), year)
if not 1 <= month <= 12:
raise ValueError('month must be in 1..12', month)
dim = _days_in_month(year, month)
if not 1 <= day <= dim:
raise ValueError('day must be in 1..%d' % dim, day)
return year, month, day
def _check_time_fields(hour, minute, second, microsecond, fold):
hour = _index(hour)
minute = _index(minute)
second = _index(second)
microsecond = _index(microsecond)
if not 0 <= hour <= 23:
raise ValueError('hour must be in 0..23', hour)
if not 0 <= minute <= 59:
raise ValueError('minute must be in 0..59', minute)
if not 0 <= second <= 59:
raise ValueError('second must be in 0..59', second)
if not 0 <= microsecond <= 999999:
raise ValueError('microsecond must be in 0..999999', microsecond)
if fold not in (0, 1):
raise ValueError('fold must be either 0 or 1', fold)
return hour, minute, second, microsecond, fold
def _check_tzinfo_arg(tz):
if tz is not None and not isinstance(tz, tzinfo):
raise TypeError("tzinfo argument must be None or of a tzinfo subclass")
def _cmperror(x, y):
raise TypeError("can't compare '%s' to '%s'" % (
type(x).__name__, type(y).__name__))
def _divide_and_round(a, b):
"""divide a by b and round result to the nearest integer
When the ratio is exactly half-way between two integers,
the even integer is returned.
"""
# Based on the reference implementation for divmod_near
# in Objects/longobject.c.
q, r = divmod(a, b)
# round up if either r / b > 0.5, or r / b == 0.5 and q is odd.
# The expression r / b > 0.5 is equivalent to 2 * r > b if b is
# positive, 2 * r < b if b negative.
r *= 2
greater_than_half = r > b if b > 0 else r < b
if greater_than_half or r == b and q % 2 == 1:
q += 1
return q
class timedelta:
"""Represent the difference between two datetime objects.
Supported operators:
- add, subtract timedelta
- unary plus, minus, abs
- compare to timedelta
- multiply, divide by int
In addition, datetime supports subtraction of two datetime objects
returning a timedelta, and addition or subtraction of a datetime
and a timedelta giving a datetime.
Representation: (days, seconds, microseconds). Why? Because I
felt like it.
"""
__slots__ = '_days', '_seconds', '_microseconds', '_hashcode'
def __new__(cls, days=0, seconds=0, microseconds=0,
milliseconds=0, minutes=0, hours=0, weeks=0):
# Doing this efficiently and accurately in C is going to be difficult
# and error-prone, due to ubiquitous overflow possibilities, and that
# C double doesn't have enough bits of precision to represent
# microseconds over 10K years faithfully. The code here tries to make
# explicit where go-fast assumptions can be relied on, in order to
# guide the C implementation; it's way more convoluted than speed-
# ignoring auto-overflow-to-long idiomatic Python could be.
# XXX Check that all inputs are ints or floats.
# Final values, all integer.
# s and us fit in 32-bit signed ints; d isn't bounded.
d = s = us = 0
# Normalize everything to days, seconds, microseconds.
days += weeks*7
seconds += minutes*60 + hours*3600
microseconds += milliseconds*1000
# Get rid of all fractions, and normalize s and us.
# Take a deep breath <wink>.
if isinstance(days, float):
dayfrac, days = _math.modf(days)
daysecondsfrac, daysecondswhole = _math.modf(dayfrac * (24.*3600.))
assert daysecondswhole == int(daysecondswhole) # can't overflow
s = int(daysecondswhole)
assert days == int(days)
d = int(days)
else:
daysecondsfrac = 0.0
d = days
assert isinstance(daysecondsfrac, float)
assert abs(daysecondsfrac) <= 1.0
assert isinstance(d, int)
assert abs(s) <= 24 * 3600
# days isn't referenced again before redefinition
if isinstance(seconds, float):
secondsfrac, seconds = _math.modf(seconds)
assert seconds == int(seconds)
seconds = int(seconds)
secondsfrac += daysecondsfrac
assert abs(secondsfrac) <= 2.0
else:
secondsfrac = daysecondsfrac
# daysecondsfrac isn't referenced again
assert isinstance(secondsfrac, float)
assert abs(secondsfrac) <= 2.0
assert isinstance(seconds, int)
days, seconds = divmod(seconds, 24*3600)
d += days
s += int(seconds) # can't overflow
assert isinstance(s, int)
assert abs(s) <= 2 * 24 * 3600
# seconds isn't referenced again before redefinition
usdouble = secondsfrac * 1e6
assert abs(usdouble) < 2.1e6 # exact value not critical
# secondsfrac isn't referenced again
if isinstance(microseconds, float):
microseconds = round(microseconds + usdouble)
seconds, microseconds = divmod(microseconds, 1000000)
days, seconds = divmod(seconds, 24*3600)
d += days
s += seconds
else:
microseconds = int(microseconds)
seconds, microseconds = divmod(microseconds, 1000000)
days, seconds = divmod(seconds, 24*3600)
d += days
s += seconds
microseconds = round(microseconds + usdouble)
assert isinstance(s, int)
assert isinstance(microseconds, int)
assert abs(s) <= 3 * 24 * 3600
assert abs(microseconds) < 3.1e6
# Just a little bit of carrying possible for microseconds and seconds.
seconds, us = divmod(microseconds, 1000000)
s += seconds
days, s = divmod(s, 24*3600)
d += days
assert isinstance(d, int)
assert isinstance(s, int) and 0 <= s < 24*3600
assert isinstance(us, int) and 0 <= us < 1000000
if abs(d) > 999999999:
raise OverflowError("timedelta # of days is too large: %d" % d)
self = object.__new__(cls)
self._days = d
self._seconds = s
self._microseconds = us
self._hashcode = -1
return self
def __repr__(self):
args = []
if self._days:
args.append("days=%d" % self._days)
if self._seconds:
args.append("seconds=%d" % self._seconds)
if self._microseconds:
args.append("microseconds=%d" % self._microseconds)
if not args:
args.append('0')
return "%s.%s(%s)" % (self.__class__.__module__,
self.__class__.__qualname__,
', '.join(args))
def __str__(self):
mm, ss = divmod(self._seconds, 60)
hh, mm = divmod(mm, 60)
s = "%d:%02d:%02d" % (hh, mm, ss)
if self._days:
def plural(n):
return n, abs(n) != 1 and "s" or ""
s = ("%d day%s, " % plural(self._days)) + s
if self._microseconds:
s = s + ".%06d" % self._microseconds
return s
def total_seconds(self):
"""Total seconds in the duration."""
return ((self.days * 86400 + self.seconds) * 10**6 +
self.microseconds) / 10**6
# Read-only field accessors
@property
def days(self):
"""days"""
return self._days
@property
def seconds(self):
"""seconds"""
return self._seconds
@property
def microseconds(self):
"""microseconds"""
return self._microseconds
def __add__(self, other):
if isinstance(other, timedelta):
# for CPython compatibility, we cannot use
# our __class__ here, but need a real timedelta
return timedelta(self._days + other._days,
self._seconds + other._seconds,
self._microseconds + other._microseconds)
return NotImplemented
__radd__ = __add__
def __sub__(self, other):
if isinstance(other, timedelta):
# for CPython compatibility, we cannot use
# our __class__ here, but need a real timedelta
return timedelta(self._days - other._days,
self._seconds - other._seconds,
self._microseconds - other._microseconds)
return NotImplemented
def __rsub__(self, other):
if isinstance(other, timedelta):
return -self + other
return NotImplemented
def __neg__(self):
# for CPython compatibility, we cannot use
# our __class__ here, but need a real timedelta
return timedelta(-self._days,
-self._seconds,
-self._microseconds)
def __pos__(self):
return self
def __abs__(self):
if self._days < 0:
return -self
else:
return self
def __mul__(self, other):
if isinstance(other, int):
# for CPython compatibility, we cannot use
# our __class__ here, but need a real timedelta
return timedelta(self._days * other,
self._seconds * other,
self._microseconds * other)
if isinstance(other, float):
usec = self._to_microseconds()
a, b = other.as_integer_ratio()
return timedelta(0, 0, _divide_and_round(usec * a, b))
return NotImplemented
__rmul__ = __mul__
def _to_microseconds(self):
return ((self._days * (24*3600) + self._seconds) * 1000000 +
self._microseconds)
def __floordiv__(self, other):
if not isinstance(other, (int, timedelta)):
return NotImplemented
usec = self._to_microseconds()
if isinstance(other, timedelta):
return usec // other._to_microseconds()
if isinstance(other, int):
return timedelta(0, 0, usec // other)
def __truediv__(self, other):
if not isinstance(other, (int, float, timedelta)):
return NotImplemented
usec = self._to_microseconds()
if isinstance(other, timedelta):
return usec / other._to_microseconds()
if isinstance(other, int):
return timedelta(0, 0, _divide_and_round(usec, other))
if isinstance(other, float):
a, b = other.as_integer_ratio()
return timedelta(0, 0, _divide_and_round(b * usec, a))
def __mod__(self, other):
if isinstance(other, timedelta):
r = self._to_microseconds() % other._to_microseconds()
return timedelta(0, 0, r)
return NotImplemented
def __divmod__(self, other):
if isinstance(other, timedelta):
q, r = divmod(self._to_microseconds(),
other._to_microseconds())
return q, timedelta(0, 0, r)
return NotImplemented
# Comparisons of timedelta objects with other.
def __eq__(self, other):
if isinstance(other, timedelta):
return self._cmp(other) == 0
else:
return NotImplemented
def __le__(self, other):
if isinstance(other, timedelta):
return self._cmp(other) <= 0
else:
return NotImplemented
def __lt__(self, other):
if isinstance(other, timedelta):
return self._cmp(other) < 0
else:
return NotImplemented
def __ge__(self, other):
if isinstance(other, timedelta):
return self._cmp(other) >= 0
else:
return NotImplemented
def __gt__(self, other):
if isinstance(other, timedelta):
return self._cmp(other) > 0
else:
return NotImplemented
def _cmp(self, other):
assert isinstance(other, timedelta)
return _cmp(self._getstate(), other._getstate())
def __hash__(self):
if self._hashcode == -1:
self._hashcode = hash(self._getstate())
return self._hashcode
def __bool__(self):
return (self._days != 0 or
self._seconds != 0 or
self._microseconds != 0)
# Pickle support.
def _getstate(self):
return (self._days, self._seconds, self._microseconds)
def __reduce__(self):
return (self.__class__, self._getstate())
timedelta.min = timedelta(-999999999)
timedelta.max = timedelta(days=999999999, hours=23, minutes=59, seconds=59,
microseconds=999999)
timedelta.resolution = timedelta(microseconds=1)
class date:
"""Concrete date type.
Constructors:
__new__()
fromtimestamp()
today()
fromordinal()
Operators:
__repr__, __str__
__eq__, __le__, __lt__, __ge__, __gt__, __hash__
__add__, __radd__, __sub__ (add/radd only with timedelta arg)
Methods:
timetuple()
toordinal()
weekday()
isoweekday(), isocalendar(), isoformat()
ctime()
strftime()
Properties (readonly):
year, month, day
"""
__slots__ = '_year', '_month', '_day', '_hashcode'
def __new__(cls, year, month=None, day=None):
"""Constructor.
Arguments:
year, month, day (required, base 1)
"""
if (month is None and
isinstance(year, (bytes, str)) and len(year) == 4 and
1 <= ord(year[2:3]) <= 12):
# Pickle support
if isinstance(year, str):
try:
year = year.encode('latin1')
except UnicodeEncodeError:
# More informative error message.
raise ValueError(
"Failed to encode latin1 string when unpickling "
"a date object. "
"pickle.load(data, encoding='latin1') is assumed.")
self = object.__new__(cls)
self.__setstate(year)
self._hashcode = -1
return self
year, month, day = _check_date_fields(year, month, day)
self = object.__new__(cls)
self._year = year
self._month = month
self._day = day
self._hashcode = -1
return self
# Additional constructors
@classmethod
def fromtimestamp(cls, t):
"Construct a date from a POSIX timestamp (like time.time())."
y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t)
return cls(y, m, d)
@classmethod
def today(cls):
"Construct a date from time.time()."
t = _time.time()
return cls.fromtimestamp(t)
@classmethod
def fromordinal(cls, n):
"""Construct a date from a proleptic Gregorian ordinal.
January 1 of year 1 is day 1. Only the year, month and day are
non-zero in the result.
"""
y, m, d = _ord2ymd(n)
return cls(y, m, d)
@classmethod
def fromisoformat(cls, date_string):
"""Construct a date from a string in ISO 8601 format."""
if not isinstance(date_string, str):
raise TypeError('fromisoformat: argument must be str')
if len(date_string) not in (7, 8, 10):
raise ValueError(f'Invalid isoformat string: {date_string!r}')
try:
return cls(*_parse_isoformat_date(date_string))
except Exception:
raise ValueError(f'Invalid isoformat string: {date_string!r}')
@classmethod
def fromisocalendar(cls, year, week, day):
"""Construct a date from the ISO year, week number and weekday.
This is the inverse of the date.isocalendar() function"""
return cls(*_isoweek_to_gregorian(year, week, day))
# Conversions to string
def __repr__(self):
"""Convert to formal string, for repr().
>>> d = date(2010, 1, 1)
>>> repr(d)
'datetime.date(2010, 1, 1)'
"""
return "%s.%s(%d, %d, %d)" % (self.__class__.__module__,
self.__class__.__qualname__,
self._year,
self._month,
self._day)
# XXX These shouldn't depend on time.localtime(), because that
# clips the usable dates to [1970 .. 2038). At least ctime() is
# easily done without using strftime() -- that's better too because
# strftime("%c", ...) is locale specific.
def ctime(self):
"Return ctime() style string."
weekday = self.toordinal() % 7 or 7
return "%s %s %2d 00:00:00 %04d" % (
_DAYNAMES[weekday],
_MONTHNAMES[self._month],
self._day, self._year)
def strftime(self, fmt):
"""
Format using strftime().
Example: "%d/%m/%Y, %H:%M:%S"
"""
return _wrap_strftime(self, fmt, self.timetuple())
def __format__(self, fmt):
if not isinstance(fmt, str):
raise TypeError("must be str, not %s" % type(fmt).__name__)
if len(fmt) != 0:
return self.strftime(fmt)
return str(self)
def isoformat(self):
"""Return the date formatted according to ISO.
This is 'YYYY-MM-DD'.
References:
- http://www.w3.org/TR/NOTE-datetime
- http://www.cl.cam.ac.uk/~mgk25/iso-time.html
"""
return "%04d-%02d-%02d" % (self._year, self._month, self._day)
__str__ = isoformat
# Read-only field accessors
@property
def year(self):
"""year (1-9999)"""
return self._year
@property
def month(self):
"""month (1-12)"""
return self._month
@property
def day(self):
"""day (1-31)"""
return self._day
# Standard conversions, __eq__, __le__, __lt__, __ge__, __gt__,
# __hash__ (and helpers)
def timetuple(self):
"Return local time tuple compatible with time.localtime()."
return _build_struct_time(self._year, self._month, self._day,
0, 0, 0, -1)
def toordinal(self):
"""Return proleptic Gregorian ordinal for the year, month and day.
January 1 of year 1 is day 1. Only the year, month and day values
contribute to the result.
"""
return _ymd2ord(self._year, self._month, self._day)
def replace(self, year=None, month=None, day=None):
"""Return a new date with new values for the specified fields."""
if year is None:
year = self._year
if month is None:
month = self._month
if day is None:
day = self._day
return type(self)(year, month, day)
# Comparisons of date objects with other.
def __eq__(self, other):
if isinstance(other, date):
return self._cmp(other) == 0
return NotImplemented
def __le__(self, other):
if isinstance(other, date):
return self._cmp(other) <= 0
return NotImplemented
def __lt__(self, other):
if isinstance(other, date):
return self._cmp(other) < 0
return NotImplemented
def __ge__(self, other):
if isinstance(other, date):
return self._cmp(other) >= 0
return NotImplemented
def __gt__(self, other):
if isinstance(other, date):
return self._cmp(other) > 0
return NotImplemented
def _cmp(self, other):
assert isinstance(other, date)
y, m, d = self._year, self._month, self._day
y2, m2, d2 = other._year, other._month, other._day
return _cmp((y, m, d), (y2, m2, d2))
def __hash__(self):
"Hash."
if self._hashcode == -1:
self._hashcode = hash(self._getstate())
return self._hashcode
# Computations
def __add__(self, other):
"Add a date to a timedelta."
if isinstance(other, timedelta):
o = self.toordinal() + other.days
if 0 < o <= _MAXORDINAL:
return type(self).fromordinal(o)
raise OverflowError("result out of range")
return NotImplemented
__radd__ = __add__
def __sub__(self, other):
"""Subtract two dates, or a date and a timedelta."""
if isinstance(other, timedelta):
return self + timedelta(-other.days)
if isinstance(other, date):
days1 = self.toordinal()
days2 = other.toordinal()
return timedelta(days1 - days2)
return NotImplemented
def weekday(self):
"Return day of the week, where Monday == 0 ... Sunday == 6."
return (self.toordinal() + 6) % 7
# Day-of-the-week and week-of-the-year, according to ISO
def isoweekday(self):
"Return day of the week, where Monday == 1 ... Sunday == 7."
# 1-Jan-0001 is a Monday
return self.toordinal() % 7 or 7
def isocalendar(self):
"""Return a named tuple containing ISO year, week number, and weekday.
The first ISO week of the year is the (Mon-Sun) week
containing the year's first Thursday; everything else derives
from that.
The first week is 1; Monday is 1 ... Sunday is 7.
ISO calendar algorithm taken from
http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
(used with permission)
"""
year = self._year
week1monday = _isoweek1monday(year)
today = _ymd2ord(self._year, self._month, self._day)
# Internally, week and day have origin 0
week, day = divmod(today - week1monday, 7)
if week < 0:
year -= 1
week1monday = _isoweek1monday(year)
week, day = divmod(today - week1monday, 7)
elif week >= 52:
if today >= _isoweek1monday(year+1):
year += 1
week = 0
return _IsoCalendarDate(year, week+1, day+1)
# Pickle support.
def _getstate(self):
yhi, ylo = divmod(self._year, 256)
return bytes([yhi, ylo, self._month, self._day]),
def __setstate(self, string):
yhi, ylo, self._month, self._day = string
self._year = yhi * 256 + ylo
def __reduce__(self):
return (self.__class__, self._getstate())
_date_class = date # so functions w/ args named "date" can get at the class
date.min = date(1, 1, 1)
date.max = date(9999, 12, 31)
date.resolution = timedelta(days=1)
class tzinfo:
"""Abstract base class for time zone info classes.
Subclasses must override the tzname(), utcoffset() and dst() methods.
"""
__slots__ = ()
def tzname(self, dt):
"datetime -> string name of time zone."
raise NotImplementedError("tzinfo subclass must override tzname()")
def utcoffset(self, dt):
"datetime -> timedelta, positive for east of UTC, negative for west of UTC"
raise NotImplementedError("tzinfo subclass must override utcoffset()")
def dst(self, dt):
"""datetime -> DST offset as timedelta, positive for east of UTC.
Return 0 if DST not in effect. utcoffset() must include the DST
offset.
"""
raise NotImplementedError("tzinfo subclass must override dst()")
def fromutc(self, dt):
"datetime in UTC -> datetime in local time."
if not isinstance(dt, datetime):
raise TypeError("fromutc() requires a datetime argument")
if dt.tzinfo is not self:
raise ValueError("dt.tzinfo is not self")
dtoff = dt.utcoffset()
if dtoff is None:
raise ValueError("fromutc() requires a non-None utcoffset() "
"result")
# See the long comment block at the end of this file for an
# explanation of this algorithm.
dtdst = dt.dst()
if dtdst is None:
raise ValueError("fromutc() requires a non-None dst() result")
delta = dtoff - dtdst
if delta:
dt += delta
dtdst = dt.dst()
if dtdst is None:
raise ValueError("fromutc(): dt.dst gave inconsistent "
"results; cannot convert")
return dt + dtdst
# Pickle support.
def __reduce__(self):
getinitargs = getattr(self, "__getinitargs__", None)
if getinitargs:
args = getinitargs()
else:
args = ()
return (self.__class__, args, self.__getstate__())
class IsoCalendarDate(tuple):
def __new__(cls, year, week, weekday, /):
return super().__new__(cls, (year, week, weekday))
@property
def year(self):
return self[0]
@property
def week(self):
return self[1]
@property
def weekday(self):
return self[2]
def __reduce__(self):
# This code is intended to pickle the object without making the
# class public. See https://bugs.python.org/msg352381
return (tuple, (tuple(self),))
def __repr__(self):
return (f'{self.__class__.__name__}'
f'(year={self[0]}, week={self[1]}, weekday={self[2]})')
_IsoCalendarDate = IsoCalendarDate
del IsoCalendarDate
_tzinfo_class = tzinfo
class time:
"""Time with time zone.
Constructors:
__new__()
Operators:
__repr__, __str__
__eq__, __le__, __lt__, __ge__, __gt__, __hash__
Methods:
strftime()
isoformat()
utcoffset()
tzname()
dst()
Properties (readonly):
hour, minute, second, microsecond, tzinfo, fold
"""
__slots__ = '_hour', '_minute', '_second', '_microsecond', '_tzinfo', '_hashcode', '_fold'
def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0):
"""Constructor.
Arguments:
hour, minute (required)
second, microsecond (default to zero)
tzinfo (default to None)
fold (keyword only, default to zero)
"""
if (isinstance(hour, (bytes, str)) and len(hour) == 6 and
ord(hour[0:1])&0x7F < 24):
# Pickle support
if isinstance(hour, str):
try:
hour = hour.encode('latin1')
except UnicodeEncodeError:
# More informative error message.
raise ValueError(
"Failed to encode latin1 string when unpickling "
"a time object. "
"pickle.load(data, encoding='latin1') is assumed.")
self = object.__new__(cls)
self.__setstate(hour, minute or None)
self._hashcode = -1
return self
hour, minute, second, microsecond, fold = _check_time_fields(
hour, minute, second, microsecond, fold)
_check_tzinfo_arg(tzinfo)
self = object.__new__(cls)
self._hour = hour
self._minute = minute
self._second = second
self._microsecond = microsecond
self._tzinfo = tzinfo
self._hashcode = -1
self._fold = fold
return self
# Read-only field accessors
@property
def hour(self):
"""hour (0-23)"""
return self._hour
@property
def minute(self):
"""minute (0-59)"""
return self._minute
@property
def second(self):
"""second (0-59)"""
return self._second
@property
def microsecond(self):
"""microsecond (0-999999)"""
return self._microsecond
@property
def tzinfo(self):
"""timezone info object"""
return self._tzinfo
@property
def fold(self):
return self._fold
# Standard conversions, __hash__ (and helpers)
# Comparisons of time objects with other.
def __eq__(self, other):
if isinstance(other, time):
return self._cmp(other, allow_mixed=True) == 0
else:
return NotImplemented
def __le__(self, other):
if isinstance(other, time):
return self._cmp(other) <= 0
else:
return NotImplemented
def __lt__(self, other):
if isinstance(other, time):
return self._cmp(other) < 0
else:
return NotImplemented
def __ge__(self, other):
if isinstance(other, time):
return self._cmp(other) >= 0
else:
return NotImplemented
def __gt__(self, other):
if isinstance(other, time):
return self._cmp(other) > 0
else:
return NotImplemented
def _cmp(self, other, allow_mixed=False):
assert isinstance(other, time)
mytz = self._tzinfo
ottz = other._tzinfo
myoff = otoff = None
if mytz is ottz:
base_compare = True
else:
myoff = self.utcoffset()
otoff = other.utcoffset()
base_compare = myoff == otoff
if base_compare:
return _cmp((self._hour, self._minute, self._second,
self._microsecond),
(other._hour, other._minute, other._second,
other._microsecond))
if myoff is None or otoff is None:
if allow_mixed:
return 2 # arbitrary non-zero value
else:
raise TypeError("cannot compare naive and aware times")
myhhmm = self._hour * 60 + self._minute - myoff//timedelta(minutes=1)
othhmm = other._hour * 60 + other._minute - otoff//timedelta(minutes=1)
return _cmp((myhhmm, self._second, self._microsecond),
(othhmm, other._second, other._microsecond))
def __hash__(self):
"""Hash."""
if self._hashcode == -1:
if self.fold:
t = self.replace(fold=0)
else:
t = self
tzoff = t.utcoffset()
if not tzoff: # zero or None
self._hashcode = hash(t._getstate()[0])
else:
h, m = divmod(timedelta(hours=self.hour, minutes=self.minute) - tzoff,
timedelta(hours=1))
assert not m % timedelta(minutes=1), "whole minute"
m //= timedelta(minutes=1)
if 0 <= h < 24:
self._hashcode = hash(time(h, m, self.second, self.microsecond))
else:
self._hashcode = hash((h, m, self.second, self.microsecond))
return self._hashcode
# Conversion to string
def _tzstr(self):
"""Return formatted timezone offset (+xx:xx) or an empty string."""
off = self.utcoffset()
return _format_offset(off)
def __repr__(self):
"""Convert to formal string, for repr()."""
if self._microsecond != 0:
s = ", %d, %d" % (self._second, self._microsecond)
elif self._second != 0:
s = ", %d" % self._second
else:
s = ""
s= "%s.%s(%d, %d%s)" % (self.__class__.__module__,
self.__class__.__qualname__,
self._hour, self._minute, s)
if self._tzinfo is not None:
assert s[-1:] == ")"
s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")"
if self._fold:
assert s[-1:] == ")"
s = s[:-1] + ", fold=1)"
return s
def isoformat(self, timespec='auto'):
"""Return the time formatted according to ISO.
The full format is 'HH:MM:SS.mmmmmm+zz:zz'. By default, the fractional
part is omitted if self.microsecond == 0.
The optional argument timespec specifies the number of additional
terms of the time to include. Valid options are 'auto', 'hours',
'minutes', 'seconds', 'milliseconds' and 'microseconds'.
"""
s = _format_time(self._hour, self._minute, self._second,
self._microsecond, timespec)
tz = self._tzstr()
if tz:
s += tz
return s
__str__ = isoformat
@classmethod
def fromisoformat(cls, time_string):
"""Construct a time from a string in one of the ISO 8601 formats."""
if not isinstance(time_string, str):
raise TypeError('fromisoformat: argument must be str')
# The spec actually requires that time-only ISO 8601 strings start with
# T, but the extended format allows this to be omitted as long as there
# is no ambiguity with date strings.
time_string = time_string.removeprefix('T')
try:
return cls(*_parse_isoformat_time(time_string))
except Exception:
raise ValueError(f'Invalid isoformat string: {time_string!r}')
def strftime(self, fmt):
"""Format using strftime(). The date part of the timestamp passed
to underlying strftime should not be used.
"""
# The year must be >= 1000 else Python's strftime implementation
# can raise a bogus exception.
timetuple = (1900, 1, 1,
self._hour, self._minute, self._second,
0, 1, -1)
return _wrap_strftime(self, fmt, timetuple)
def __format__(self, fmt):
if not isinstance(fmt, str):
raise TypeError("must be str, not %s" % type(fmt).__name__)
if len(fmt) != 0:
return self.strftime(fmt)
return str(self)
# Timezone functions
def utcoffset(self):
"""Return the timezone offset as timedelta, positive east of UTC
(negative west of UTC)."""
if self._tzinfo is None:
return None
offset = self._tzinfo.utcoffset(None)
_check_utc_offset("utcoffset", offset)
return offset
def tzname(self):
"""Return the timezone name.
Note that the name is 100% informational -- there's no requirement that
it mean anything in particular. For example, "GMT", "UTC", "-500",
"-5:00", "EDT", "US/Eastern", "America/New York" are all valid replies.
"""
if self._tzinfo is None:
return None
name = self._tzinfo.tzname(None)
_check_tzname(name)
return name
def dst(self):
"""Return 0 if DST is not in effect, or the DST offset (as timedelta
positive eastward) if DST is in effect.
This is purely informational; the DST offset has already been added to
the UTC offset returned by utcoffset() if applicable, so there's no
need to consult dst() unless you're interested in displaying the DST
info.
"""
if self._tzinfo is None:
return None
offset = self._tzinfo.dst(None)
_check_utc_offset("dst", offset)
return offset
def replace(self, hour=None, minute=None, second=None, microsecond=None,
tzinfo=True, *, fold=None):
"""Return a new time with new values for the specified fields."""
if hour is None:
hour = self.hour
if minute is None:
minute = self.minute
if second is None:
second = self.second
if microsecond is None:
microsecond = self.microsecond
if tzinfo is True:
tzinfo = self.tzinfo
if fold is None:
fold = self._fold
return type(self)(hour, minute, second, microsecond, tzinfo, fold=fold)
# Pickle support.
def _getstate(self, protocol=3):
us2, us3 = divmod(self._microsecond, 256)
us1, us2 = divmod(us2, 256)
h = self._hour
if self._fold and protocol > 3:
h += 128
basestate = bytes([h, self._minute, self._second,
us1, us2, us3])
if self._tzinfo is None:
return (basestate,)
else:
return (basestate, self._tzinfo)
def __setstate(self, string, tzinfo):
if tzinfo is not None and not isinstance(tzinfo, _tzinfo_class):
raise TypeError("bad tzinfo state arg")
h, self._minute, self._second, us1, us2, us3 = string
if h > 127:
self._fold = 1
self._hour = h - 128
else:
self._fold = 0
self._hour = h
self._microsecond = (((us1 << 8) | us2) << 8) | us3
self._tzinfo = tzinfo
def __reduce_ex__(self, protocol):
return (self.__class__, self._getstate(protocol))
def __reduce__(self):
return self.__reduce_ex__(2)
_time_class = time # so functions w/ args named "time" can get at the class
time.min = time(0, 0, 0)
time.max = time(23, 59, 59, 999999)
time.resolution = timedelta(microseconds=1)
class datetime(date):
"""datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]])
The year, month and day arguments are required. tzinfo may be None, or an
instance of a tzinfo subclass. The remaining arguments may be ints.
"""
__slots__ = date.__slots__ + time.__slots__
def __new__(cls, year, month=None, day=None, hour=0, minute=0, second=0,
microsecond=0, tzinfo=None, *, fold=0):
if (isinstance(year, (bytes, str)) and len(year) == 10 and
1 <= ord(year[2:3])&0x7F <= 12):
# Pickle support
if isinstance(year, str):
try:
year = bytes(year, 'latin1')
except UnicodeEncodeError:
# More informative error message.
raise ValueError(
"Failed to encode latin1 string when unpickling "
"a datetime object. "
"pickle.load(data, encoding='latin1') is assumed.")
self = object.__new__(cls)
self.__setstate(year, month)
self._hashcode = -1
return self
year, month, day = _check_date_fields(year, month, day)
hour, minute, second, microsecond, fold = _check_time_fields(
hour, minute, second, microsecond, fold)
_check_tzinfo_arg(tzinfo)
self = object.__new__(cls)
self._year = year
self._month = month
self._day = day
self._hour = hour
self._minute = minute
self._second = second
self._microsecond = microsecond
self._tzinfo = tzinfo
self._hashcode = -1
self._fold = fold
return self
# Read-only field accessors
@property
def hour(self):
"""hour (0-23)"""
return self._hour
@property
def minute(self):
"""minute (0-59)"""
return self._minute
@property
def second(self):
"""second (0-59)"""
return self._second
@property
def microsecond(self):
"""microsecond (0-999999)"""
return self._microsecond
@property
def tzinfo(self):
"""timezone info object"""
return self._tzinfo
@property
def fold(self):
return self._fold
@classmethod
def _fromtimestamp(cls, t, utc, tz):
"""Construct a datetime from a POSIX timestamp (like time.time()).
A timezone info object may be passed in as well.
"""
frac, t = _math.modf(t)
us = round(frac * 1e6)
if us >= 1000000:
t += 1
us -= 1000000
elif us < 0:
t -= 1
us += 1000000
converter = _time.gmtime if utc else _time.localtime
y, m, d, hh, mm, ss, weekday, jday, dst = converter(t)
ss = min(ss, 59) # clamp out leap seconds if the platform has them
result = cls(y, m, d, hh, mm, ss, us, tz)
if tz is None and not utc:
# As of version 2015f max fold in IANA database is
# 23 hours at 1969-09-30 13:00:00 in Kwajalein.
# Let's probe 24 hours in the past to detect a transition:
max_fold_seconds = 24 * 3600
# On Windows localtime_s throws an OSError for negative values,
# thus we can't perform fold detection for values of time less
# than the max time fold. See comments in _datetimemodule's
# version of this method for more details.
if t < max_fold_seconds and sys.platform.startswith("win"):
return result
y, m, d, hh, mm, ss = converter(t - max_fold_seconds)[:6]
probe1 = cls(y, m, d, hh, mm, ss, us, tz)
trans = result - probe1 - timedelta(0, max_fold_seconds)
if trans.days < 0:
y, m, d, hh, mm, ss = converter(t + trans // timedelta(0, 1))[:6]
probe2 = cls(y, m, d, hh, mm, ss, us, tz)
if probe2 == result:
result._fold = 1
elif tz is not None:
result = tz.fromutc(result)
return result
@classmethod
def fromtimestamp(cls, t, tz=None):
"""Construct a datetime from a POSIX timestamp (like time.time()).
A timezone info object may be passed in as well.
"""
_check_tzinfo_arg(tz)
return cls._fromtimestamp(t, tz is not None, tz)
@classmethod
def utcfromtimestamp(cls, t):
"""Construct a naive UTC datetime from a POSIX timestamp."""
return cls._fromtimestamp(t, True, None)
@classmethod
def now(cls, tz=None):
"Construct a datetime from time.time() and optional time zone info."
t = _time.time()
return cls.fromtimestamp(t, tz)
@classmethod
def utcnow(cls):
"Construct a UTC datetime from time.time()."
t = _time.time()
return cls.utcfromtimestamp(t)
@classmethod
def combine(cls, date, time, tzinfo=True):
"Construct a datetime from a given date and a given time."
if not isinstance(date, _date_class):
raise TypeError("date argument must be a date instance")
if not isinstance(time, _time_class):
raise TypeError("time argument must be a time instance")
if tzinfo is True:
tzinfo = time.tzinfo
return cls(date.year, date.month, date.day,
time.hour, time.minute, time.second, time.microsecond,
tzinfo, fold=time.fold)
@classmethod
def fromisoformat(cls, date_string):
"""Construct a datetime from a string in one of the ISO 8601 formats."""
if not isinstance(date_string, str):
raise TypeError('fromisoformat: argument must be str')
if len(date_string) < 7:
raise ValueError(f'Invalid isoformat string: {date_string!r}')
# Split this at the separator
try:
separator_location = _find_isoformat_datetime_separator(date_string)
dstr = date_string[0:separator_location]
tstr = date_string[(separator_location+1):]
date_components = _parse_isoformat_date(dstr)
except ValueError:
raise ValueError(
f'Invalid isoformat string: {date_string!r}') from None
if tstr:
try:
time_components = _parse_isoformat_time(tstr)
except ValueError:
raise ValueError(
f'Invalid isoformat string: {date_string!r}') from None
else:
time_components = [0, 0, 0, 0, None]
return cls(*(date_components + time_components))
def timetuple(self):
"Return local time tuple compatible with time.localtime()."
dst = self.dst()
if dst is None:
dst = -1
elif dst:
dst = 1
else:
dst = 0
return _build_struct_time(self.year, self.month, self.day,
self.hour, self.minute, self.second,
dst)
def _mktime(self):
"""Return integer POSIX timestamp."""
epoch = datetime(1970, 1, 1)
max_fold_seconds = 24 * 3600
t = (self - epoch) // timedelta(0, 1)
def local(u):
y, m, d, hh, mm, ss = _time.localtime(u)[:6]
return (datetime(y, m, d, hh, mm, ss) - epoch) // timedelta(0, 1)
# Our goal is to solve t = local(u) for u.
a = local(t) - t
u1 = t - a
t1 = local(u1)
if t1 == t:
# We found one solution, but it may not be the one we need.
# Look for an earlier solution (if `fold` is 0), or a
# later one (if `fold` is 1).
u2 = u1 + (-max_fold_seconds, max_fold_seconds)[self.fold]
b = local(u2) - u2
if a == b:
return u1
else:
b = t1 - u1
assert a != b
u2 = t - b
t2 = local(u2)
if t2 == t:
return u2
if t1 == t:
return u1
# We have found both offsets a and b, but neither t - a nor t - b is
# a solution. This means t is in the gap.
return (max, min)[self.fold](u1, u2)
def timestamp(self):
"Return POSIX timestamp as float"
if self._tzinfo is None:
s = self._mktime()
return s + self.microsecond / 1e6
else:
return (self - _EPOCH).total_seconds()
def utctimetuple(self):
"Return UTC time tuple compatible with time.gmtime()."
offset = self.utcoffset()
if offset:
self -= offset
y, m, d = self.year, self.month, self.day
hh, mm, ss = self.hour, self.minute, self.second
return _build_struct_time(y, m, d, hh, mm, ss, 0)
def date(self):
"Return the date part."
return date(self._year, self._month, self._day)
def time(self):
"Return the time part, with tzinfo None."
return time(self.hour, self.minute, self.second, self.microsecond, fold=self.fold)
def timetz(self):
"Return the time part, with same tzinfo."
return time(self.hour, self.minute, self.second, self.microsecond,
self._tzinfo, fold=self.fold)
def replace(self, year=None, month=None, day=None, hour=None,
minute=None, second=None, microsecond=None, tzinfo=True,
*, fold=None):
"""Return a new datetime with new values for the specified fields."""
if year is None:
year = self.year
if month is None:
month = self.month
if day is None:
day = self.day
if hour is None:
hour = self.hour
if minute is None:
minute = self.minute
if second is None:
second = self.second
if microsecond is None:
microsecond = self.microsecond
if tzinfo is True:
tzinfo = self.tzinfo
if fold is None:
fold = self.fold
return type(self)(year, month, day, hour, minute, second,
microsecond, tzinfo, fold=fold)
def _local_timezone(self):
if self.tzinfo is None:
ts = self._mktime()
else:
ts = (self - _EPOCH) // timedelta(seconds=1)
localtm = _time.localtime(ts)
local = datetime(*localtm[:6])
# Extract TZ data
gmtoff = localtm.tm_gmtoff
zone = localtm.tm_zone
return timezone(timedelta(seconds=gmtoff), zone)
def astimezone(self, tz=None):
if tz is None:
tz = self._local_timezone()
elif not isinstance(tz, tzinfo):
raise TypeError("tz argument must be an instance of tzinfo")
mytz = self.tzinfo
if mytz is None:
mytz = self._local_timezone()
myoffset = mytz.utcoffset(self)
else:
myoffset = mytz.utcoffset(self)
if myoffset is None:
mytz = self.replace(tzinfo=None)._local_timezone()
myoffset = mytz.utcoffset(self)
if tz is mytz:
return self
# Convert self to UTC, and attach the new time zone object.
utc = (self - myoffset).replace(tzinfo=tz)
# Convert from UTC to tz's local time.
return tz.fromutc(utc)
# Ways to produce a string.
def ctime(self):
"Return ctime() style string."
weekday = self.toordinal() % 7 or 7
return "%s %s %2d %02d:%02d:%02d %04d" % (
_DAYNAMES[weekday],
_MONTHNAMES[self._month],
self._day,
self._hour, self._minute, self._second,
self._year)
def isoformat(self, sep='T', timespec='auto'):
"""Return the time formatted according to ISO.
The full format looks like 'YYYY-MM-DD HH:MM:SS.mmmmmm'.
By default, the fractional part is omitted if self.microsecond == 0.
If self.tzinfo is not None, the UTC offset is also attached, giving
giving a full format of 'YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM'.
Optional argument sep specifies the separator between date and
time, default 'T'.
The optional argument timespec specifies the number of additional
terms of the time to include. Valid options are 'auto', 'hours',
'minutes', 'seconds', 'milliseconds' and 'microseconds'.
"""
s = ("%04d-%02d-%02d%c" % (self._year, self._month, self._day, sep) +
_format_time(self._hour, self._minute, self._second,
self._microsecond, timespec))
off = self.utcoffset()
tz = _format_offset(off)
if tz:
s += tz
return s
def __repr__(self):
"""Convert to formal string, for repr()."""
L = [self._year, self._month, self._day, # These are never zero
self._hour, self._minute, self._second, self._microsecond]
if L[-1] == 0:
del L[-1]
if L[-1] == 0:
del L[-1]
s = "%s.%s(%s)" % (self.__class__.__module__,
self.__class__.__qualname__,
", ".join(map(str, L)))
if self._tzinfo is not None:
assert s[-1:] == ")"
s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")"
if self._fold:
assert s[-1:] == ")"
s = s[:-1] + ", fold=1)"
return s
def __str__(self):
"Convert to string, for str()."
return self.isoformat(sep=' ')
@classmethod
def strptime(cls, date_string, format):
'string, format -> new datetime parsed from a string (like time.strptime()).'
import _strptime
return _strptime._strptime_datetime(cls, date_string, format)
def utcoffset(self):
"""Return the timezone offset as timedelta positive east of UTC (negative west of
UTC)."""
if self._tzinfo is None:
return None
offset = self._tzinfo.utcoffset(self)
_check_utc_offset("utcoffset", offset)
return offset
def tzname(self):
"""Return the timezone name.
Note that the name is 100% informational -- there's no requirement that
it mean anything in particular. For example, "GMT", "UTC", "-500",
"-5:00", "EDT", "US/Eastern", "America/New York" are all valid replies.
"""
if self._tzinfo is None:
return None
name = self._tzinfo.tzname(self)
_check_tzname(name)
return name
def dst(self):
"""Return 0 if DST is not in effect, or the DST offset (as timedelta
positive eastward) if DST is in effect.
This is purely informational; the DST offset has already been added to
the UTC offset returned by utcoffset() if applicable, so there's no
need to consult dst() unless you're interested in displaying the DST
info.
"""
if self._tzinfo is None:
return None
offset = self._tzinfo.dst(self)
_check_utc_offset("dst", offset)
return offset
# Comparisons of datetime objects with other.
def __eq__(self, other):
if isinstance(other, datetime):
return self._cmp(other, allow_mixed=True) == 0
elif not isinstance(other, date):
return NotImplemented
else:
return False
def __le__(self, other):
if isinstance(other, datetime):
return self._cmp(other) <= 0
elif not isinstance(other, date):
return NotImplemented
else:
_cmperror(self, other)
def __lt__(self, other):
if isinstance(other, datetime):
return self._cmp(other) < 0
elif not isinstance(other, date):
return NotImplemented
else:
_cmperror(self, other)
def __ge__(self, other):
if isinstance(other, datetime):
return self._cmp(other) >= 0
elif not isinstance(other, date):
return NotImplemented
else:
_cmperror(self, other)
def __gt__(self, other):
if isinstance(other, datetime):
return self._cmp(other) > 0
elif not isinstance(other, date):
return NotImplemented
else:
_cmperror(self, other)
def _cmp(self, other, allow_mixed=False):
assert isinstance(other, datetime)
mytz = self._tzinfo
ottz = other._tzinfo
myoff = otoff = None
if mytz is ottz:
base_compare = True
else:
myoff = self.utcoffset()
otoff = other.utcoffset()
# Assume that allow_mixed means that we are called from __eq__
if allow_mixed:
if myoff != self.replace(fold=not self.fold).utcoffset():
return 2
if otoff != other.replace(fold=not other.fold).utcoffset():
return 2
base_compare = myoff == otoff
if base_compare:
return _cmp((self._year, self._month, self._day,
self._hour, self._minute, self._second,
self._microsecond),
(other._year, other._month, other._day,
other._hour, other._minute, other._second,
other._microsecond))
if myoff is None or otoff is None:
if allow_mixed:
return 2 # arbitrary non-zero value
else:
raise TypeError("cannot compare naive and aware datetimes")
# XXX What follows could be done more efficiently...
diff = self - other # this will take offsets into account
if diff.days < 0:
return -1
return diff and 1 or 0
def __add__(self, other):
"Add a datetime and a timedelta."
if not isinstance(other, timedelta):
return NotImplemented
delta = timedelta(self.toordinal(),
hours=self._hour,
minutes=self._minute,
seconds=self._second,
microseconds=self._microsecond)
delta += other
hour, rem = divmod(delta.seconds, 3600)
minute, second = divmod(rem, 60)
if 0 < delta.days <= _MAXORDINAL:
return type(self).combine(date.fromordinal(delta.days),
time(hour, minute, second,
delta.microseconds,
tzinfo=self._tzinfo))
raise OverflowError("result out of range")
__radd__ = __add__
def __sub__(self, other):
"Subtract two datetimes, or a datetime and a timedelta."
if not isinstance(other, datetime):
if isinstance(other, timedelta):
return self + -other
return NotImplemented
days1 = self.toordinal()
days2 = other.toordinal()
secs1 = self._second + self._minute * 60 + self._hour * 3600
secs2 = other._second + other._minute * 60 + other._hour * 3600
base = timedelta(days1 - days2,
secs1 - secs2,
self._microsecond - other._microsecond)
if self._tzinfo is other._tzinfo:
return base
myoff = self.utcoffset()
otoff = other.utcoffset()
if myoff == otoff:
return base
if myoff is None or otoff is None:
raise TypeError("cannot mix naive and timezone-aware time")
return base + otoff - myoff
def __hash__(self):
if self._hashcode == -1:
if self.fold:
t = self.replace(fold=0)
else:
t = self
tzoff = t.utcoffset()
if tzoff is None:
self._hashcode = hash(t._getstate()[0])
else:
days = _ymd2ord(self.year, self.month, self.day)
seconds = self.hour * 3600 + self.minute * 60 + self.second
self._hashcode = hash(timedelta(days, seconds, self.microsecond) - tzoff)
return self._hashcode
# Pickle support.
def _getstate(self, protocol=3):
yhi, ylo = divmod(self._year, 256)
us2, us3 = divmod(self._microsecond, 256)
us1, us2 = divmod(us2, 256)
m = self._month
if self._fold and protocol > 3:
m += 128
basestate = bytes([yhi, ylo, m, self._day,
self._hour, self._minute, self._second,
us1, us2, us3])
if self._tzinfo is None:
return (basestate,)
else:
return (basestate, self._tzinfo)
def __setstate(self, string, tzinfo):
if tzinfo is not None and not isinstance(tzinfo, _tzinfo_class):
raise TypeError("bad tzinfo state arg")
(yhi, ylo, m, self._day, self._hour,
self._minute, self._second, us1, us2, us3) = string
if m > 127:
self._fold = 1
self._month = m - 128
else:
self._fold = 0
self._month = m
self._year = yhi * 256 + ylo
self._microsecond = (((us1 << 8) | us2) << 8) | us3
self._tzinfo = tzinfo
def __reduce_ex__(self, protocol):
return (self.__class__, self._getstate(protocol))
def __reduce__(self):
return self.__reduce_ex__(2)
datetime.min = datetime(1, 1, 1)
datetime.max = datetime(9999, 12, 31, 23, 59, 59, 999999)
datetime.resolution = timedelta(microseconds=1)
def _isoweek1monday(year):
# Helper to calculate the day number of the Monday starting week 1
# XXX This could be done more efficiently
THURSDAY = 3
firstday = _ymd2ord(year, 1, 1)
firstweekday = (firstday + 6) % 7 # See weekday() above
week1monday = firstday - firstweekday
if firstweekday > THURSDAY:
week1monday += 7
return week1monday
class timezone(tzinfo):
__slots__ = '_offset', '_name'
# Sentinel value to disallow None
_Omitted = object()
def __new__(cls, offset, name=_Omitted):
if not isinstance(offset, timedelta):
raise TypeError("offset must be a timedelta")
if name is cls._Omitted:
if not offset:
return cls.utc
name = None
elif not isinstance(name, str):
raise TypeError("name must be a string")
if not cls._minoffset <= offset <= cls._maxoffset:
raise ValueError("offset must be a timedelta "
"strictly between -timedelta(hours=24) and "
"timedelta(hours=24).")
return cls._create(offset, name)
@classmethod
def _create(cls, offset, name=None):
self = tzinfo.__new__(cls)
self._offset = offset
self._name = name
return self
def __getinitargs__(self):
"""pickle support"""
if self._name is None:
return (self._offset,)
return (self._offset, self._name)
def __eq__(self, other):
if isinstance(other, timezone):
return self._offset == other._offset
return NotImplemented
def __hash__(self):
return hash(self._offset)
def __repr__(self):
"""Convert to formal string, for repr().
>>> tz = timezone.utc
>>> repr(tz)
'datetime.timezone.utc'
>>> tz = timezone(timedelta(hours=-5), 'EST')
>>> repr(tz)
"datetime.timezone(datetime.timedelta(-1, 68400), 'EST')"
"""
if self is self.utc:
return 'datetime.timezone.utc'
if self._name is None:
return "%s.%s(%r)" % (self.__class__.__module__,
self.__class__.__qualname__,
self._offset)
return "%s.%s(%r, %r)" % (self.__class__.__module__,
self.__class__.__qualname__,
self._offset, self._name)
def __str__(self):
return self.tzname(None)
def utcoffset(self, dt):
if isinstance(dt, datetime) or dt is None:
return self._offset
raise TypeError("utcoffset() argument must be a datetime instance"
" or None")
def tzname(self, dt):
if isinstance(dt, datetime) or dt is None:
if self._name is None:
return self._name_from_offset(self._offset)
return self._name
raise TypeError("tzname() argument must be a datetime instance"
" or None")
def dst(self, dt):
if isinstance(dt, datetime) or dt is None:
return None
raise TypeError("dst() argument must be a datetime instance"
" or None")
def fromutc(self, dt):
if isinstance(dt, datetime):
if dt.tzinfo is not self:
raise ValueError("fromutc: dt.tzinfo "
"is not self")
return dt + self._offset
raise TypeError("fromutc() argument must be a datetime instance"
" or None")
_maxoffset = timedelta(hours=24, microseconds=-1)
_minoffset = -_maxoffset
@staticmethod
def _name_from_offset(delta):
if not delta:
return 'UTC'
if delta < timedelta(0):
sign = '-'
delta = -delta
else:
sign = '+'
hours, rest = divmod(delta, timedelta(hours=1))
minutes, rest = divmod(rest, timedelta(minutes=1))
seconds = rest.seconds
microseconds = rest.microseconds
if microseconds:
return (f'UTC{sign}{hours:02d}:{minutes:02d}:{seconds:02d}'
f'.{microseconds:06d}')
if seconds:
return f'UTC{sign}{hours:02d}:{minutes:02d}:{seconds:02d}'
return f'UTC{sign}{hours:02d}:{minutes:02d}'
UTC = timezone.utc = timezone._create(timedelta(0))
# bpo-37642: These attributes are rounded to the nearest minute for backwards
# compatibility, even though the constructor will accept a wider range of
# values. This may change in the future.
timezone.min = timezone._create(-timedelta(hours=23, minutes=59))
timezone.max = timezone._create(timedelta(hours=23, minutes=59))
_EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
# Some time zone algebra. For a datetime x, let
# x.n = x stripped of its timezone -- its naive time.
# x.o = x.utcoffset(), and assuming that doesn't raise an exception or
# return None
# x.d = x.dst(), and assuming that doesn't raise an exception or
# return None
# x.s = x's standard offset, x.o - x.d
#
# Now some derived rules, where k is a duration (timedelta).
#
# 1. x.o = x.s + x.d
# This follows from the definition of x.s.
#
# 2. If x and y have the same tzinfo member, x.s = y.s.
# This is actually a requirement, an assumption we need to make about
# sane tzinfo classes.
#
# 3. The naive UTC time corresponding to x is x.n - x.o.
# This is again a requirement for a sane tzinfo class.
#
# 4. (x+k).s = x.s
# This follows from #2, and that datetime.timetz+timedelta preserves tzinfo.
#
# 5. (x+k).n = x.n + k
# Again follows from how arithmetic is defined.
#
# Now we can explain tz.fromutc(x). Let's assume it's an interesting case
# (meaning that the various tzinfo methods exist, and don't blow up or return
# None when called).
#
# The function wants to return a datetime y with timezone tz, equivalent to x.
# x is already in UTC.
#
# By #3, we want
#
# y.n - y.o = x.n [1]
#
# The algorithm starts by attaching tz to x.n, and calling that y. So
# x.n = y.n at the start. Then it wants to add a duration k to y, so that [1]
# becomes true; in effect, we want to solve [2] for k:
#
# (y+k).n - (y+k).o = x.n [2]
#
# By #1, this is the same as
#
# (y+k).n - ((y+k).s + (y+k).d) = x.n [3]
#
# By #5, (y+k).n = y.n + k, which equals x.n + k because x.n=y.n at the start.
# Substituting that into [3],
#
# x.n + k - (y+k).s - (y+k).d = x.n; the x.n terms cancel, leaving
# k - (y+k).s - (y+k).d = 0; rearranging,
# k = (y+k).s - (y+k).d; by #4, (y+k).s == y.s, so
# k = y.s - (y+k).d
#
# On the RHS, (y+k).d can't be computed directly, but y.s can be, and we
# approximate k by ignoring the (y+k).d term at first. Note that k can't be
# very large, since all offset-returning methods return a duration of magnitude
# less than 24 hours. For that reason, if y is firmly in std time, (y+k).d must
# be 0, so ignoring it has no consequence then.
#
# In any case, the new value is
#
# z = y + y.s [4]
#
# It's helpful to step back at look at [4] from a higher level: it's simply
# mapping from UTC to tz's standard time.
#
# At this point, if
#
# z.n - z.o = x.n [5]
#
# we have an equivalent time, and are almost done. The insecurity here is
# at the start of daylight time. Picture US Eastern for concreteness. The wall
# time jumps from 1:59 to 3:00, and wall hours of the form 2:MM don't make good
# sense then. The docs ask that an Eastern tzinfo class consider such a time to
# be EDT (because it's "after 2"), which is a redundant spelling of 1:MM EST
# on the day DST starts. We want to return the 1:MM EST spelling because that's
# the only spelling that makes sense on the local wall clock.
#
# In fact, if [5] holds at this point, we do have the standard-time spelling,
# but that takes a bit of proof. We first prove a stronger result. What's the
# difference between the LHS and RHS of [5]? Let
#
# diff = x.n - (z.n - z.o) [6]
#
# Now
# z.n = by [4]
# (y + y.s).n = by #5
# y.n + y.s = since y.n = x.n
# x.n + y.s = since z and y are have the same tzinfo member,
# y.s = z.s by #2
# x.n + z.s
#
# Plugging that back into [6] gives
#
# diff =
# x.n - ((x.n + z.s) - z.o) = expanding
# x.n - x.n - z.s + z.o = cancelling
# - z.s + z.o = by #2
# z.d
#
# So diff = z.d.
#
# If [5] is true now, diff = 0, so z.d = 0 too, and we have the standard-time
# spelling we wanted in the endcase described above. We're done. Contrarily,
# if z.d = 0, then we have a UTC equivalent, and are also done.
#
# If [5] is not true now, diff = z.d != 0, and z.d is the offset we need to
# add to z (in effect, z is in tz's standard time, and we need to shift the
# local clock into tz's daylight time).
#
# Let
#
# z' = z + z.d = z + diff [7]
#
# and we can again ask whether
#
# z'.n - z'.o = x.n [8]
#
# If so, we're done. If not, the tzinfo class is insane, according to the
# assumptions we've made. This also requires a bit of proof. As before, let's
# compute the difference between the LHS and RHS of [8] (and skipping some of
# the justifications for the kinds of substitutions we've done several times
# already):
#
# diff' = x.n - (z'.n - z'.o) = replacing z'.n via [7]
# x.n - (z.n + diff - z'.o) = replacing diff via [6]
# x.n - (z.n + x.n - (z.n - z.o) - z'.o) =
# x.n - z.n - x.n + z.n - z.o + z'.o = cancel x.n
# - z.n + z.n - z.o + z'.o = cancel z.n
# - z.o + z'.o = #1 twice
# -z.s - z.d + z'.s + z'.d = z and z' have same tzinfo
# z'.d - z.d
#
# So z' is UTC-equivalent to x iff z'.d = z.d at this point. If they are equal,
# we've found the UTC-equivalent so are done. In fact, we stop with [7] and
# return z', not bothering to compute z'.d.
#
# How could z.d and z'd differ? z' = z + z.d [7], so merely moving z' by
# a dst() offset, and starting *from* a time already in DST (we know z.d != 0),
# would have to change the result dst() returns: we start in DST, and moving
# a little further into it takes us out of DST.
#
# There isn't a sane case where this can happen. The closest it gets is at
# the end of DST, where there's an hour in UTC with no spelling in a hybrid
# tzinfo class. In US Eastern, that's 5:MM UTC = 0:MM EST = 1:MM EDT. During
# that hour, on an Eastern clock 1:MM is taken as being in standard time (6:MM
# UTC) because the docs insist on that, but 0:MM is taken as being in daylight
# time (4:MM UTC). There is no local time mapping to 5:MM UTC. The local
# clock jumps from 1:59 back to 1:00 again, and repeats the 1:MM hour in
# standard time. Since that's what the local clock *does*, we want to map both
# UTC hours 5:MM and 6:MM to 1:MM Eastern. The result is ambiguous
# in local time, but so it goes -- it's the way the local clock works.
#
# When x = 5:MM UTC is the input to this algorithm, x.o=0, y.o=-5 and y.d=0,
# so z=0:MM. z.d=60 (minutes) then, so [5] doesn't hold and we keep going.
# z' = z + z.d = 1:MM then, and z'.d=0, and z'.d - z.d = -60 != 0 so [8]
# (correctly) concludes that z' is not UTC-equivalent to x.
#
# Because we know z.d said z was in daylight time (else [5] would have held and
# we would have stopped then), and we know z.d != z'.d (else [8] would have held
# and we have stopped then), and there are only 2 possible values dst() can
# return in Eastern, it follows that z'.d must be 0 (which it is in the example,
# but the reasoning doesn't depend on the example -- it depends on there being
# two possible dst() outcomes, one zero and the other non-zero). Therefore
# z' must be in standard time, and is the spelling we want in this case.
#
# Note again that z' is not UTC-equivalent as far as the hybrid tzinfo class is
# concerned (because it takes z' as being in standard time rather than the
# daylight time we intend here), but returning it gives the real-life "local
# clock repeats an hour" behavior when mapping the "unspellable" UTC hour into
# tz.
#
# When the input is 6:MM, z=1:MM and z.d=0, and we stop at once, again with
# the 1:MM standard time spelling we want.
#
# So how can this break? One of the assumptions must be violated. Two
# possibilities:
#
# 1) [2] effectively says that y.s is invariant across all y belong to a given
# time zone. This isn't true if, for political reasons or continental drift,
# a region decides to change its base offset from UTC.
#
# 2) There may be versions of "double daylight" time where the tail end of
# the analysis gives up a step too early. I haven't thought about that
# enough to say.
#
# In any case, it's clear that the default fromutc() is strong enough to handle
# "almost all" time zones: so long as the standard offset is invariant, it
# doesn't matter if daylight time transition points change from year to year, or
# if daylight time is skipped in some years; it doesn't matter how large or
# small dst() may get within its bounds; and it doesn't even matter if some
# perverse time zone returns a negative dst()). So a breaking case must be
# pretty bizarre, and a tzinfo subclass can override fromutc() if it is.
try:
from _datetime import *
except ImportError:
pass
else:
# Clean up unused names
del (_DAYNAMES, _DAYS_BEFORE_MONTH, _DAYS_IN_MONTH, _DI100Y, _DI400Y,
_DI4Y, _EPOCH, _MAXORDINAL, _MONTHNAMES, _build_struct_time,
_check_date_fields, _check_time_fields,
_check_tzinfo_arg, _check_tzname, _check_utc_offset, _cmp, _cmperror,
_date_class, _days_before_month, _days_before_year, _days_in_month,
_format_time, _format_offset, _index, _is_leap, _isoweek1monday, _math,
_ord2ymd, _time, _time_class, _tzinfo_class, _wrap_strftime, _ymd2ord,
_divide_and_round, _parse_isoformat_date, _parse_isoformat_time,
_parse_hh_mm_ss_ff, _IsoCalendarDate, _isoweek_to_gregorian,
_find_isoformat_datetime_separator, _FRACTION_CORRECTION,
_is_ascii_digit)
# XXX Since import * above excludes names that start with _,
# docstring does not get overwritten. In the future, it may be
# appropriate to maintain a single module level docstring and
# remove the following line.
from _datetime import __doc__
Because from_num
is a class method that has the first parameter cls
, it returns an instance of the class using cls
. When it is subclasses:
# part of custom_text.py
class CustomText5(CustomText4):
pass
If CustomText5.from_num
is used, an instance of CustomText5
is instantiated:
In [1]: from custom_text import CustomText5
In [2]: text = CustomText5.from_num(1)
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
text | custom_text.CustomText5 | 5 | hello |
A static method is contained within the namespace of a class but is bound neither to the instance or a class. It is essentially a regular function only found in the classes namespace because it is associated with the class.
The static method requires the decorator @staticmethod
, let's change the from_num
class method in CustomText4
to a static method:
# part of custom_text.py
class CustomText4(UserString):
@staticmethod
def from_num(cls, num):
if num==0:
text = 'zero'
if num==1:
text = 'one'
if num==2:
text = 'two'
return CustomText4(text)
Notice the only place where cls
was used in the class method within the methods body was in the return statement, this is now updated to directly return a instance of CustomText4:
If CustomText5.from_num
is used again, an instance of CustomText4
is instantiated when CustomText5.from_num
is used:
In [3]: exit
In [1]: from custom_text import CustomText5
In [2]: text = CustomText5.from_num(1)
In [3]: exit
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
text | custom_text.CustomText4 | 5 | hello |
The static method CustomText4
is hard wired to always return an instance of CustomText4
and not suited for subclassing.
__dir__
(dunder dir) controls the behaviour of the builtins
function dir
and treats the namespace as a directory, listing all of the identifiers within the directory.
In [1]: one = 1
In [2]: dir(one)
Out[2]: [
'__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__',
'__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__',
'__format__', '__ge__', '__getattribute__', '__getnewargs__', '__getstate__', '__gt__',
'__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__',
'__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__',
'__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__',
'__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__',
'__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__',
'__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__',
'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes',
'imag', 'numerator', 'real', 'to_bytes'
]
In [3]: dir(int)
Out[3]: [
'__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__',
'__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__',
'__format__', '__ge__', '__getattribute__', '__getnewargs__', '__getstate__', '__gt__',
'__hash__', '__index__', '__init__', '__init_subclass__', '__int__', '__invert__', '__le__',
'__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__',
'__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__',
'__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__',
'__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__',
'__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__',
'as_integer_ratio', 'bit_count', 'bit_length', 'conjugate', 'denominator', 'from_bytes',
'imag', 'numerator', 'real', 'to_bytes'
]
In [4]: text = 'hello'
In [5]: dir('hello')
Out[5]: [
'__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getstate__',
'__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__',
'__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__repr__',
'__radd__', '__rmatmul__', '__rmul__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode',
'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum',
'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric',
'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower',
'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust',
'rpartition', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase',
'title', 'upper', 'zfill'
]
In [6]: dir(str)
Out[6]: [
'__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getstate__',
'__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__',
'__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__repr__',
'__radd__', '__rmatmul__', '__rmul__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode',
'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum',
'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric',
'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower',
'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust',
'rpartition', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase',
'title', 'upper', 'zfill'
]
In [7]: dir()
Out[7]: [
'In', 'Out', '_', '_2', '_3', '_5', '__', '___', '__builtin__', '__builtins__',
'__doc__', '__loader__', '__name__', '__package__', '__spec__', '_dh', '_i',
'_i1', '_i2', '_i3', '_i4', '_i5', '_i6', '_ih', '_ii', '_iii', '_oh',
'exit', 'get_ipython', 'one', 'open', 'quit', 'text'
]
IPython adds a number of identifiers in the main namespace that keep track of the inputs and outputs. In the current state In [8]
the following identifiers are:
In
, also available as an internal attribute _ih
, meaning input history is a list of the strings of inputs given to each IPython cell:
['one = 1', 'dir(one)', 'dir(int)', "text = 'hello'", "dir('hello')", 'dir(str)', 'dir()']
Each input is available as an internal identifier:
_i1 # 'one = 1'
_i2 # 'dir(one)'
_i3 # 'dir(int)'
_i4 # "text = 'hello'"
_i5 # "dir('hello')"
_i6 # 'dir(str)'
_i7 # 'dir()'
The last three are available as:
iii # "dir('hello')"
ii # 'dir(str)'
i # 'dir()'
'Out'
is a dictionary, also available as an internal attribute _oh
, meaning output history mapping input command numbers to their corresponding outputs:
{
1: None,
2: [...], # Output of dir(one) (list of attributes/methods for integer)
3: [...], # Output of dir(int) (list of attributes/methods for int class)
4: None,
5: [...], # Output of dir('hello') (list of attributes/methods for string)
6: [...], # Output of dir(str) (list of attributes/methods for str class)
7: [...] # Output of dir() (list of names in the current scope)
}
Each of these which returned a value are available as an internal identifier:
_2 # Output of dir(one) (list of attributes/methods for integer)
_3 # Output of dir(int) (list of attributes/methods for int class)
_5 # Output of dir('hello') (list of attributes/methods for string)
_6 # Output of dir(str) (list of attributes/methods for str class)
_7 # Output of dir() (list of names in the current scope)
The results of the last, second last and third last expressions that returned a value to the IPython Console are also available as:
_ # Output of dir() (list of names in the current scope)
__ # Output of dir(str) (list of attributes/methods for str class)
___ # Output of dir() (list of names in the current scope)
_dh
is an internal attribute meaning directory history and is a list of strings of directories used by the IPython console:
['c:\\users\\phili\\onedrive\\documents']
Notice that the identifiers listed in the IPython console also include '__builtins__'
, this means all the identifiers from the builtins
module are available in the namespace of the IPython console. '__builtins__'
are available for every module in Python:
In [8]: dir(__builtins__)
Out[8]: [
'ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BaseExceptionGroup',
'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError',
'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError',
'DeprecationWarning', 'EOFError', 'Ellipsis', 'EncodingWarning', 'EnvironmentError', 'Exception',
'ExceptionGroup', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '__IPYTHON__', '__build_class__', '__debug__', '__doc__', '__import__', '__loader__', '__name__', '__package__', '__spec__', 'abs', 'aiter', 'all', 'anext', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'display', 'divmod', 'enumerate', 'eval', 'exec', 'execfile', 'filter', 'float', 'format', 'frozenset', 'get_ipython', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'range', 'repr', 'reversed', 'round', 'runfile', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip'
]
The builtins
module can be explictly imported and the identifiers from the module can be examined:
In [9]: import builtins
In [10]: builtins.
# -------------------------------
# Available Identifiers for `builtins`:
# -------------------------------------
# 🔧 Functions:
# - abs(x) : Returns the absolute value of a number.
# - aiter(async_iterable) : Returns an asynchronous iterator for the object.
# - all(iterable) : Returns `True` if all elements of the iterable are true.
# - anext(async_iterator, default) : Retrieves the next item from an asynchronous iterator.
# - any(iterable) : Returns `True` if any element of the iterable is true.
# - ascii(object) : Returns a string representation with escape characters.
# - bin(x) : Converts an integer to a binary string.
# - callable(object) : Returns `True` if the object appears callable.
# - chr(i) : Converts an integer to a character.
# - classmethod(function) : Converts a function into a class method.
# - compile(source, filename, mode) : Compiles source code into code objects.
# - delattr(object, name) : Deletes an attribute from an object.
# - dir([object]) : Attempts to return a list of valid attributes.
# - divmod(a, b) : Returns a pair of quotient and remainder.
# - enumerate(iterable, start=0) : Returns an iterator of pairs (index, value).
# - eval(expression) : Evaluates the given expression in the current context.
# - exec(object) : Executes the given code or object.
# - filter(function, iterable) : Filters elements in an iterable based on a function.
# - format(value, format_spec) : Formats a value using the format specification.
# - getattr(object, name[, default]) : Returns an attribute value from an object.
# - globals() : Returns the current global symbol table.
# - hasattr(object, name) : Checks if an object has a specific attribute.
# - hash(object) : Returns the hash value of the object.
# - help([object]) : Invokes the built-in help system.
# - hex(x) : Converts an integer to a hexadecimal string.
# - id(object) : Returns the unique identity of the object.
# - input([prompt]) : Reads a line of input from the user.
# - isinstance(object, classinfo) : Checks if the object is an instance of a class
# or a tuple of classes.
# - issubclass(class, classinfo) : Checks if the class is a subclass of a class
# or a tuple of classes.
# - iter(object[, sentinel]) : Returns an iterator object.
# - len(s) : Returns the length of an object.
# - locals() : Returns a dictionary of the current local symbol table.
# - max(iterable, *[, key, default]) : Returns the largest item in an iterable.
# - min(iterable, *[, key, default]) : Returns the smallest item in an iterable.
# - next(iterator[, default]) : Retrieves the next item from an iterator.
# - open(file, mode='r', ...) : Opens a file and returns a corresponding
# file object.
# - ord(c) : Returns the Unicode code point for a character.
# - pow(base, exp[, mod]) : Returns the value of base raised to the power exp.
# - print(*objects, sep=' ', end='\n') : Prints the provided objects.
# - property(fget=None, fset=None, fdel=None, : Creates a property.
# doc=None) :
# - repr(object) : Returns the string representation of an object.
# - reversed(seq) : Returns a reverse iterator.
# - round(number[, ndigits]) : Rounds a number to a specified number of digits.
# - setattr(object, name, value) : Sets an attribute on an object.
# - slice(stop) : Returns a slice object.
# - sorted(iterable[, key, reverse]) : Returns a sorted list from an iterable.
# - staticmethod(function) : Converts a function into a static method.
# - sum(iterable[, start]) : Sums the items in an iterable.
# - super([type[, object-or-type]]) : Returns the next class in the method resolution
# order.
# - type(object) : Returns the type of the object.
# - vars([object]) : Returns the `__dict__` attribute of an object.
# - zip(*iterables) : Aggregates elements from multiple iterables.
#
# 🔧 Classes:
# - bool() : Returns a boolean value.
# - bytearray() : Returns a mutable array of bytes.
# - bytes() : Returns an immutable array of bytes.
# - complex() : Returns a complex number.
# - dict() : Returns a dictionary.
# - float() : Returns a floating-point number.
# - frozenset() : Returns an immutable set.
# - int() : Returns an integer.
# - list() : Returns a list.
# - memoryview() : Returns a memory view object.
# - object() : Base class for all objects.
# - property() : Creates a property.
# - range() : Returns a sequence of numbers.
# - set() : Returns a set.
# - staticmethod() : Converts a function to a static method.
# - str() : Returns a string.
# - super() : Returns a proxy object to delegate method calls.
# - tuple() : Returns a tuple.
# - type() : Returns the type of an object.
#
# 🔧 Constants:
# - Ellipsis : Ellipsis literal `...`.
# - False : Boolean `False`.
# - None : Represents the absence of a value.
# - True : Boolean `True`.
# - __debug__ : Constant indicating whether debugging is enabled.
#
# 🔧 Exceptions:
# - ArithmeticError() : Base class for errors in arithmetic operations.
# - AssertionError() : Raised when an assert statement fails.
# - AttributeError() : Raised when an attribute reference or assignment fails.
# - BaseException() : Base class for all built-in exceptions.
# - BufferError() : Raised when a buffer-related operation fails.
# - EOFError() : Raised when the input() function hits an end-of-file
# condition.
# - ImportError() : Raised when an import statement fails to find the module.
# - IndexError() : Raised when a sequence subscript is out of range.
# - KeyError() : Raised when a dictionary key is not found.
# - LookupError() : Base class for all lookup-related errors.
# - MemoryError() : Raised when an operation runs out of memory.
# - NameError() : Raised when a local or global name is not found.
# - NotImplementedError() : Raised when an abstract method requires implementation.
# - OSError() : Raised when a system-related operation fails.
# - OverflowError() : Raised when the result of an arithmetic operation is too # large.
# - ReferenceError() : Raised when a weak reference attempts to access a
# garbage collected referent.
# - RuntimeError() : Raised when an error is detected that doesn’t fall under
# any other category.
# - StopIteration() : Raised by an iterator's next() method to indicate that
# there are no further items.
# - SyntaxError() : Raised when the parser encounters a syntax error.
# - TypeError() : Raised when an operation or function is applied to an
# object of inappropriate type.
# - ValueError() : Raised when a function receives an argument of the right
# type but inappropriate value.
# - Warning() : Base class for warning categories.
# - ZeroDivisionError() : Raised when a division or modulo operation is performed
# with zero as the divisor.
# -------------------------------------
The user generally only uses the functions
, lower cases classes
, the constants
but will often encounter the Exceptions
while doing so!
In builtins
, most of the lowercase classes have a shorthand version of instantiation:
In [10]: str('hello')
Out[10]: 'hello'
In [11]: int(1)
Out[11]: 1
In [12]: bool(True)
Out[12]: True
In [13]: float(3.14)
Out[13]: 3.14
In [14]: tuple(('a', 'b', 'c', 1, 2, 3))
Out[14]: ('a', 'b', 'c', 1, 2, 3)
In [15]: list(('a', 'b', 'c', 1, 2, 3))
Out[15]: ['a', 'b', 'c', 1, 2, 3]
In [16]: set(('a', 'b', 'c', 1, 2, 3))
Out[16]: {'a', 'b', 'c', 1, 2, 3}
In [16]: dict('a'=1, 'b'=2, 'c'=3)
Out[16]: {'a': 1, 'b': 2, 'c': 3}
The builtins
lowercase classes
are however routinely used to cast from one builtins
class to another. For example:
In [17]: str(3)
Out[17]: '3'
In [18]: float(1)
Out[18]: 1.0
Notice that the builtins
functions frequently have object
as an input parameter. Previously the concept of subclassing was explored when UserString
was used as a base for the CustomString
. In Python everything is based opon the object
base class:
In [19]: dir(object)
Out[19]: [
'__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'
]
In [20]: object.
# -------------------------------
# Available Identifiers for `object`:
# -------------------------------------
# 🔧 Functions:
# - __init__(self, /, *args, **kwargs) : Initializes the object.
# - __new__(*args, **kwargs) : Creates a new instance of the class.
# - __delattr__(self, name, /) : Defines behavior for when an attribute is deleted.
# - __dir__(self, /) : Default dir() implementation.
# - __sizeof__(self, /) : Returns the size of the object in memory, in bytes.
# - __eq__(self, value, /) : Checks for equality with another object.
# - __ne__(self, value, /) : Checks for inequality with another object.
# - __lt__(self, value, /) : Checks if the object is less than another.
# - __le__(self, value, /) : Checks if the object is less than or equal to another.
# - __gt__(self, value, /) : Checks if the object is greater than another.
# - __ge__(self, value, /) : Checks if the object is greater than or equal to another.
# - __repr__(self, /) : Returns a string representation of the object.
# - __str__(self, /) : Returns a string for display purposes.
# - __format__(self, format_spec, /) : Returns a formatted string representation of the object.
# - __hash__(self, /) : Returns a hash of the object.
# - __getattribute__(self, name, /) : Gets an attribute from the object.
# - __setattr__(self, name, value, /) : Sets an attribute on the object.
# - __delattr__(self, name, /) : Deletes an attribute from the object.
# - __reduce__(self, /) : Prepares the object for pickling.
# - __reduce_ex__(self, protocol, /) : Similar to __reduce__, with a protocol argument.
# - __init_subclass__(...) : Called when a class is subclassed; default
# implementation does nothing.
# - __subclasshook__(...) : Customize issubclass() for abstract classes.
#
# 🔍 Attributes:
# - __class__ : The class of the object.
# - __doc__ : The docstring of the object.
# -------------------------------------
Some of the following object
based data model identifiers have already been discussed. Recall that the constructor __new__
(dunder new) is used to construct a new instance, which internally in the class is known as self
. During construction __new__
calls the method __init__
to initialise the instance self
with initialisation data. When the object
class is input with open parenthesis, the docstring of __init__
(dunder init) displays outlining what initialisation data needs to be provided:
In [21]: exit
In [1]: object(
# Docstring popup:
"""
The base class of the class hierarchy.
When called, it accepts no arguments and returns a new featureless
instance that has no instance attributes and cannot be given any.
"""
For the object
class, no initialisation data is required. When the instance is assigned to an object
name it is displayed on the Variable Explorer:
In [22]: exit
In [1]: instance1 = object()
In [2]: instance2 = object()
In [3]: instance3 = object()
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
instance1 | object | 1 | <object at 0x29cfafde710> |
instance2 | object | 1 | <object at 0x29cfafde580> |
instance3 | object | 1 | <object at 0x29cfafde9a0> |
__class__
(dunder class) is an attribute corresponding to the class of an instance. Although many of the dunder methods correspond to a builtins
function or class without the dunder. In the case of the keyword class
, it is used to classify a class. Instead the builtins
class type
is used to determine the class type:
In [4]: type(instance1)
Out[4]: object
In [5]: type('hello')
Out[5]: str
In [6]: type(2)
Out[6]: int
The method resolution order method will return a list of classes:
In [7]: object.mro()
Out[7]: [object]
In [8]: object.mro()
Out[8]: [int, object]
In [9]: bool.mro()
Out[9]: [bool, int, object]
__sizeof__
(dunder sizeof) is a method which retrieves the size of an instance in bytes. It is not normally used directly and defines how the getsizeof
function found in the sys
module behaves on the instance:
In [10]: import sys
In [11]: sys.getsizeof(instance1)
Out[11]: 16
In [12]: sys.getsizeof(instance2)
Out[12]: 16
In [13]: sys.getsizeof('')
Out[13]: 49
In [14]: sys.getsizeof('hello')
Out[14]: 54
Notice object
is the base class of all classes and is always the last item in the list. The method resolution order for int
states that the class should use a method defined in the int
class and if it's not defined here, it should fall back on the implementation in object
. The method resolution order for bool
, shows that the bool
is a subclass of the int
class which in turn is a subclass of the object
base class.
Because object
is the base
class all the other classes follow the design pattern of the object
class and have consistent identifiers. The identifiers display for the int
and str
classes when int.
or str.
are input, for convenience, the ones consistent with the object
class are categorised separately:
In [15]: int.
# -------------------------------
# Available Identifiers Consistent with `object`:
# -------------------------------------
# 🔧 Functions:
# - __init__(self, /, *args, **kwargs) : Initializes the object.
# - __new__(*args, **kwargs) : Creates a new instance of the class.
# - __delattr__(self, name, /) : Defines behavior for when an attribute is deleted.
# - __dir__(self, /) : Default dir() implementation.
# - __sizeof__(self, /) : Returns the size of the object in memory, in bytes.
# - __eq__(self, value, /) : Checks for equality with another object.
# - __ne__(self, value, /) : Checks for inequality with another object.
# - __lt__(self, value, /) : Checks if the object is less than another.
# - __le__(self, value, /) : Checks if the object is less than or equal to another.
# - __gt__(self, value, /) : Checks if the object is greater than another.
# - __ge__(self, value, /) : Checks if the object is greater than or equal to another.
# - __repr__(self, /) : Returns a string representation of the object.
# - __str__(self, /) : Returns a string for display purposes.
# - __format__(self, format_spec, /) : Returns a formatted string representation of the object.
# - __hash__(self, /) : Returns a hash of the object.
# - __getattribute__(self, name, /) : Gets an attribute from the object.
# - __setattr__(self, name, value, /) : Sets an attribute on the object.
# - __delattr__(self, name, /) : Deletes an attribute from the object.
# - __reduce__(self, /) : Prepares the object for pickling.
# - __reduce_ex__(self, protocol, /) : Similar to __reduce__, with a protocol argument.
# - __init_subclass__(...) : Called when a class is subclassed; default
# implementation does nothing.
# - __subclasshook__(...) : Customize issubclass() for abstract classes.
#
# 🔍 Attributes:
# - __class__ : The class of the object.
# - __doc__ : The docstring of the object.
# -------------------------------
# Additional Identifiers:
# -------------------------------------
# 🔧 Functions:
# - __abs__(self, /) : Returns the absolute value of the integer.
# - __add__(self, value, /) : Adds two integers.
# - __and__(self, value, /) : Performs a bitwise AND operation.
# - __ceil__(self, /) : Returns the ceiling value of the integer.
# - __divmod__(self, value, /) : Returns the quotient and remainder as a tuple.
# - __float__(self, /) : Converts the integer to a float.
# - __floor__(self, /) : Returns the floor value of the integer.
# - __floordiv__(self, value, /) : Performs floor division.
# - __index__(self, /) : Returns the integer itself (used for slicing).
# - __int__(self, /) : Returns the integer itself.
# - __invert__(self, /) : Returns the bitwise inversion of the integer.
# - __lshift__(self, value, /) : Performs a bitwise left shift.
# - __mod__(self, value, /) : Returns the remainder of division.
# - __mul__(self, value, /) : Multiplies two integers.
# - __neg__(self, /) : Negates the integer.
# - __or__(self, value, /) : Performs a bitwise OR operation.
# - __pos__(self, /) : Returns the positive value of the integer.
# - __pow__(self, value, mod=None, /) : Raises the integer to a power, optionally modulo.
# - __radd__(self, value, /) : Adds two integers (for reversed operands).
# - __rand__(self, value, /) : Performs a bitwise AND (for reversed operands).
# - __rdivmod__(self, value, /) : Returns quotient and remainder (for reversed operands).
# - __rfloordiv__(self, value, /) : Performs floor division (for reversed operands).
# - __rlshift__(self, value, /) : Performs bitwise left shift (for reversed operands).
# - __rmod__(self, value, /) : Returns remainder (for reversed operands).
# - __rmul__(self, value, /) : Multiplies two integers (for reversed operands).
# - __ror__(self, value, /) : Performs bitwise OR (for reversed operands).
# - __rpow__(self, value, mod=None, /) : Raises another number to the integer's power.
# - __rrshift__(self, value, /) : Performs bitwise right shift (for reversed operands).
# - __rshift__(self, value, /) : Performs bitwise right shift.
# - __rsub__(self, value, /) : Subtracts from another number (for reversed operands).
# - __rtruediv__(self, value, /) : Performs true division (for reversed operands).
# - __rxor__(self, value, /) : Performs bitwise XOR (for reversed operands).
# - __sub__(self, value, /) : Subtracts another number from the integer.
# - __truediv__(self, value, /) : Performs true division.
# - __trunc__(self, /) : Returns the truncated value of the integer.
# - __xor__(self, value, /) : Performs bitwise XOR.
# 🔍 Attributes:
# - bit_length(self, /) : Returns the number of bits necessary to represent the integer.
# - conjugate(self, /) : Returns the integer itself (used in complex numbers).
# - denominator(self, /) : Always returns 1 (for rational number support).
# - from_bytes(bytes, byteorder, *, signed=False): Converts a byte array to an integer.
# - imag(self, /) : Always returns 0 (used in complex numbers).
# - numerator(self, /) : Returns the integer itself (for rational number support).
# - real(self, /) : Returns the real part of the integer.
# - to_bytes(length, byteorder, *, signed=False): Converts the integer to an array of bytes.
In [15]: int.
# -------------------------------
# Available Identifiers Consistent with `object`:
# -------------------------------------
# 🔧 Functions:
# - __init__(self, /, *args, **kwargs) : Initializes the object.
# - __new__(*args, **kwargs) : Creates a new instance of the class.
# - __delattr__(self, name, /) : Defines behavior for when an attribute is deleted.
# - __dir__(self, /) : Default dir() implementation.
# - __sizeof__(self, /) : Returns the size of the object in memory, in bytes.
# - __eq__(self, value, /) : Checks for equality with another object.
# - __ne__(self, value, /) : Checks for inequality with another object.
# - __lt__(self, value, /) : Checks if the object is less than another.
# - __le__(self, value, /) : Checks if the object is less than or equal to another.
# - __gt__(self, value, /) : Checks if the object is greater than another.
# - __ge__(self, value, /) : Checks if the object is greater than or equal to another.
# - __repr__(self, /) : Returns a string representation of the object.
# - __str__(self, /) : Returns a string for display purposes.
# - __format__(self, format_spec, /) : Returns a formatted string representation of the object.
# - __hash__(self, /) : Returns a hash of the object.
# - __getattribute__(self, name, /) : Gets an attribute from the object.
# - __setattr__(self, name, value, /) : Sets an attribute on the object.
# - __delattr__(self, name, /) : Deletes an attribute from the object.
# - __reduce__(self, /) : Prepares the object for pickling.
# - __reduce_ex__(self, protocol, /) : Similar to __reduce__, with a protocol argument.
# - __init_subclass__(...) : Called when a class is subclassed; default
# implementation does nothing.
# - __subclasshook__(...) : Customize issubclass() for abstract classes.
#
# 🔍 Attributes:
# - __class__ : The class of the object.
# - __doc__ : The docstring of the object.
# -------------------------------
# Additional Identifiers:
# -------------------------------------
# 🔧 Functions:
# - __add__(self, value, /) : Concatenates two strings.
# - __contains__(self, key, /) : Checks if a substring is in the string.
# - __getitem__(self, key, /) : Returns a character or a slice of the string.
# - __iter__(self, /) : Returns an iterator over the characters in the string.
# - __len__(self, /) : Returns the length of the string.
# - __mod__(self, value, /) : Performs string formatting using % formatting.
# - __mul__(self, value, /) : Repeats the string by multiplying with an integer.
# - __rmod__(self, value, /) : Reverses string formatting using % formatting.
# - __rmul__(self, value, /) : Repeats the string with reversed multiplication.
# - capitalize(self, /) : Returns a copy of the string with its first character capitalized.
# - casefold(self, /) : Returns a version of the string that is case-insensitive.
# - center(self, width, fillchar=' ', /) : Returns a centered version of the string.
# - count(self, sub, start=0, end=-1, /) : Returns the number of occurrences of a substring.
# - encode(self, encoding='utf-8', : Returns an encoded version of the string.
# errors='strict', /)
# - endswith(self, suffix, start=0, end=-1, /) : Checks if the string ends with the given suffix.
# - expandtabs(self, tabsize=8, /) : Returns a copy with tabs expanded into spaces.
# - find(self, sub, start=0, end=-1, /) : Returns the lowest index of the substring, or -1 if not found.
# - format(self, *args, **kwargs) : Performs string formatting.
# - format_map(self, mapping, /) : Performs string formatting using a mapping.
# - index(self, sub, start=0, end=-1, /) : Returns the lowest index of the substring, raises ValueError if not found.
# - isalnum(self, /) : Returns True if all characters are alphanumeric.
# - isalpha(self, /) : Returns True if all characters are alphabetic.
# - isascii(self, /) : Returns True if all characters are ASCII.
# - isdecimal(self, /) : Returns True if all characters are decimal digits.
# - isdigit(self, /) : Returns True if all characters are digits.
# - isidentifier(self, /) : Returns True if the string is a valid identifier.
# - islower(self, /) : Returns True if all characters are lowercase.
# - isnumeric(self, /) : Returns True if all characters are numeric.
# - isprintable(self, /) : Returns True if all characters are printable.
# - isspace(self, /) : Returns True if all characters are whitespace.
# - istitle(self, /) : Returns True if the string is in title case.
# - isupper(self, /) : Returns True if all characters are uppercase.
# - join(self, iterable, /) : Concatenates the elements of an iterable with the string as the separator.
# - ljust(self, width, fillchar=' ', /) : Left-aligns the string within a given width.
# - lower(self, /) : Returns a copy of the string in lowercase.
# - lstrip(self, chars=None, /) : Returns a copy of the string with leading whitespace removed.
# - partition(self, sep, /) : Splits the string into a tuple (head, separator, tail).
# - replace(self, old, new, count=-1, /) : Returns a copy of the string with occurrences of a substring replaced.
# - rfind(self, sub, start=0, end=-1, /) : Returns the highest index of the substring, or -1 if not found.
# - rindex(self, sub, start=0, end=-1, /) : Returns the highest index of the substring, raises ValueError if not found.
# - rjust(self, width, fillchar=' ', /) : Right-aligns the string within a given width.
# - rpartition(self, sep, /) : Splits the string into a tuple (head, separator, tail), starting from the right.
# - rsplit(self, sep=None, maxsplit=-1, /) : Splits the string from the right.
# - rstrip(self, chars=None, /) : Returns a copy of the string with trailing whitespace removed.
# - split(self, sep=None, maxsplit=-1, /) : Splits the string into a list of substrings.
# - splitlines(self, keepends=False, /) : Splits the string at line breaks.
# - startswith(self, prefix, start=0, end=-1, /): Checks if the string starts with the given prefix.
# - strip(self, chars=None, /) : Returns a copy of the string with leading and trailing whitespace removed.
# - swapcase(self, /) : Returns a copy of the string with uppercase and lowercase swapped.
# - title(self, /) : Returns a title-cased version of the string.
# - translate(self, table, /) : Returns a copy of the string where each character is mapped to its translation.
# - upper(self, /) : Returns a copy of the string in uppercase.
# - zfill(self, width, /) : Returns a copy of the string left-padded with '0' to the given width.
Data Model Methods (__eq__
, __ne__
, __lt__
, __gt__
, __le__
, __ge__
): Comparison Operators (==
, !=
, <
, >
, <=
, >=
)
The following 3 object
instances are shown in the Variable Explorer:
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
instance1 | object | 1 | <object at 0x29cfafde710> |
instance2 | object | 1 | <object at 0x29cfafde580> |
instance3 | object | 1 | <object at 0x29cfafde9a0> |
The object
class has 6 comparison operators, although only 2 of them are defined. The equals to operator ==
is True
only when the object
is the same object
in memory. The not equals to operator !=
is the inverse. Note do not confuse the equals to operator ==
with assignment =
:
In [15]: instance1 == instance2
Out[15]: False
In [16]: instance1 != instance2
Out[16]: True
If an alias to instance1
is assigned:
In [17]: alias = instance1
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
alias | object | 1 | <object at 0x29cfafde710> |
instance1 | object | 1 | <object at 0x29cfafde710> |
instance2 | object | 1 | <object at 0x29cfafde580> |
instance3 | object | 1 | <object at 0x29cfafde9a0> |
alias
can be seen to be equal to instance1
:
In [18]: alias == instance1
Out[18]: True
In the case of the oject
class, the keyword is
can be sued to check if two object
isntances are the same object
instance. Therefore is
and ==
in this case behave consistently:
In [16]: instance1 is instance2
Out[16]: False
In [17]: instance1 is alias
Out[17]: True
When attempting to use one of the other 4 comparison operators weith object
instances a TypeError
displays:
In [18]: instance1 > instance2
Traceback (most recent call last):
Cell In[18], line 1
instance1 > instance2
TypeError: '>' not supported between instances of 'object' and 'object'
The six comparison operators are defined when a class is ordinal, for example an int
instance:
In [19]: 1 == 2 # equals to?
Out[19]: False
In [20]: 1 != 2 # not equals to?
Out[20]: True
In [21]: 1 < 2 # less than?
Out[21]: True
In [22]: 2 < 2
Out[22]: False
In [23]: 2 <= 2 # less than or equals to?
Out[23]: True
In [24]: (2 < 2) or (2 == 2) # less than or equals to? (explicit)
Out[24]: True
In [25]: 2 > 2 # greater than?
Out[25]: False
In [26]: 2 > 3 # greater than?
Out[26]: False
In [27]: 2 >= 2 # greater than or equals to?
Out[27]: True
In [28]: (2 >= 2) or (2 == 2) # greater than or equals to? (explicit)
Out[28]: True
In [29]: 3 is 3 # is the same instance?
Out[29]: True
In [30]: (2 < 2) and (2 == 2) # condition1 and condition2 are True?
Out[30]: False
In [31]: (2 < 3) and (2 > 1) # condition1 and condition 2 are True?
Out[31]: False
In [32]: (2 < 2) or (2 == 2) # condition1 or condition2 are True?
Out[32]: False
In [33]: (2 < 3) or (2 > 1) # condition1 or condition 2 are True?
Out[33]: False
Each object
has two string representations, the formal and informal string representation:
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
alias | object | 1 | <object at 0x29cfafde710> |
instance1 | object | 1 | <object at 0x29cfafde710> |
instance2 | object | 1 | <object at 0x29cfafde580> |
instance3 | object | 1 | <object at 0x29cfafde9a0> |
For the object
class both of these return the same value outlining where the object
instance is stored in memory:
In [34]: str(instance1)
Out[34]: '<object object at 0x0000029cfafde710>'
In [35]: repr(instance1)
Out[35]: '<object object at 0x0000029cfafde710>'
Begineers often fail to see the difference between these two strign repesentations as many classes like the int
and float
give identical values for each representation. The difference between the string representations is subtle in the str
class however is more apparent when the str
instance has an escape character. If the following is examined:
In [36]: text = 'hello\tworld'
In [37]: text
Out[37]: 'hello\tworld'
In [37]: print(text)
hello world
In [36]
is what is input to instantiate an instance of the str
class and Out[37]
matches this form.
The printed output shown under In [37]
formats the special characters. The '
which are used to enclose the str
are not shown and the \t
escape character is shown as a tab. Notice the vVriable Explorer shows this printed form for convenience:
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
alias | object | 1 | <object at 0x29cfafde710> |
instance1 | object | 1 | <object at 0x29cfafde710> |
instance2 | object | 1 | <object at 0x29cfafde580> |
instance3 | object | 1 | <object at 0x29cfafde9a0> |
text | str | 11 | hello world |
Using the str
class, on an instance of the str
class itself, it should cast a str
to a str
instance leaving it unchanged:
In [38]: str(text)
Out[38]: 'hello\tworld'
This is known as the informal representation.
Using the repr
function on an instance of the str
class changes it to the following:
In [39]: repr(text)
Out[39]: "'hello\\tworld'"
Notice that the single quotations '
which enclosed text
are in the return value incorporated into the str
and therefore the str
returned now is enclosed with double quotations "
. Notice also that \t
which is an instruction to insert an escape character now becomes \\t
in the return value. Recall the first \
in \\
is an instruction to insert an escape character and the second \
in \\
means that the character to be inserted is \
itself.
When repr(text)
is printed:
In [40]: print(repr(text))
'hello\tworld'
It matches the form that is input to an IPython cell to instantiate a str
instance, which matches what is returned to the IPython cell:
In [41]: 'hello\tworld'
Out[41]: 'hello\tworld'
Another example is the fractions.Fraction
class. If the fractions
module is imported and fractions.Fraction
is input with open parenthesis, the docstring displays:
In [42]: import fractions
In [43]: fractions.Fraction(
# Docstring popup:
"""
Init signature: fractions.Fraction(numerator=0, denominator=None)
Docstring:
This class implements rational numbers.
In the two-argument form of the constructor, Fraction(8, 6) will
produce a rational number equivalent to 4/3. Both arguments must
be Rational. The numerator defaults to 0 and the denominator
defaults to 1 so that Fraction(3) == 3 and Fraction() == 0.
Fractions can also be constructed from:
- numeric strings similar to those accepted by the
float constructor (for example, '-2.3' or '1e10')
- strings of the form '123/456'
- float and Decimal instances
- other Rational instances (including integers)
File: c:\users\appslab\miniforge3\envs\spyder-env\lib\fractions.py
Type: ABCMeta
"""
A numerator and denominator are required and have the default values 0
and 1
:
In [43]: fractions.Fraction(numerator=5, denominator=3)
Out[43]: Fraction(5, 3)
Notice the return value shown in the cell output shows how to preferentially instantiate this class, assuming the Fractions
class is in the __main__
namespace and prefers the input arguments to be supplied positionally.
This can be instantiated to the instance f1
in the manner outlined:
In [44]: from fractions import Fraction
In [45]: f1 = Fraction(5, 3)
Notice when f1
is printed that it displays in the following form:
In [46]: print(f1)
5/3
Which matches how it should display on the Variable Explorer:
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
f1 | fractions.Fraction | 1 | 5/3 |
alias | object | 1 | <object at 0x29cfafde710> |
instance1 | object | 1 | <object at 0x29cfafde710> |
instance2 | object | 1 | <object at 0x29cfafde580> |
instance3 | object | 1 | <object at 0x29cfafde9a0> |
text | str | 11 | hello world |
And therefore the informal and formal string representation can be compared:
In [47]: str(f1)
Out[47]: '5/3'
In [48]: repr(f1)
Out[48]: 'Fraction(5, 3)'
Notice that when these are printed, the following are displayed:
In [48]: print(str(f1))
5/3
In [49]: repr(f1)
Fraction(5, 3)
The print out of the informal representation displays how a Fraction
instance can essentially be cast ito a float
instance:
In [50]: 5/3
Out[50]: 1.6666666666666667
In [51]: float(Fraction(5, 3))
Out[51]: 1.6666666666666667
The print out of the formal representation displays how to instantiate a Fraction
instance:
In [52]: Fraction(5, 3)
Out[52]: Fraction(5, 3)
A formatted string can be used to insert a Variable into a string. The str
is prepended with f
and the Variable is enclosed in braces {}
:
In [53]: f'text text text {Variable}'
For example:
In [53]: f'The fraction is {-f1}'
Out[53]: 'The fraction is -5/3'
The __format__
data model method defines how the builtins
function format
behaves and within a formatted string controls how the Variable is displayed using a format specifier. In a fstring the format specifier is seperated from the Variable using a colon:
In [54]: f'text text text {Variable:FormatSpec}'
For example:
In [54]: f'The fraction is {-f1:05.1f}'
Out[54]: 'The fraction is -01.7'
05.1f
:
0
means show leading zeros.5
means a character width of 5 (note this includes the decimal point and sign).
groups together properties associated with the decimal point.1
means show 1 decimal place after the decimal point.f
is the fixed format.
In [55]: f'The fraction is {-f1:09.1e}'
Out[55]: 'The fraction is -01.7e+00'
09.1e
:
0
means show leading zeros.9
means a character width of 9 (note this includes the decimal point, sign, e, e sign, e power).
groups together properties associated with the decimal point.1
means show 1 decimal place after the decimal point.f
is the fixed format.
The Fractions
class can be examined (use the show/hide below to quickly glance at the standard module):
fractions.py
At this point, you are unlikely to understand everything in the fractions.py
however you should be able to examine the data model methods and see how they cntrol the behaviour of string representation and string formatting:
__repr__
__str__
__format__
The Fractions
class also has examples of alternative constructors which use classmethods:
# Originally contributed by Sjoerd Mullender.
# Significantly modified by Jeffrey Yasskin <jyasskin at gmail.com>.
"""Fraction, infinite-precision, rational numbers."""
from decimal import Decimal
import functools
import math
import numbers
import operator
import re
import sys
__all__ = ['Fraction']
# Constants related to the hash implementation; hash(x) is based
# on the reduction of x modulo the prime _PyHASH_MODULUS.
_PyHASH_MODULUS = sys.hash_info.modulus
# Value to be used for rationals that reduce to infinity modulo
# _PyHASH_MODULUS.
_PyHASH_INF = sys.hash_info.inf
@functools.lru_cache(maxsize = 1 << 14)
def _hash_algorithm(numerator, denominator):
# To make sure that the hash of a Fraction agrees with the hash
# of a numerically equal integer, float or Decimal instance, we
# follow the rules for numeric hashes outlined in the
# documentation. (See library docs, 'Built-in Types').
try:
dinv = pow(denominator, -1, _PyHASH_MODULUS)
except ValueError:
# ValueError means there is no modular inverse.
hash_ = _PyHASH_INF
else:
# The general algorithm now specifies that the absolute value of
# the hash is
# (|N| * dinv) % P
# where N is self._numerator and P is _PyHASH_MODULUS. That's
# optimized here in two ways: first, for a non-negative int i,
# hash(i) == i % P, but the int hash implementation doesn't need
# to divide, and is faster than doing % P explicitly. So we do
# hash(|N| * dinv)
# instead. Second, N is unbounded, so its product with dinv may
# be arbitrarily expensive to compute. The final answer is the
# same if we use the bounded |N| % P instead, which can again
# be done with an int hash() call. If 0 <= i < P, hash(i) == i,
# so this nested hash() call wastes a bit of time making a
# redundant copy when |N| < P, but can save an arbitrarily large
# amount of computation for large |N|.
hash_ = hash(hash(abs(numerator)) * dinv)
result = hash_ if numerator >= 0 else -hash_
return -2 if result == -1 else result
_RATIONAL_FORMAT = re.compile(r"""
\A\s* # optional whitespace at the start,
(?P<sign>[-+]?) # an optional sign, then
(?=\d|\.\d) # lookahead for digit or .digit
(?P<num>\d*|\d+(_\d+)*) # numerator (possibly empty)
(?: # followed by
(?:\s*/\s*(?P<denom>\d+(_\d+)*))? # an optional denominator
| # or
(?:\.(?P<decimal>\d*|\d+(_\d+)*))? # an optional fractional part
(?:E(?P<exp>[-+]?\d+(_\d+)*))? # and optional exponent
)
\s*\Z # and optional whitespace to finish
""", re.VERBOSE | re.IGNORECASE)
# Helpers for formatting
def _round_to_exponent(n, d, exponent, no_neg_zero=False):
"""Round a rational number to the nearest multiple of a given power of 10.
Rounds the rational number n/d to the nearest integer multiple of
10**exponent, rounding to the nearest even integer multiple in the case of
a tie. Returns a pair (sign: bool, significand: int) representing the
rounded value (-1)**sign * significand * 10**exponent.
If no_neg_zero is true, then the returned sign will always be False when
the significand is zero. Otherwise, the sign reflects the sign of the
input.
d must be positive, but n and d need not be relatively prime.
"""
if exponent >= 0:
d *= 10**exponent
else:
n *= 10**-exponent
# The divmod quotient is correct for round-ties-towards-positive-infinity;
# In the case of a tie, we zero out the least significant bit of q.
q, r = divmod(n + (d >> 1), d)
if r == 0 and d & 1 == 0:
q &= -2
sign = q < 0 if no_neg_zero else n < 0
return sign, abs(q)
def _round_to_figures(n, d, figures):
"""Round a rational number to a given number of significant figures.
Rounds the rational number n/d to the given number of significant figures
using the round-ties-to-even rule, and returns a triple
(sign: bool, significand: int, exponent: int) representing the rounded
value (-1)**sign * significand * 10**exponent.
In the special case where n = 0, returns a significand of zero and
an exponent of 1 - figures, for compatibility with formatting.
Otherwise, the returned significand satisfies
10**(figures - 1) <= significand < 10**figures.
d must be positive, but n and d need not be relatively prime.
figures must be positive.
"""
# Special case for n == 0.
if n == 0:
return False, 0, 1 - figures
# Find integer m satisfying 10**(m - 1) <= abs(n)/d <= 10**m. (If abs(n)/d
# is a power of 10, either of the two possible values for m is fine.)
str_n, str_d = str(abs(n)), str(d)
m = len(str_n) - len(str_d) + (str_d <= str_n)
# Round to a multiple of 10**(m - figures). The significand we get
# satisfies 10**(figures - 1) <= significand <= 10**figures.
exponent = m - figures
sign, significand = _round_to_exponent(n, d, exponent)
# Adjust in the case where significand == 10**figures, to ensure that
# 10**(figures - 1) <= significand < 10**figures.
if len(str(significand)) == figures + 1:
significand //= 10
exponent += 1
return sign, significand, exponent
# Pattern for matching float-style format specifications;
# supports 'e', 'E', 'f', 'F', 'g', 'G' and '%' presentation types.
_FLOAT_FORMAT_SPECIFICATION_MATCHER = re.compile(r"""
(?:
(?P<fill>.)?
(?P<align>[<>=^])
)?
(?P<sign>[-+ ]?)
(?P<no_neg_zero>z)?
(?P<alt>\#)?
# A '0' that's *not* followed by another digit is parsed as a minimum width
# rather than a zeropad flag.
(?P<zeropad>0(?=[0-9]))?
(?P<minimumwidth>0|[1-9][0-9]*)?
(?P<thousands_sep>[,_])?
(?:\.(?P<precision>0|[1-9][0-9]*))?
(?P<presentation_type>[eEfFgG%])
""", re.DOTALL | re.VERBOSE).fullmatch
class Fraction(numbers.Rational):
"""This class implements rational numbers.
In the two-argument form of the constructor, Fraction(8, 6) will
produce a rational number equivalent to 4/3. Both arguments must
be Rational. The numerator defaults to 0 and the denominator
defaults to 1 so that Fraction(3) == 3 and Fraction() == 0.
Fractions can also be constructed from:
- numeric strings similar to those accepted by the
float constructor (for example, '-2.3' or '1e10')
- strings of the form '123/456'
- float and Decimal instances
- other Rational instances (including integers)
"""
__slots__ = ('_numerator', '_denominator')
# We're immutable, so use __new__ not __init__
def __new__(cls, numerator=0, denominator=None):
"""Constructs a Rational.
Takes a string like '3/2' or '1.5', another Rational instance, a
numerator/denominator pair, or a float.
Examples
--------
>>> Fraction(10, -8)
Fraction(-5, 4)
>>> Fraction(Fraction(1, 7), 5)
Fraction(1, 35)
>>> Fraction(Fraction(1, 7), Fraction(2, 3))
Fraction(3, 14)
>>> Fraction('314')
Fraction(314, 1)
>>> Fraction('-35/4')
Fraction(-35, 4)
>>> Fraction('3.1415') # conversion from numeric string
Fraction(6283, 2000)
>>> Fraction('-47e-2') # string may include a decimal exponent
Fraction(-47, 100)
>>> Fraction(1.47) # direct construction from float (exact conversion)
Fraction(6620291452234629, 4503599627370496)
>>> Fraction(2.25)
Fraction(9, 4)
>>> Fraction(Decimal('1.47'))
Fraction(147, 100)
"""
self = super(Fraction, cls).__new__(cls)
if denominator is None:
if type(numerator) is int:
self._numerator = numerator
self._denominator = 1
return self
elif isinstance(numerator, numbers.Rational):
self._numerator = numerator.numerator
self._denominator = numerator.denominator
return self
elif isinstance(numerator, (float, Decimal)):
# Exact conversion
self._numerator, self._denominator = numerator.as_integer_ratio()
return self
elif isinstance(numerator, str):
# Handle construction from strings.
m = _RATIONAL_FORMAT.match(numerator)
if m is None:
raise ValueError('Invalid literal for Fraction: %r' %
numerator)
numerator = int(m.group('num') or '0')
denom = m.group('denom')
if denom:
denominator = int(denom)
else:
denominator = 1
decimal = m.group('decimal')
if decimal:
decimal = decimal.replace('_', '')
scale = 10**len(decimal)
numerator = numerator * scale + int(decimal)
denominator *= scale
exp = m.group('exp')
if exp:
exp = int(exp)
if exp >= 0:
numerator *= 10**exp
else:
denominator *= 10**-exp
if m.group('sign') == '-':
numerator = -numerator
else:
raise TypeError("argument should be a string "
"or a Rational instance")
elif type(numerator) is int is type(denominator):
pass # *very* normal case
elif (isinstance(numerator, numbers.Rational) and
isinstance(denominator, numbers.Rational)):
numerator, denominator = (
numerator.numerator * denominator.denominator,
denominator.numerator * numerator.denominator
)
else:
raise TypeError("both arguments should be "
"Rational instances")
if denominator == 0:
raise ZeroDivisionError('Fraction(%s, 0)' % numerator)
g = math.gcd(numerator, denominator)
if denominator < 0:
g = -g
numerator //= g
denominator //= g
self._numerator = numerator
self._denominator = denominator
return self
@classmethod
def from_float(cls, f):
"""Converts a finite float to a rational number, exactly.
Beware that Fraction.from_float(0.3) != Fraction(3, 10).
"""
if isinstance(f, numbers.Integral):
return cls(f)
elif not isinstance(f, float):
raise TypeError("%s.from_float() only takes floats, not %r (%s)" %
(cls.__name__, f, type(f).__name__))
return cls._from_coprime_ints(*f.as_integer_ratio())
@classmethod
def from_decimal(cls, dec):
"""Converts a finite Decimal instance to a rational number, exactly."""
from decimal import Decimal
if isinstance(dec, numbers.Integral):
dec = Decimal(int(dec))
elif not isinstance(dec, Decimal):
raise TypeError(
"%s.from_decimal() only takes Decimals, not %r (%s)" %
(cls.__name__, dec, type(dec).__name__))
return cls._from_coprime_ints(*dec.as_integer_ratio())
@classmethod
def _from_coprime_ints(cls, numerator, denominator, /):
"""Convert a pair of ints to a rational number, for internal use.
The ratio of integers should be in lowest terms and the denominator
should be positive.
"""
obj = super(Fraction, cls).__new__(cls)
obj._numerator = numerator
obj._denominator = denominator
return obj
def is_integer(self):
"""Return True if the Fraction is an integer."""
return self._denominator == 1
def as_integer_ratio(self):
"""Return a pair of integers, whose ratio is equal to the original Fraction.
The ratio is in lowest terms and has a positive denominator.
"""
return (self._numerator, self._denominator)
def limit_denominator(self, max_denominator=1000000):
"""Closest Fraction to self with denominator at most max_denominator.
>>> Fraction('3.141592653589793').limit_denominator(10)
Fraction(22, 7)
>>> Fraction('3.141592653589793').limit_denominator(100)
Fraction(311, 99)
>>> Fraction(4321, 8765).limit_denominator(10000)
Fraction(4321, 8765)
"""
# Algorithm notes: For any real number x, define a *best upper
# approximation* to x to be a rational number p/q such that:
#
# (1) p/q >= x, and
# (2) if p/q > r/s >= x then s > q, for any rational r/s.
#
# Define *best lower approximation* similarly. Then it can be
# proved that a rational number is a best upper or lower
# approximation to x if, and only if, it is a convergent or
# semiconvergent of the (unique shortest) continued fraction
# associated to x.
#
# To find a best rational approximation with denominator <= M,
# we find the best upper and lower approximations with
# denominator <= M and take whichever of these is closer to x.
# In the event of a tie, the bound with smaller denominator is
# chosen. If both denominators are equal (which can happen
# only when max_denominator == 1 and self is midway between
# two integers) the lower bound---i.e., the floor of self, is
# taken.
if max_denominator < 1:
raise ValueError("max_denominator should be at least 1")
if self._denominator <= max_denominator:
return Fraction(self)
p0, q0, p1, q1 = 0, 1, 1, 0
n, d = self._numerator, self._denominator
while True:
a = n//d
q2 = q0+a*q1
if q2 > max_denominator:
break
p0, q0, p1, q1 = p1, q1, p0+a*p1, q2
n, d = d, n-a*d
k = (max_denominator-q0)//q1
# Determine which of the candidates (p0+k*p1)/(q0+k*q1) and p1/q1 is
# closer to self. The distance between them is 1/(q1*(q0+k*q1)), while
# the distance from p1/q1 to self is d/(q1*self._denominator). So we
# need to compare 2*(q0+k*q1) with self._denominator/d.
if 2*d*(q0+k*q1) <= self._denominator:
return Fraction._from_coprime_ints(p1, q1)
else:
return Fraction._from_coprime_ints(p0+k*p1, q0+k*q1)
@property
def numerator(a):
return a._numerator
@property
def denominator(a):
return a._denominator
def __repr__(self):
"""repr(self)"""
return '%s(%s, %s)' % (self.__class__.__name__,
self._numerator, self._denominator)
def __str__(self):
"""str(self)"""
if self._denominator == 1:
return str(self._numerator)
else:
return '%s/%s' % (self._numerator, self._denominator)
def __format__(self, format_spec, /):
"""Format this fraction according to the given format specification."""
# Backwards compatiblility with existing formatting.
if not format_spec:
return str(self)
# Validate and parse the format specifier.
match = _FLOAT_FORMAT_SPECIFICATION_MATCHER(format_spec)
if match is None:
raise ValueError(
f"Invalid format specifier {format_spec!r} "
f"for object of type {type(self).__name__!r}"
)
elif match["align"] is not None and match["zeropad"] is not None:
# Avoid the temptation to guess.
raise ValueError(
f"Invalid format specifier {format_spec!r} "
f"for object of type {type(self).__name__!r}; "
"can't use explicit alignment when zero-padding"
)
fill = match["fill"] or " "
align = match["align"] or ">"
pos_sign = "" if match["sign"] == "-" else match["sign"]
no_neg_zero = bool(match["no_neg_zero"])
alternate_form = bool(match["alt"])
zeropad = bool(match["zeropad"])
minimumwidth = int(match["minimumwidth"] or "0")
thousands_sep = match["thousands_sep"]
precision = int(match["precision"] or "6")
presentation_type = match["presentation_type"]
trim_zeros = presentation_type in "gG" and not alternate_form
trim_point = not alternate_form
exponent_indicator = "E" if presentation_type in "EFG" else "e"
# Round to get the digits we need, figure out where to place the point,
# and decide whether to use scientific notation. 'point_pos' is the
# relative to the _end_ of the digit string: that is, it's the number
# of digits that should follow the point.
if presentation_type in "fF%":
exponent = -precision
if presentation_type == "%":
exponent -= 2
negative, significand = _round_to_exponent(
self._numerator, self._denominator, exponent, no_neg_zero)
scientific = False
point_pos = precision
else: # presentation_type in "eEgG"
figures = (
max(precision, 1)
if presentation_type in "gG"
else precision + 1
)
negative, significand, exponent = _round_to_figures(
self._numerator, self._denominator, figures)
scientific = (
presentation_type in "eE"
or exponent > 0
or exponent + figures <= -4
)
point_pos = figures - 1 if scientific else -exponent
# Get the suffix - the part following the digits, if any.
if presentation_type == "%":
suffix = "%"
elif scientific:
suffix = f"{exponent_indicator}{exponent + point_pos:+03d}"
else:
suffix = ""
# String of output digits, padded sufficiently with zeros on the left
# so that we'll have at least one digit before the decimal point.
digits = f"{significand:0{point_pos + 1}d}"
# Before padding, the output has the form f"{sign}{leading}{trailing}",
# where `leading` includes thousands separators if necessary and
# `trailing` includes the decimal separator where appropriate.
sign = "-" if negative else pos_sign
leading = digits[: len(digits) - point_pos]
frac_part = digits[len(digits) - point_pos :]
if trim_zeros:
frac_part = frac_part.rstrip("0")
separator = "" if trim_point and not frac_part else "."
trailing = separator + frac_part + suffix
# Do zero padding if required.
if zeropad:
min_leading = minimumwidth - len(sign) - len(trailing)
# When adding thousands separators, they'll be added to the
# zero-padded portion too, so we need to compensate.
leading = leading.zfill(
3 * min_leading // 4 + 1 if thousands_sep else min_leading
)
# Insert thousands separators if required.
if thousands_sep:
first_pos = 1 + (len(leading) - 1) % 3
leading = leading[:first_pos] + "".join(
thousands_sep + leading[pos : pos + 3]
for pos in range(first_pos, len(leading), 3)
)
# We now have a sign and a body. Pad with fill character if necessary
# and return.
body = leading + trailing
padding = fill * (minimumwidth - len(sign) - len(body))
if align == ">":
return padding + sign + body
elif align == "<":
return sign + body + padding
elif align == "^":
half = len(padding) // 2
return padding[:half] + sign + body + padding[half:]
else: # align == "="
return sign + padding + body
def _operator_fallbacks(monomorphic_operator, fallback_operator):
"""Generates forward and reverse operators given a purely-rational
operator and a function from the operator module.
Use this like:
__op__, __rop__ = _operator_fallbacks(just_rational_op, operator.op)
In general, we want to implement the arithmetic operations so
that mixed-mode operations either call an implementation whose
author knew about the types of both arguments, or convert both
to the nearest built in type and do the operation there. In
Fraction, that means that we define __add__ and __radd__ as:
def __add__(self, other):
# Both types have numerators/denominator attributes,
# so do the operation directly
if isinstance(other, (int, Fraction)):
return Fraction(self.numerator * other.denominator +
other.numerator * self.denominator,
self.denominator * other.denominator)
# float and complex don't have those operations, but we
# know about those types, so special case them.
elif isinstance(other, float):
return float(self) + other
elif isinstance(other, complex):
return complex(self) + other
# Let the other type take over.
return NotImplemented
def __radd__(self, other):
# radd handles more types than add because there's
# nothing left to fall back to.
if isinstance(other, numbers.Rational):
return Fraction(self.numerator * other.denominator +
other.numerator * self.denominator,
self.denominator * other.denominator)
elif isinstance(other, Real):
return float(other) + float(self)
elif isinstance(other, Complex):
return complex(other) + complex(self)
return NotImplemented
There are 5 different cases for a mixed-type addition on
Fraction. I'll refer to all of the above code that doesn't
refer to Fraction, float, or complex as "boilerplate". 'r'
will be an instance of Fraction, which is a subtype of
Rational (r : Fraction <: Rational), and b : B <:
Complex. The first three involve 'r + b':
1. If B <: Fraction, int, float, or complex, we handle
that specially, and all is well.
2. If Fraction falls back to the boilerplate code, and it
were to return a value from __add__, we'd miss the
possibility that B defines a more intelligent __radd__,
so the boilerplate should return NotImplemented from
__add__. In particular, we don't handle Rational
here, even though we could get an exact answer, in case
the other type wants to do something special.
3. If B <: Fraction, Python tries B.__radd__ before
Fraction.__add__. This is ok, because it was
implemented with knowledge of Fraction, so it can
handle those instances before delegating to Real or
Complex.
The next two situations describe 'b + r'. We assume that b
didn't know about Fraction in its implementation, and that it
uses similar boilerplate code:
4. If B <: Rational, then __radd_ converts both to the
builtin rational type (hey look, that's us) and
proceeds.
5. Otherwise, __radd__ tries to find the nearest common
base ABC, and fall back to its builtin type. Since this
class doesn't subclass a concrete type, there's no
implementation to fall back to, so we need to try as
hard as possible to return an actual value, or the user
will get a TypeError.
"""
def forward(a, b):
if isinstance(b, Fraction):
return monomorphic_operator(a, b)
elif isinstance(b, int):
return monomorphic_operator(a, Fraction(b))
elif isinstance(b, float):
return fallback_operator(float(a), b)
elif isinstance(b, complex):
return fallback_operator(complex(a), b)
else:
return NotImplemented
forward.__name__ = '__' + fallback_operator.__name__ + '__'
forward.__doc__ = monomorphic_operator.__doc__
def reverse(b, a):
if isinstance(a, numbers.Rational):
# Includes ints.
return monomorphic_operator(Fraction(a), b)
elif isinstance(a, numbers.Real):
return fallback_operator(float(a), float(b))
elif isinstance(a, numbers.Complex):
return fallback_operator(complex(a), complex(b))
else:
return NotImplemented
reverse.__name__ = '__r' + fallback_operator.__name__ + '__'
reverse.__doc__ = monomorphic_operator.__doc__
return forward, reverse
# Rational arithmetic algorithms: Knuth, TAOCP, Volume 2, 4.5.1.
#
# Assume input fractions a and b are normalized.
#
# 1) Consider addition/subtraction.
#
# Let g = gcd(da, db). Then
#
# na nb na*db ± nb*da
# a ± b == -- ± -- == ------------- ==
# da db da*db
#
# na*(db//g) ± nb*(da//g) t
# == ----------------------- == -
# (da*db)//g d
#
# Now, if g > 1, we're working with smaller integers.
#
# Note, that t, (da//g) and (db//g) are pairwise coprime.
#
# Indeed, (da//g) and (db//g) share no common factors (they were
# removed) and da is coprime with na (since input fractions are
# normalized), hence (da//g) and na are coprime. By symmetry,
# (db//g) and nb are coprime too. Then,
#
# gcd(t, da//g) == gcd(na*(db//g), da//g) == 1
# gcd(t, db//g) == gcd(nb*(da//g), db//g) == 1
#
# Above allows us optimize reduction of the result to lowest
# terms. Indeed,
#
# g2 = gcd(t, d) == gcd(t, (da//g)*(db//g)*g) == gcd(t, g)
#
# t//g2 t//g2
# a ± b == ----------------------- == ----------------
# (da//g)*(db//g)*(g//g2) (da//g)*(db//g2)
#
# is a normalized fraction. This is useful because the unnormalized
# denominator d could be much larger than g.
#
# We should special-case g == 1 (and g2 == 1), since 60.8% of
# randomly-chosen integers are coprime:
# https://en.wikipedia.org/wiki/Coprime_integers#Probability_of_coprimality
# Note, that g2 == 1 always for fractions, obtained from floats: here
# g is a power of 2 and the unnormalized numerator t is an odd integer.
#
# 2) Consider multiplication
#
# Let g1 = gcd(na, db) and g2 = gcd(nb, da), then
#
# na*nb na*nb (na//g1)*(nb//g2)
# a*b == ----- == ----- == -----------------
# da*db db*da (db//g1)*(da//g2)
#
# Note, that after divisions we're multiplying smaller integers.
#
# Also, the resulting fraction is normalized, because each of
# two factors in the numerator is coprime to each of the two factors
# in the denominator.
#
# Indeed, pick (na//g1). It's coprime with (da//g2), because input
# fractions are normalized. It's also coprime with (db//g1), because
# common factors are removed by g1 == gcd(na, db).
#
# As for addition/subtraction, we should special-case g1 == 1
# and g2 == 1 for same reason. That happens also for multiplying
# rationals, obtained from floats.
def _add(a, b):
"""a + b"""
na, da = a._numerator, a._denominator
nb, db = b._numerator, b._denominator
g = math.gcd(da, db)
if g == 1:
return Fraction._from_coprime_ints(na * db + da * nb, da * db)
s = da // g
t = na * (db // g) + nb * s
g2 = math.gcd(t, g)
if g2 == 1:
return Fraction._from_coprime_ints(t, s * db)
return Fraction._from_coprime_ints(t // g2, s * (db // g2))
__add__, __radd__ = _operator_fallbacks(_add, operator.add)
def _sub(a, b):
"""a - b"""
na, da = a._numerator, a._denominator
nb, db = b._numerator, b._denominator
g = math.gcd(da, db)
if g == 1:
return Fraction._from_coprime_ints(na * db - da * nb, da * db)
s = da // g
t = na * (db // g) - nb * s
g2 = math.gcd(t, g)
if g2 == 1:
return Fraction._from_coprime_ints(t, s * db)
return Fraction._from_coprime_ints(t // g2, s * (db // g2))
__sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub)
def _mul(a, b):
"""a * b"""
na, da = a._numerator, a._denominator
nb, db = b._numerator, b._denominator
g1 = math.gcd(na, db)
if g1 > 1:
na //= g1
db //= g1
g2 = math.gcd(nb, da)
if g2 > 1:
nb //= g2
da //= g2
return Fraction._from_coprime_ints(na * nb, db * da)
__mul__, __rmul__ = _operator_fallbacks(_mul, operator.mul)
def _div(a, b):
"""a / b"""
# Same as _mul(), with inversed b.
nb, db = b._numerator, b._denominator
if nb == 0:
raise ZeroDivisionError('Fraction(%s, 0)' % db)
na, da = a._numerator, a._denominator
g1 = math.gcd(na, nb)
if g1 > 1:
na //= g1
nb //= g1
g2 = math.gcd(db, da)
if g2 > 1:
da //= g2
db //= g2
n, d = na * db, nb * da
if d < 0:
n, d = -n, -d
return Fraction._from_coprime_ints(n, d)
__truediv__, __rtruediv__ = _operator_fallbacks(_div, operator.truediv)
def _floordiv(a, b):
"""a // b"""
return (a.numerator * b.denominator) // (a.denominator * b.numerator)
__floordiv__, __rfloordiv__ = _operator_fallbacks(_floordiv, operator.floordiv)
def _divmod(a, b):
"""(a // b, a % b)"""
da, db = a.denominator, b.denominator
div, n_mod = divmod(a.numerator * db, da * b.numerator)
return div, Fraction(n_mod, da * db)
__divmod__, __rdivmod__ = _operator_fallbacks(_divmod, divmod)
def _mod(a, b):
"""a % b"""
da, db = a.denominator, b.denominator
return Fraction((a.numerator * db) % (b.numerator * da), da * db)
__mod__, __rmod__ = _operator_fallbacks(_mod, operator.mod)
def __pow__(a, b):
"""a ** b
If b is not an integer, the result will be a float or complex
since roots are generally irrational. If b is an integer, the
result will be rational.
"""
if isinstance(b, numbers.Rational):
if b.denominator == 1:
power = b.numerator
if power >= 0:
return Fraction._from_coprime_ints(a._numerator ** power,
a._denominator ** power)
elif a._numerator > 0:
return Fraction._from_coprime_ints(a._denominator ** -power,
a._numerator ** -power)
elif a._numerator == 0:
raise ZeroDivisionError('Fraction(%s, 0)' %
a._denominator ** -power)
else:
return Fraction._from_coprime_ints((-a._denominator) ** -power,
(-a._numerator) ** -power)
else:
# A fractional power will generally produce an
# irrational number.
return float(a) ** float(b)
elif isinstance(b, (float, complex)):
return float(a) ** b
else:
return NotImplemented
def __rpow__(b, a):
"""a ** b"""
if b._denominator == 1 and b._numerator >= 0:
# If a is an int, keep it that way if possible.
return a ** b._numerator
if isinstance(a, numbers.Rational):
return Fraction(a.numerator, a.denominator) ** b
if b._denominator == 1:
return a ** b._numerator
return a ** float(b)
def __pos__(a):
"""+a: Coerces a subclass instance to Fraction"""
return Fraction._from_coprime_ints(a._numerator, a._denominator)
def __neg__(a):
"""-a"""
return Fraction._from_coprime_ints(-a._numerator, a._denominator)
def __abs__(a):
"""abs(a)"""
return Fraction._from_coprime_ints(abs(a._numerator), a._denominator)
def __int__(a, _index=operator.index):
"""int(a)"""
if a._numerator < 0:
return _index(-(-a._numerator // a._denominator))
else:
return _index(a._numerator // a._denominator)
def __trunc__(a):
"""math.trunc(a)"""
if a._numerator < 0:
return -(-a._numerator // a._denominator)
else:
return a._numerator // a._denominator
def __floor__(a):
"""math.floor(a)"""
return a._numerator // a._denominator
def __ceil__(a):
"""math.ceil(a)"""
# The negations cleverly convince floordiv to return the ceiling.
return -(-a._numerator // a._denominator)
def __round__(self, ndigits=None):
"""round(self, ndigits)
Rounds half toward even.
"""
if ndigits is None:
d = self._denominator
floor, remainder = divmod(self._numerator, d)
if remainder * 2 < d:
return floor
elif remainder * 2 > d:
return floor + 1
# Deal with the half case:
elif floor % 2 == 0:
return floor
else:
return floor + 1
shift = 10**abs(ndigits)
# See _operator_fallbacks.forward to check that the results of
# these operations will always be Fraction and therefore have
# round().
if ndigits > 0:
return Fraction(round(self * shift), shift)
else:
return Fraction(round(self / shift) * shift)
def __hash__(self):
"""hash(self)"""
return _hash_algorithm(self._numerator, self._denominator)
def __eq__(a, b):
"""a == b"""
if type(b) is int:
return a._numerator == b and a._denominator == 1
if isinstance(b, numbers.Rational):
return (a._numerator == b.numerator and
a._denominator == b.denominator)
if isinstance(b, numbers.Complex) and b.imag == 0:
b = b.real
if isinstance(b, float):
if math.isnan(b) or math.isinf(b):
# comparisons with an infinity or nan should behave in
# the same way for any finite a, so treat a as zero.
return 0.0 == b
else:
return a == a.from_float(b)
else:
# Since a doesn't know how to compare with b, let's give b
# a chance to compare itself with a.
return NotImplemented
def _richcmp(self, other, op):
"""Helper for comparison operators, for internal use only.
Implement comparison between a Rational instance `self`, and
either another Rational instance or a float `other`. If
`other` is not a Rational instance or a float, return
NotImplemented. `op` should be one of the six standard
comparison operators.
"""
# convert other to a Rational instance where reasonable.
if isinstance(other, numbers.Rational):
return op(self._numerator * other.denominator,
self._denominator * other.numerator)
if isinstance(other, float):
if math.isnan(other) or math.isinf(other):
return op(0.0, other)
else:
return op(self, self.from_float(other))
else:
return NotImplemented
def __lt__(a, b):
"""a < b"""
return a._richcmp(b, operator.lt)
def __gt__(a, b):
"""a > b"""
return a._richcmp(b, operator.gt)
def __le__(a, b):
"""a <= b"""
return a._richcmp(b, operator.le)
def __ge__(a, b):
"""a >= b"""
return a._richcmp(b, operator.ge)
def __bool__(a):
"""a != 0"""
# bpo-39274: Use bool() because (a._numerator != 0) can return an
# object which is not a bool.
return bool(a._numerator)
# support for pickling, copy, and deepcopy
def __reduce__(self):
return (self.__class__, (self._numerator, self._denominator))
def __copy__(self):
if type(self) == Fraction:
return self # I'm immutable; therefore I am my own clone
return self.__class__(self._numerator, self._denominator)
def __deepcopy__(self, memo):
if type(self) == Fraction:
return self # My components are also immutable
return self.__class__(self._numerator, self._denominator)
Every class
examined so far, is immutable, which means once instantiated it cannot be modified.
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
f1 | fractions.Fraction | 1 | 5/3 |
alias | object | 1 | <object at 0x29cfafde710> |
instance1 | object | 1 | <object at 0x29cfafde710> |
instance2 | object | 1 | <object at 0x29cfafde580> |
instance3 | object | 1 | <object at 0x29cfafde9a0> |
text | str | 11 | hello world |
As a consequence, each immutable instance has a hash
value which can be used to verify the object
:
In [56]: hash(instance1)
Out[56]: 179578068593
In [57]: hash(alias)
Out[57]: 179578068593
In [58]: hash(f1)
Out[58]: 768614336404564652
In [59]: hash(text)
Out[59]: 6661848511987573470
In [60]: hash(1)
Out[60]: 1
In [61]: hash(3.14)
Out[61]: 322818021289917443
With exception to __new__
and __init__
which recall are used to construct the instance self
and provide it with instance data during instantiation, each instance method in an immutable class returns a new instance (of the same class or of another class).
Immutable instances can be used as keys
in a dict
. The most common keys used are str
instances:
In [62]: mapping = {'r': 'red', 'g': 'green', 'b': 'blue'}
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
f1 | fractions.Fraction | 1 | 5/3 |
alias | object | 1 | <object at 0x29cfafde710> |
instance1 | object | 1 | <object at 0x29cfafde710> |
instance2 | object | 1 | <object at 0x29cfafde580> |
instance3 | object | 1 | <object at 0x29cfafde9a0> |
mapping | dict | 3 | {'r': 'red', 'g': 'green', 'b': 'blue'} |
text | str | 11 | hello world |
The dict
is a MutableMapping
and be examined by double clicking its entry in the Variable Explorer:
mapping - dict (3 elements) | ||||
---|---|---|---|---|
▲ | Key | Type | Size | Value |
0 | r | str | 3 | red |
1 | g | str | 5 | green |
2 | b | str | 4 | blue |
In a dict
, the insertion order is maintained indexing is not carried out using the numeric index. The key
is used to retrieve a value
:
In [63]: mapping['r']
Out[63]: 'red'
Conceptualise a physical key, in order for the key to fit a lock and retrieve a value the key cannot be modified (mutated), threfore the key must be immutable. The dict
itself is mutable. Notice the following method has no return value, instead:
In [64]: mapping.clear()
The dict
instane mapping
is modified in place:
mapping - dict (0 elements) | ||||
---|---|---|---|---|
▲ | Key | Type | Size | Value |
No elements in the dictionary |
Note because it is mutable, the dict
has no hash
value:
In [65]: hash(mapping)
Traceback (most recent call last):
Cell In[54], line 1
hash(mapping)
TypeError: unhashable type: 'dict'
All object
instances, regardless whether they are mutable or immutable have an object
identifiication:
In [66]: id(text)
Out[66]: 2523097836336
In [67]: id(mapping)
Out[67]: 2523099048832
In [68]: mapping['r'] = 'red'
In [69]: id(mapping)
Out[69]: 2523099048832
Notice the mutable method, updates mapping
in place and the object
identification is constant:
mapping - dict (1 element) | ||||
---|---|---|---|---|
▲ | Key | Type | Size | Value |
0 | r | str | 3 | red |
Begineers often confusion reassignment with a mutable method. The difference can be seen when the identification is used before reassignment:
In [70]: mapping = {'r': 'red', 'g': 'green', 'b': 'blue'}
In [71]: id(mapping)
Out[71]: 2523099148224
Recall the object
name should be conceptualised as a label. Essentially the label mapping
has been peeled off the object
with identification 2523099048832
and been placed on the object
with identification 2523099148224
. The old object
with identification 2523099048832
was not modified but it now has no reference and is orphaned. It is cleaned up by Python's garbage collection:
mapping - dict (3 elements) | ||||
---|---|---|---|---|
▲ | Key | Type | Size | Value |
0 | r | str | 3 | red |
1 | g | str | 5 | green |
2 | b | str | 4 | blue |
In [72]: mapping2 = {'r': 'red', 'g': 'green', 'b': 'blue'}
In [73]: id(mapping2)
Out[73]: 2523099185600
The two mutable object
instances are equal to one another but are not the same object
:
In [74]: mapping == mapping2
Out[74]: True
In [75]: mapping is mapping2
Out[75]: False
An attribute is normally accessed using the .
syntax. For example:
In [76]: instance1.__doc__
Out[76]: 'The base class of the class hierarchy.\n\nWhen called, it accepts no arguments and returns a new featureless\ninstance that has no instance attributes and cannot be given any.\n'
The data model identifier __getattr__
defines how an object
accesses an attribute. It controls the behaviour of .
syntax but also the behaviour of the getattr
function. The getattr
function takes in the object
as its first positional input argument and the attribute as a string as its second positional input argument:
In [76]: getattr(instance1, '__doc__')
Out[76]: 'The base class of the class hierarchy.\n\nWhen called, it accepts no arguments and returns a new featureless\ninstance that has no instance attributes and cannot be given any.\n'
This is commonly used with the dir
function which recall retrieves a list of the strings of identifiers:
In [77]: dir(instance1)
Out[77]: [
'__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'
]
In [78]: exit
The __setattr__
and __delattr__
are not defined for an immutable object
instances as these methods modify an object
instance in place. To examine these, a class with an attribute can be created such as a Coordinate
class:
# coordinates.py
class Coordinate(object):
def __init__(self, x, y, label):
self.x = x
self.y = y
self.label = label
def __repr__(self):
return f'type(self).__name__(x={self.x}, y={self.y})'
def __str__(self):
return f'(x={self.x}, y={self.y})'
__hash__ = None
details = '2D'
This class be imported an the docstring will popup prompting for the initialisation data:
In [1]: from coordinates import Coordinate
In [2]: Coordinate(
# Docstring popup
"""
Coordinate(x, y)
No documentation available.
"""
The Coordinate
instance with x
at 2
and y
at 2
can be instantiated:
In [2]: c1 = Coordinate(2, 2, 'c1')
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
c1 | coordinates.Coordinate | 1 | (2, 2) |
One of the attributes for example x
can be get, set and deleted using the dot .
syntax:
In [3]: c1.x
Out[3]: 2
In [4]: c1.x = 3
In [5]: c1.x
Out[5]: 3
In [6]: del c1.x
In [7]: c1.x
Traceback (most recent call last):
Cell In[7], line 1
c1.x
AttributeError: 'Coordinate' object has no attribute 'x'
The other attribute y
can be get, set and deleted using the builtins
functions:
In [8]: hasattr(c1, 'y')
Out[8]: True
In [9]: getattr(c1, 'y')
Out[9]: 2
In [10]: setattr(c1, 'y', 3)
In [11]: getattr(c1, 'y')
Out[11]: 3
In [12]: delattr(c1, 'y')
In [13]: hasattr(c1, 'y')
Out[13]: False
The data model methods __getattribute__
, __setattr__
, __delattr__
are inherited from the base object
class. The behaviour can be modified:
# coordinates.py
class Coordinate(object):
def __init__(self, x, y, label):
self.x = x
self.y = y
self.label = label
def __repr__(self):
return f'type(self).__name__(x={self.x}, y={self.y})'
def __str__(self):
return f'(x={self.x}, y={self.y})'
def __delattr__(self, name):
"""
Sets attribute to 0 instead fo removing it
"""
if hasattr(self, name):
object.__setattr__(self, name, 0)
else:
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
__hash__ = None
details = '2D'
The Coordinate2
instance with x
at 2
and y
at 2
can be instantiated:
In [12]: exit
In [1]: from coordinates import Coordinate
In [2]: c1 = Coordinate(2, 2, 'c1')
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
c1 | coordinates.Coordinate2 | 1 | (2, 2) |
One of the attributes for example x
can be get, set and deleted using the dot .
syntax:
In [3]: c1.x
Out[3]: 2
In [4]: c1.x = 3
In [5]: c1.x
Out[5]: 3
In [6]: del c1.x
In [7]: c1.x
Out[8]: 0
Altering the the data model methods __getattribute__
, __setattr__
, __delattr__
will change the way that all attributes are get, set and deleted in a class. In the example above, the class attribute label
which is a str
will behave in a similar way:
In [9]: c1.label
Out[9] 'c1'
In [10]: del c1.label
In [11]: c1.label
Out[11]: 0
Notice this changes label
from a str
to an int
which may be unexpected and lead to a TypeError
.
In [11]: 'Coordinate_' + c1.label
'Coordinate_' +c1.label
Traceback (most recent call last):
Cell In[11], line 1
'Coordinate_' + c1.label
TypeError: can only concatenate str (not "int") to str
Instead of modifying the data model methods __getattribute__
, __setattr__
, __delattr__
. A property can be set for each attribute. Let's look at the attribute x
for example:
# coordinates.py
class Coordinate(object):
def __init__(self, x, y, label):
self._x = x # Internal attribute, denoted by leading underscore
self.y = y
self.label = label
...
# Property for x
@property
def x(self):
pass
In the __init__
, the external x
supplied by the user is assigned to an internal _x
denoted by a leading underscore.
The @x.getter
is a getter for the external attribute x
:
@x.getter
def x(self):
And outlines how external x
is retrieved by internal _x
.
@x.getter
def x(self):
return self._x
Another decorator can be used to set internal _x
to a value:
@x.setter
def x(self, value):
self._x = value
Note the setter, sets internal _x
to a value and the getter instructs how to retrieve the external x
from internal _x
.
It is common to include an assert
statement to ensure the value supplied is of a specific class type or grouping of classes for example numeric classes. Further assert statements may be made to assert that a numeric value is in specific numeric range or a text value is a specific length. In this example an assert statement will be made to ensure x
is an instance of the int
or float
classes:
@x.setter
def x(self, value):
assert isinstance(value, (int, float))
self._x = value
Another decorator can be used to delete internal _x
, in this case zeroing internal _x
. Note the deleter, sets internal _x
to 0
and the getter instructs how to retrieve the external x
from internal _x
.
@x.deleter
def x(self):
self._x = 0
Bringing this together:
# coordinates.py
class Coordinate(object):
def __init__(self, x, y, label):
self._x = x # Internal attribute, denoted by leading underscore
self.y = y
self.label = label
...
# Property for x
@property
def x(self):
pass
@x.getter
def x(self):
return self._x
@x.setter
def x(self, value):
assert isinstance(value, (int, float))
self._x = value
@x.deleter
def x(self):
self._x = 0
The empty method under @property
is normally used in place of @x.getter
:
# coordinates.py
class Coordinate(object):
def __init__(self, x, y):
self._x = x # Internal attribute, denoted by leading underscore
self.y = y
...
# Property for x
@property
def x(self):
return self._x
@x.setter
def x(self, value):
assert isinstance(value, (int, float))
self._x = value
@x.deleter
def x(self):
self._x = 0
Returning to the full class:
# coordinates.py
class Coordinate(object):
def __init__(self, x, y, label):
self._x = x
self.y = y
self.label = label
def __repr__(self):
return f'type(self).__name__(x={self.x}, y={self.y})'
def __str__(self):
return f'(x={self.x}, y={self.y})'
# Property for x
@property
def x(self):
return self._x
@x.setter
def x(self, value):
assert isinstance(value, (int, float))
self._x = value
@x.deleter
def x(self):
self._x = 0
__hash__ = None
details = '2D'
This can be tested with:
In [12]: exit
In [1]: from coordinates import Coordinate
In [2]: c1 = Coordinate(2, 2, 'c1')
In [3]: c1.x
Out[3]: 2
In [4]: c1.x = 3
In [5]: c1.x
Out[5]: 3
In [6]: del c1.x
In [7]: c1.x
Out[7]: 0
Notice the attribute label
have the default behaviour:
In [8]: c1.label
Out[8]: 'c1'
In [9]: del c1.label
In [10]: c1.label
Traceback (most recent call last):
Cell In[10], line 1
c1.label
AttributeError: 'Coordinate' object has no attribute 'label'
A property can be set for each attribute. Notice that the initialisation signature __init__
is kept relatively simple and can be used to quickly evaluate what initialisation data is required for initialisation. The assert statements are handled in each attributes property:
# coordinates.py
class Coordinate(object):
def __init__(self, x, y, label):
self._x = x
self._y = y
self._label = label
def __repr__(self):
return f'type(self).__name__(x={self.x}, y={self.y})'
def __str__(self):
return f'(x={self.x}, y={self.y})'
# Property for x
@property
def x(self):
return self._x
@x.setter
def x(self, value):
assert isinstance(value, (int, float))
self._x = value
@x.deleter
def x(self):
self._x = 0
# Property for y
@property
def y(self):
return self._y
@y.setter
def y(self, value):
assert isinstance(value, (int, float))
self._y = value
@y.deleter
def y(self):
self._y = 0
# Property for label
@property
def label(self):
return self._label
@label.setter
def label(self, value):
assert isinstance(value, str)
self._label = value
@label.deleter
def label(self):
self._label = ''
__hash__ = None
details = '2D'
This class works as expected:
In [10]: exit
In [1]: from coordinates import Coordinate
In [2]: c1 = Coordinate(2, 2, 'c1')
In [3]: c1.x
Out[3]: 2
In [4]: c1.y
Out[4]: 2
In [5]: c1.label
Out[5]: 'c1'
In [6]: c1.x = 3
In [7]: c1.x
Out[7]: 3
In [8]: del c1.x
In [9]: c1.x
Out[9]: 0
In [10]: c1.label = 'coordinate1'
In [11]: c1.label
Out[11]: 'coordinate1'
In [12]: del c1.label
In [13]: c1.label
Out[13]: ''
An abstract base class requires the base class abc.ABC
:
# coordinates.py
import abc
class AbstractCoordinate(abc.ABC):
pass
An abstract base class outlines a design pattern for a class and therefore should contain all the attributes and methods that are within the design pattern. For example, the design pattern for an AbstractCoordinate
class can be created:
# coordinates.py
import abc
class AbstractCoordinate(abc.ABC):
# __init__ inherited from abc.ABC
def __repr__(self):
pass
def __str__(self):
pass
@property
@abc.abstractmethod
def x(self):
pass
@property
@abc.abstractmethod
def y(self):
pass
@property
@abc.abstractmethod
def label(self):
pass
details = None
__subclasshook__
is a class method that is used within an abstract base class to define whether a class is a child class of the abstract base class. It has the form:
# coordinates.py
import abc
class AbstractCoordinate(abc.ABC):
@classmethod
def __subclasshook__(cls, subclass):
if condition:
return True
else:
return NotImplemented
Which is often simplified down to:
# coordinates.py
import abc
class AbstractCoordinate(abc.ABC):
@classmethod
def __subclasshook__(cls, subclass):
if condition:
return True
return NotImplemented
Examining the condition:
# coordinates.py
import abc
class AbstractCoordinate(abc.ABC):
@classmethod
def __subclasshook__(cls, subclass):
condition = all([hasattr(subclass, 'x'),
hasattr(subclass, 'y'),
hasattr(subclass, 'label'),
hasattr(subclass, 'details'),
hasattr(subclass, '__repr__'),
callable(subclass.__repr__),
hasattr(subclass, '__str__'),
callable(subclass.__str__)])
if condition:
return True
return NotImplemented
Updating coordinates.py
to:
# coordinates.py
import abc
class AbstractCoordinate(abc.ABC):
# __init__ inherited from abc.ABC
@classmethod
def __subclasshook__(cls, subclass):
condition = all([hasattr(subclass, 'x'),
hasattr(subclass, 'y'),
hasattr(subclass, 'label'),
hasattr(subclass, 'details'),
hasattr(subclass, '__repr__'),
callable(subclass.__repr__),
hasattr(subclass, '__str__'),
callable(subclass.__str__)])
if condition:
return True
return NotImplemented
def __repr__(self):
pass
def __str__(self):
pass
@property
@abc.abstractmethod
def x(self):
pass
@property
@abc.abstractmethod
def y(self):
pass
@property
@abc.abstractmethod
def label(self):
pass
details = None
class Coordinate(object):
def __init__(self, x, y, label):
self._x = x
self._y = y
self._label = label
def __repr__(self):
return f'type(self).__name__(x={self.x}, y={self.y})'
def __str__(self):
return f'(x={self.x}, y={self.y})'
# Property for x
@property
def x(self):
return self._x
@x.setter
def x(self, value):
assert isinstance(value, (int, float))
self._x = value
@x.deleter
def x(self):
self._x = 0
# Property for y
@property
def y(self):
return self._y
@y.setter
def y(self, value):
assert isinstance(value, (int, float))
self._y = value
@y.deleter
def y(self):
self._y = 0
# Property for label
@property
def label(self):
return self._label
@label.setter
def label(self, value):
assert isinstance(value, str)
self._label = value
@label.deleter
def label(self):
self._label = ''
__hash__ = None
details = '2D'
This can be tested:
In [14]: exit
In [1]: from coordinates import AbstractCoordinate, Coordinate
In [2]: issubclass(Coordinate, AbstractCoordinate)
Out[2]: True
Notice that AbstractCoordinate
is a base class of Coordinate
because it follows the design pattern outlined, however Coordinate
was only classified with object
as a base
class.
The class method __init_subclass__
can be used to introduce additional behaviour when a class is subclassed. If AbstractCoordinate
is updated to include:
@classmethod
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
print('Abstract Coordinate Subclassed')
For this to work properly, Coordinate
must be updated to use AbstractCoordinate
as a base class during classification:
# coordinates.py
import abc
class AbstractCoordinate(abc.ABC):
# __init__ inherited from abc.ABC
@classmethod
def __subclasshook__(cls, subclass):
condition = all([hasattr(subclass, 'x'),
hasattr(subclass, 'y'),
hasattr(subclass, 'label'),
hasattr(subclass, 'details'),
hasattr(subclass, '__repr__'),
callable(subclass.__repr__),
hasattr(subclass, '__str__'),
callable(subclass.__str__)])
if condition:
return True
return NotImplemented
@classmethod
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
print('Abstract Coordinate Subclassed')
def __repr__(self):
pass
def __str__(self):
pass
@property
@abc.abstractmethod
def x(self):
pass
@property
@abc.abstractmethod
def y(self):
pass
@property
@abc.abstractmethod
def label(self):
pass
details = None
class Coordinate(AbstractCoordinate):
def __init__(self, x, y, label):
self._x = x
self._y = y
self._label = label
def __repr__(self):
return f'type(self).__name__(x={self.x}, y={self.y})'
def __str__(self):
return f'(x={self.x}, y={self.y})'
# Property for x
@property
def x(self):
return self._x
@x.setter
def x(self, value):
assert isinstance(value, (int, float))
self._x = value
@x.deleter
def x(self):
self._x = 0
# Property for y
@property
def y(self):
return self._y
@y.setter
def y(self, value):
assert isinstance(value, (int, float))
self._y = value
@y.deleter
def y(self):
self._y = 0
# Property for label
@property
def label(self):
return self._label
@label.setter
def label(self, value):
assert isinstance(value, str)
self._label = value
@label.deleter
def label(self):
self._label = ''
__hash__ = None
details = '2D'
Now notice when the Coordinate class is imported, the print statement informs that Coordiaate
is a subclass of AbstractCoordinate
:
In [13]: exit
In [1]: from coordinates import Coordinate
Out[1]: Abstract Coordinate Subclassed
A byte is a memory configuration, which can be conceptualised as 8 binary switches. Binary means two combinations (each switch can have the value 0
or 1
) and the prefix 0b
is used to distinguish the base 2 numbering system from the default base 10 numbering system:
Typically the byte is represented using 2 hexadecimal digits (each hexadecimal value can have the value 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, a, b, c, d, e, f
) and the prefix 0x
is used to distinguish the base 16 numbering system from the default base 10 numbering system.
The pickle
module can be imported and the Python object
can be serialised to a bytes
object
.
In [14]: exit
In [1]: instance1 = object()
In [2]: import pickle
In [3]: pickle.dumps(instance1)
In [4]: b = pickle.dumps(instance1)
In [5]: b.hex()
Out[5]: '8004951a000000000000008c086275696c74696e73948c066f626a6563749493942981942e'
Variable Explorer | |||
---|---|---|---|
Name ▲ | Type | Size | Value |
instance1 | object | 1 | <object at 0x18a3e232a90> |
This object
can be retieved from this bytes
object
using:
In [6]: pickle.loads(b)
Out[6]: <object at 0x18a3e232b00>
The three data model methods are helper methods for the pickle
module:
__getstate__
can be used to customise the behaviour of pickling an object
, that is serialisation to a bytes
object
. __get_state__
is typically only overridden to control what gets serialised, allowing exclusion of certain attributes or modification of attributes before serialisation.
__reduce__
allows customisation of pickling and of unpickling.
__reduce_ex__
allows customisation of pickling and of unpickling for each pickle
protocol.
Controlling this behaviour is more of an advanced topic although there are examples of these methods in the standard modules:
functools.py
"""functools.py - Tools for working with functions and callable objects
"""
# Python module wrapper for _functools C module
# to allow utilities written in Python to be added
# to the functools module.
# Written by Nick Coghlan <ncoghlan at gmail.com>,
# Raymond Hettinger <python at rcn.com>,
# and Łukasz Langa <lukasz at langa.pl>.
# Copyright (C) 2006-2013 Python Software Foundation.
# See C source code for _functools credits/copyright
__all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES',
'total_ordering', 'cache', 'cmp_to_key', 'lru_cache', 'reduce',
'partial', 'partialmethod', 'singledispatch', 'singledispatchmethod',
'cached_property']
from abc import get_cache_token
from collections import namedtuple
# import types, weakref # Deferred to single_dispatch()
from reprlib import recursive_repr
from _thread import RLock
from types import GenericAlias
################################################################################
### update_wrapper() and wraps() decorator
################################################################################
# update_wrapper() and wraps() are tools to help write
# wrapper functions that can handle naive introspection
WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
'__annotations__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Update a wrapper function to look like the wrapped function
wrapper is the function to be updated
wrapped is the original function
assigned is a tuple naming the attributes assigned directly
from the wrapped function to the wrapper function (defaults to
functools.WRAPPER_ASSIGNMENTS)
updated is a tuple naming the attributes of the wrapper that
are updated with the corresponding attribute from the wrapped
function (defaults to functools.WRAPPER_UPDATES)
"""
for attr in assigned:
try:
value = getattr(wrapped, attr)
except AttributeError:
pass
else:
setattr(wrapper, attr, value)
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
# Issue #17482: set __wrapped__ last so we don't inadvertently copy it
# from the wrapped function when updating __dict__
wrapper.__wrapped__ = wrapped
# Return the wrapper so this can be used as a decorator via partial()
return wrapper
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Decorator factory to apply update_wrapper() to a wrapper function
Returns a decorator that invokes update_wrapper() with the decorated
function as the wrapper argument and the arguments to wraps() as the
remaining arguments. Default arguments are as for update_wrapper().
This is a convenience function to simplify applying partial() to
update_wrapper().
"""
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
################################################################################
### total_ordering class decorator
################################################################################
# The total ordering functions all invoke the root magic method directly
# rather than using the corresponding operator. This avoids possible
# infinite recursion that could occur when the operator dispatch logic
# detects a NotImplemented result and then calls a reflected method.
def _gt_from_lt(self, other):
'Return a > b. Computed by @total_ordering from (not a < b) and (a != b).'
op_result = type(self).__lt__(self, other)
if op_result is NotImplemented:
return op_result
return not op_result and self != other
def _le_from_lt(self, other):
'Return a <= b. Computed by @total_ordering from (a < b) or (a == b).'
op_result = type(self).__lt__(self, other)
if op_result is NotImplemented:
return op_result
return op_result or self == other
def _ge_from_lt(self, other):
'Return a >= b. Computed by @total_ordering from (not a < b).'
op_result = type(self).__lt__(self, other)
if op_result is NotImplemented:
return op_result
return not op_result
def _ge_from_le(self, other):
'Return a >= b. Computed by @total_ordering from (not a <= b) or (a == b).'
op_result = type(self).__le__(self, other)
if op_result is NotImplemented:
return op_result
return not op_result or self == other
def _lt_from_le(self, other):
'Return a < b. Computed by @total_ordering from (a <= b) and (a != b).'
op_result = type(self).__le__(self, other)
if op_result is NotImplemented:
return op_result
return op_result and self != other
def _gt_from_le(self, other):
'Return a > b. Computed by @total_ordering from (not a <= b).'
op_result = type(self).__le__(self, other)
if op_result is NotImplemented:
return op_result
return not op_result
def _lt_from_gt(self, other):
'Return a < b. Computed by @total_ordering from (not a > b) and (a != b).'
op_result = type(self).__gt__(self, other)
if op_result is NotImplemented:
return op_result
return not op_result and self != other
def _ge_from_gt(self, other):
'Return a >= b. Computed by @total_ordering from (a > b) or (a == b).'
op_result = type(self).__gt__(self, other)
if op_result is NotImplemented:
return op_result
return op_result or self == other
def _le_from_gt(self, other):
'Return a <= b. Computed by @total_ordering from (not a > b).'
op_result = type(self).__gt__(self, other)
if op_result is NotImplemented:
return op_result
return not op_result
def _le_from_ge(self, other):
'Return a <= b. Computed by @total_ordering from (not a >= b) or (a == b).'
op_result = type(self).__ge__(self, other)
if op_result is NotImplemented:
return op_result
return not op_result or self == other
def _gt_from_ge(self, other):
'Return a > b. Computed by @total_ordering from (a >= b) and (a != b).'
op_result = type(self).__ge__(self, other)
if op_result is NotImplemented:
return op_result
return op_result and self != other
def _lt_from_ge(self, other):
'Return a < b. Computed by @total_ordering from (not a >= b).'
op_result = type(self).__ge__(self, other)
if op_result is NotImplemented:
return op_result
return not op_result
_convert = {
'__lt__': [('__gt__', _gt_from_lt),
('__le__', _le_from_lt),
('__ge__', _ge_from_lt)],
'__le__': [('__ge__', _ge_from_le),
('__lt__', _lt_from_le),
('__gt__', _gt_from_le)],
'__gt__': [('__lt__', _lt_from_gt),
('__ge__', _ge_from_gt),
('__le__', _le_from_gt)],
'__ge__': [('__le__', _le_from_ge),
('__gt__', _gt_from_ge),
('__lt__', _lt_from_ge)]
}
def total_ordering(cls):
"""Class decorator that fills in missing ordering methods"""
# Find user-defined comparisons (not those inherited from object).
roots = {op for op in _convert if getattr(cls, op, None) is not getattr(object, op, None)}
if not roots:
raise ValueError('must define at least one ordering operation: < > <= >=')
root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__
for opname, opfunc in _convert[root]:
if opname not in roots:
opfunc.__name__ = opname
setattr(cls, opname, opfunc)
return cls
################################################################################
### cmp_to_key() function converter
################################################################################
def cmp_to_key(mycmp):
"""Convert a cmp= function into a key= function"""
class K(object):
__slots__ = ['obj']
def __init__(self, obj):
self.obj = obj
def __lt__(self, other):
return mycmp(self.obj, other.obj) < 0
def __gt__(self, other):
return mycmp(self.obj, other.obj) > 0
def __eq__(self, other):
return mycmp(self.obj, other.obj) == 0
def __le__(self, other):
return mycmp(self.obj, other.obj) <= 0
def __ge__(self, other):
return mycmp(self.obj, other.obj) >= 0
__hash__ = None
return K
try:
from _functools import cmp_to_key
except ImportError:
pass
################################################################################
### reduce() sequence to a single item
################################################################################
_initial_missing = object()
def reduce(function, sequence, initial=_initial_missing):
"""
reduce(function, iterable[, initial]) -> value
Apply a function of two arguments cumulatively to the items of a sequence
or iterable, from left to right, so as to reduce the iterable to a single
value. For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
((((1+2)+3)+4)+5). If initial is present, it is placed before the items
of the iterable in the calculation, and serves as a default when the
iterable is empty.
"""
it = iter(sequence)
if initial is _initial_missing:
try:
value = next(it)
except StopIteration:
raise TypeError(
"reduce() of empty iterable with no initial value") from None
else:
value = initial
for element in it:
value = function(value, element)
return value
try:
from _functools import reduce
except ImportError:
pass
################################################################################
### partial() argument application
################################################################################
# Purely functional, no descriptor behaviour
class partial:
"""New function with partial application of the given arguments
and keywords.
"""
__slots__ = "func", "args", "keywords", "__dict__", "__weakref__"
def __new__(cls, func, /, *args, **keywords):
if not callable(func):
raise TypeError("the first argument must be callable")
if hasattr(func, "func"):
args = func.args + args
keywords = {**func.keywords, **keywords}
func = func.func
self = super(partial, cls).__new__(cls)
self.func = func
self.args = args
self.keywords = keywords
return self
def __call__(self, /, *args, **keywords):
keywords = {**self.keywords, **keywords}
return self.func(*self.args, *args, **keywords)
@recursive_repr()
def __repr__(self):
qualname = type(self).__qualname__
args = [repr(self.func)]
args.extend(repr(x) for x in self.args)
args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items())
if type(self).__module__ == "functools":
return f"functools.{qualname}({', '.join(args)})"
return f"{qualname}({', '.join(args)})"
def __reduce__(self):
return type(self), (self.func,), (self.func, self.args,
self.keywords or None, self.__dict__ or None)
def __setstate__(self, state):
if not isinstance(state, tuple):
raise TypeError("argument to __setstate__ must be a tuple")
if len(state) != 4:
raise TypeError(f"expected 4 items in state, got {len(state)}")
func, args, kwds, namespace = state
if (not callable(func) or not isinstance(args, tuple) or
(kwds is not None and not isinstance(kwds, dict)) or
(namespace is not None and not isinstance(namespace, dict))):
raise TypeError("invalid partial state")
args = tuple(args) # just in case it's a subclass
if kwds is None:
kwds = {}
elif type(kwds) is not dict: # XXX does it need to be *exactly* dict?
kwds = dict(kwds)
if namespace is None:
namespace = {}
self.__dict__ = namespace
self.func = func
self.args = args
self.keywords = kwds
try:
from _functools import partial
except ImportError:
pass
# Descriptor version
class partialmethod(object):
"""Method descriptor with partial application of the given arguments
and keywords.
Supports wrapping existing descriptors and handles non-descriptor
callables as instance methods.
"""
def __init__(self, func, /, *args, **keywords):
if not callable(func) and not hasattr(func, "__get__"):
raise TypeError("{!r} is not callable or a descriptor"
.format(func))
# func could be a descriptor like classmethod which isn't callable,
# so we can't inherit from partial (it verifies func is callable)
if isinstance(func, partialmethod):
# flattening is mandatory in order to place cls/self before all
# other arguments
# it's also more efficient since only one function will be called
self.func = func.func
self.args = func.args + args
self.keywords = {**func.keywords, **keywords}
else:
self.func = func
self.args = args
self.keywords = keywords
def __repr__(self):
args = ", ".join(map(repr, self.args))
keywords = ", ".join("{}={!r}".format(k, v)
for k, v in self.keywords.items())
format_string = "{module}.{cls}({func}, {args}, {keywords})"
return format_string.format(module=self.__class__.__module__,
cls=self.__class__.__qualname__,
func=self.func,
args=args,
keywords=keywords)
def _make_unbound_method(self):
def _method(cls_or_self, /, *args, **keywords):
keywords = {**self.keywords, **keywords}
return self.func(cls_or_self, *self.args, *args, **keywords)
_method.__isabstractmethod__ = self.__isabstractmethod__
_method._partialmethod = self
return _method
def __get__(self, obj, cls=None):
get = getattr(self.func, "__get__", None)
result = None
if get is not None:
new_func = get(obj, cls)
if new_func is not self.func:
# Assume __get__ returning something new indicates the
# creation of an appropriate callable
result = partial(new_func, *self.args, **self.keywords)
try:
result.__self__ = new_func.__self__
except AttributeError:
pass
if result is None:
# If the underlying descriptor didn't do anything, treat this
# like an instance method
result = self._make_unbound_method().__get__(obj, cls)
return result
@property
def __isabstractmethod__(self):
return getattr(self.func, "__isabstractmethod__", False)
__class_getitem__ = classmethod(GenericAlias)
# Helper functions
def _unwrap_partial(func):
while isinstance(func, partial):
func = func.func
return func
################################################################################
### LRU Cache function decorator
################################################################################
_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"])
class _HashedSeq(list):
""" This class guarantees that hash() will be called no more than once
per element. This is important because the lru_cache() will hash
the key multiple times on a cache miss.
"""
__slots__ = 'hashvalue'
def __init__(self, tup, hash=hash):
self[:] = tup
self.hashvalue = hash(tup)
def __hash__(self):
return self.hashvalue
def _make_key(args, kwds, typed,
kwd_mark = (object(),),
fasttypes = {int, str},
tuple=tuple, type=type, len=len):
"""Make a cache key from optionally typed positional and keyword arguments
The key is constructed in a way that is flat as possible rather than
as a nested structure that would take more memory.
If there is only a single argument and its data type is known to cache
its hash value, then that argument is returned without a wrapper. This
saves space and improves lookup speed.
"""
# All of code below relies on kwds preserving the order input by the user.
# Formerly, we sorted() the kwds before looping. The new way is *much*
# faster; however, it means that f(x=1, y=2) will now be treated as a
# distinct call from f(y=2, x=1) which will be cached separately.
key = args
if kwds:
key += kwd_mark
for item in kwds.items():
key += item
if typed:
key += tuple(type(v) for v in args)
if kwds:
key += tuple(type(v) for v in kwds.values())
elif len(key) == 1 and type(key[0]) in fasttypes:
return key[0]
return _HashedSeq(key)
def lru_cache(maxsize=128, typed=False):
"""Least-recently-used cache decorator.
If *maxsize* is set to None, the LRU features are disabled and the cache
can grow without bound.
If *typed* is True, arguments of different types will be cached separately.
For example, f(3.0) and f(3) will be treated as distinct calls with
distinct results.
Arguments to the cached function must be hashable.
View the cache statistics named tuple (hits, misses, maxsize, currsize)
with f.cache_info(). Clear the cache and statistics with f.cache_clear().
Access the underlying function with f.__wrapped__.
See: https://en.wikipedia.org/wiki/Cache_replacement_policies#Least_recently_used_(LRU)
"""
# Users should only access the lru_cache through its public API:
# cache_info, cache_clear, and f.__wrapped__
# The internals of the lru_cache are encapsulated for thread safety and
# to allow the implementation to change (including a possible C version).
if isinstance(maxsize, int):
# Negative maxsize is treated as 0
if maxsize < 0:
maxsize = 0
elif callable(maxsize) and isinstance(typed, bool):
# The user_function was passed in directly via the maxsize argument
user_function, maxsize = maxsize, 128
wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
return update_wrapper(wrapper, user_function)
elif maxsize is not None:
raise TypeError(
'Expected first argument to be an integer, a callable, or None')
def decorating_function(user_function):
wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
wrapper.cache_parameters = lambda : {'maxsize': maxsize, 'typed': typed}
return update_wrapper(wrapper, user_function)
return decorating_function
def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo):
# Constants shared by all lru cache instances:
sentinel = object() # unique object used to signal cache misses
make_key = _make_key # build a key from the function arguments
PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields
cache = {}
hits = misses = 0
full = False
cache_get = cache.get # bound method to lookup a key or return None
cache_len = cache.__len__ # get cache size without calling len()
lock = RLock() # because linkedlist updates aren't threadsafe
root = [] # root of the circular doubly linked list
root[:] = [root, root, None, None] # initialize by pointing to self
if maxsize == 0:
def wrapper(*args, **kwds):
# No caching -- just a statistics update
nonlocal misses
misses += 1
result = user_function(*args, **kwds)
return result
elif maxsize is None:
def wrapper(*args, **kwds):
# Simple caching without ordering or size limit
nonlocal hits, misses
key = make_key(args, kwds, typed)
result = cache_get(key, sentinel)
if result is not sentinel:
hits += 1
return result
misses += 1
result = user_function(*args, **kwds)
cache[key] = result
return result
else:
def wrapper(*args, **kwds):
# Size limited caching that tracks accesses by recency
nonlocal root, hits, misses, full
key = make_key(args, kwds, typed)
with lock:
link = cache_get(key)
if link is not None:
# Move the link to the front of the circular queue
link_prev, link_next, _key, result = link
link_prev[NEXT] = link_next
link_next[PREV] = link_prev
last = root[PREV]
last[NEXT] = root[PREV] = link
link[PREV] = last
link[NEXT] = root
hits += 1
return result
misses += 1
result = user_function(*args, **kwds)
with lock:
if key in cache:
# Getting here means that this same key was added to the
# cache while the lock was released. Since the link
# update is already done, we need only return the
# computed result and update the count of misses.
pass
elif full:
# Use the old root to store the new key and result.
oldroot = root
oldroot[KEY] = key
oldroot[RESULT] = result
# Empty the oldest link and make it the new root.
# Keep a reference to the old key and old result to
# prevent their ref counts from going to zero during the
# update. That will prevent potentially arbitrary object
# clean-up code (i.e. __del__) from running while we're
# still adjusting the links.
root = oldroot[NEXT]
oldkey = root[KEY]
oldresult = root[RESULT]
root[KEY] = root[RESULT] = None
# Now update the cache dictionary.
del cache[oldkey]
# Save the potentially reentrant cache[key] assignment
# for last, after the root and links have been put in
# a consistent state.
cache[key] = oldroot
else:
# Put result in a new link at the front of the queue.
last = root[PREV]
link = [last, root, key, result]
last[NEXT] = root[PREV] = cache[key] = link
# Use the cache_len bound method instead of the len() function
# which could potentially be wrapped in an lru_cache itself.
full = (cache_len() >= maxsize)
return result
def cache_info():
"""Report cache statistics"""
with lock:
return _CacheInfo(hits, misses, maxsize, cache_len())
def cache_clear():
"""Clear the cache and cache statistics"""
nonlocal hits, misses, full
with lock:
cache.clear()
root[:] = [root, root, None, None]
hits = misses = 0
full = False
wrapper.cache_info = cache_info
wrapper.cache_clear = cache_clear
return wrapper
try:
from _functools import _lru_cache_wrapper
except ImportError:
pass
################################################################################
### cache -- simplified access to the infinity cache
################################################################################
def cache(user_function, /):
'Simple lightweight unbounded cache. Sometimes called "memoize".'
return lru_cache(maxsize=None)(user_function)
################################################################################
### singledispatch() - single-dispatch generic function decorator
################################################################################
def _c3_merge(sequences):
"""Merges MROs in *sequences* to a single MRO using the C3 algorithm.
Adapted from https://www.python.org/download/releases/2.3/mro/.
"""
result = []
while True:
sequences = [s for s in sequences if s] # purge empty sequences
if not sequences:
return result
for s1 in sequences: # find merge candidates among seq heads
candidate = s1[0]
for s2 in sequences:
if candidate in s2[1:]:
candidate = None
break # reject the current head, it appears later
else:
break
if candidate is None:
raise RuntimeError("Inconsistent hierarchy")
result.append(candidate)
# remove the chosen candidate
for seq in sequences:
if seq[0] == candidate:
del seq[0]
def _c3_mro(cls, abcs=None):
"""Computes the method resolution order using extended C3 linearization.
If no *abcs* are given, the algorithm works exactly like the built-in C3
linearization used for method resolution.
If given, *abcs* is a list of abstract base classes that should be inserted
into the resulting MRO. Unrelated ABCs are ignored and don't end up in the
result. The algorithm inserts ABCs where their functionality is introduced,
i.e. issubclass(cls, abc) returns True for the class itself but returns
False for all its direct base classes. Implicit ABCs for a given class
(either registered or inferred from the presence of a special method like
__len__) are inserted directly after the last ABC explicitly listed in the
MRO of said class. If two implicit ABCs end up next to each other in the
resulting MRO, their ordering depends on the order of types in *abcs*.
"""
for i, base in enumerate(reversed(cls.__bases__)):
if hasattr(base, '__abstractmethods__'):
boundary = len(cls.__bases__) - i
break # Bases up to the last explicit ABC are considered first.
else:
boundary = 0
abcs = list(abcs) if abcs else []
explicit_bases = list(cls.__bases__[:boundary])
abstract_bases = []
other_bases = list(cls.__bases__[boundary:])
for base in abcs:
if issubclass(cls, base) and not any(
issubclass(b, base) for b in cls.__bases__
):
# If *cls* is the class that introduces behaviour described by
# an ABC *base*, insert said ABC to its MRO.
abstract_bases.append(base)
for base in abstract_bases:
abcs.remove(base)
explicit_c3_mros = [_c3_mro(base, abcs=abcs) for base in explicit_bases]
abstract_c3_mros = [_c3_mro(base, abcs=abcs) for base in abstract_bases]
other_c3_mros = [_c3_mro(base, abcs=abcs) for base in other_bases]
return _c3_merge(
[[cls]] +
explicit_c3_mros + abstract_c3_mros + other_c3_mros +
[explicit_bases] + [abstract_bases] + [other_bases]
)
def _compose_mro(cls, types):
"""Calculates the method resolution order for a given class *cls*.
Includes relevant abstract base classes (with their respective bases) from
the *types* iterable. Uses a modified C3 linearization algorithm.
"""
bases = set(cls.__mro__)
# Remove entries which are already present in the __mro__ or unrelated.
def is_related(typ):
return (typ not in bases and hasattr(typ, '__mro__')
and not isinstance(typ, GenericAlias)
and issubclass(cls, typ))
types = [n for n in types if is_related(n)]
# Remove entries which are strict bases of other entries (they will end up
# in the MRO anyway.
def is_strict_base(typ):
for other in types:
if typ != other and typ in other.__mro__:
return True
return False
types = [n for n in types if not is_strict_base(n)]
# Subclasses of the ABCs in *types* which are also implemented by
# *cls* can be used to stabilize ABC ordering.
type_set = set(types)
mro = []
for typ in types:
found = []
for sub in typ.__subclasses__():
if sub not in bases and issubclass(cls, sub):
found.append([s for s in sub.__mro__ if s in type_set])
if not found:
mro.append(typ)
continue
# Favor subclasses with the biggest number of useful bases
found.sort(key=len, reverse=True)
for sub in found:
for subcls in sub:
if subcls not in mro:
mro.append(subcls)
return _c3_mro(cls, abcs=mro)
def _find_impl(cls, registry):
"""Returns the best matching implementation from *registry* for type *cls*.
Where there is no registered implementation for a specific type, its method
resolution order is used to find a more generic implementation.
Note: if *registry* does not contain an implementation for the base
*object* type, this function may return None.
"""
mro = _compose_mro(cls, registry.keys())
match = None
for t in mro:
if match is not None:
# If *match* is an implicit ABC but there is another unrelated,
# equally matching implicit ABC, refuse the temptation to guess.
if (t in registry and t not in cls.__mro__
and match not in cls.__mro__
and not issubclass(match, t)):
raise RuntimeError("Ambiguous dispatch: {} or {}".format(
match, t))
break
if t in registry:
match = t
return registry.get(match)
def singledispatch(func):
"""Single-dispatch generic function decorator.
Transforms a function into a generic function, which can have different
behaviours depending upon the type of its first argument. The decorated
function acts as the default implementation, and additional
implementations can be registered using the register() attribute of the
generic function.
"""
# There are many programs that use functools without singledispatch, so we
# trade-off making singledispatch marginally slower for the benefit of
# making start-up of such applications slightly faster.
import types, weakref
registry = {}
dispatch_cache = weakref.WeakKeyDictionary()
cache_token = None
def dispatch(cls):
"""generic_func.dispatch(cls) -> <function implementation>
Runs the dispatch algorithm to return the best available implementation
for the given *cls* registered on *generic_func*.
"""
nonlocal cache_token
if cache_token is not None:
current_token = get_cache_token()
if cache_token != current_token:
dispatch_cache.clear()
cache_token = current_token
try:
impl = dispatch_cache[cls]
except KeyError:
try:
impl = registry[cls]
except KeyError:
impl = _find_impl(cls, registry)
dispatch_cache[cls] = impl
return impl
def _is_union_type(cls):
from typing import get_origin, Union
return get_origin(cls) in {Union, types.UnionType}
def _is_valid_dispatch_type(cls):
if isinstance(cls, type):
return True
from typing import get_args
return (_is_union_type(cls) and
all(isinstance(arg, type) for arg in get_args(cls)))
def register(cls, func=None):
"""generic_func.register(cls, func) -> func
Registers a new implementation for the given *cls* on a *generic_func*.
"""
nonlocal cache_token
if _is_valid_dispatch_type(cls):
if func is None:
return lambda f: register(cls, f)
else:
if func is not None:
raise TypeError(
f"Invalid first argument to `register()`. "
f"{cls!r} is not a class or union type."
)
ann = getattr(cls, '__annotations__', {})
if not ann:
raise TypeError(
f"Invalid first argument to `register()`: {cls!r}. "
f"Use either `@register(some_class)` or plain `@register` "
f"on an annotated function."
)
func = cls
# only import typing if annotation parsing is necessary
from typing import get_type_hints
argname, cls = next(iter(get_type_hints(func).items()))
if not _is_valid_dispatch_type(cls):
if _is_union_type(cls):
raise TypeError(
f"Invalid annotation for {argname!r}. "
f"{cls!r} not all arguments are classes."
)
else:
raise TypeError(
f"Invalid annotation for {argname!r}. "
f"{cls!r} is not a class."
)
if _is_union_type(cls):
from typing import get_args
for arg in get_args(cls):
registry[arg] = func
else:
registry[cls] = func
if cache_token is None and hasattr(cls, '__abstractmethods__'):
cache_token = get_cache_token()
dispatch_cache.clear()
return func
def wrapper(*args, **kw):
if not args:
raise TypeError(f'{funcname} requires at least '
'1 positional argument')
return dispatch(args[0].__class__)(*args, **kw)
funcname = getattr(func, '__name__', 'singledispatch function')
registry[object] = func
wrapper.register = register
wrapper.dispatch = dispatch
wrapper.registry = types.MappingProxyType(registry)
wrapper._clear_cache = dispatch_cache.clear
update_wrapper(wrapper, func)
return wrapper
# Descriptor version
class singledispatchmethod:
"""Single-dispatch generic method descriptor.
Supports wrapping existing descriptors and handles non-descriptor
callables as instance methods.
"""
def __init__(self, func):
if not callable(func) and not hasattr(func, "__get__"):
raise TypeError(f"{func!r} is not callable or a descriptor")
self.dispatcher = singledispatch(func)
self.func = func
def register(self, cls, method=None):
"""generic_method.register(cls, func) -> func
Registers a new implementation for the given *cls* on a *generic_method*.
"""
return self.dispatcher.register(cls, func=method)
def __get__(self, obj, cls=None):
def _method(*args, **kwargs):
method = self.dispatcher.dispatch(args[0].__class__)
return method.__get__(obj, cls)(*args, **kwargs)
_method.__isabstractmethod__ = self.__isabstractmethod__
_method.register = self.register
update_wrapper(_method, self.func)
return _method
@property
def __isabstractmethod__(self):
return getattr(self.func, '__isabstractmethod__', False)
################################################################################
### cached_property() - computed once per instance, cached as attribute
################################################################################
_NOT_FOUND = object()
class cached_property:
def __init__(self, func):
self.func = func
self.attrname = None
self.__doc__ = func.__doc__
self.lock = RLock()
def __set_name__(self, owner, name):
if self.attrname is None:
self.attrname = name
elif name != self.attrname:
raise TypeError(
"Cannot assign the same cached_property to two different names "
f"({self.attrname!r} and {name!r})."
)
def __get__(self, instance, owner=None):
if instance is None:
return self
if self.attrname is None:
raise TypeError(
"Cannot use cached_property instance without calling __set_name__ on it.")
try:
cache = instance.__dict__
except AttributeError: # not all objects have __dict__ (e.g. class defines slots)
msg = (
f"No '__dict__' attribute on {type(instance).__name__!r} "
f"instance to cache {self.attrname!r} property."
)
raise TypeError(msg) from None
val = cache.get(self.attrname, _NOT_FOUND)
if val is _NOT_FOUND:
with self.lock:
# check if another thread filled cache while we awaited lock
val = cache.get(self.attrname, _NOT_FOUND)
if val is _NOT_FOUND:
val = self.func(instance)
try:
cache[self.attrname] = val
except TypeError:
msg = (
f"The '__dict__' attribute on {type(instance).__name__!r} instance "
f"does not support item assignment for caching {self.attrname!r} property."
)
raise TypeError(msg) from None
return val
__class_getitem__ = classmethod(GenericAlias)
datetime.py
"""Concrete date/time and related types.
See http://www.iana.org/time-zones/repository/tz-link.html for
time zone and DST data sources.
"""
__all__ = ("date", "datetime", "time", "timedelta", "timezone", "tzinfo",
"MINYEAR", "MAXYEAR", "UTC")
import time as _time
import math as _math
import sys
from operator import index as _index
def _cmp(x, y):
return 0 if x == y else 1 if x > y else -1
MINYEAR = 1
MAXYEAR = 9999
_MAXORDINAL = 3652059 # date.max.toordinal()
# Utility functions, adapted from Python's Demo/classes/Dates.py, which
# also assumes the current Gregorian calendar indefinitely extended in
# both directions. Difference: Dates.py calls January 1 of year 0 day
# number 1. The code here calls January 1 of year 1 day number 1. This is
# to match the definition of the "proleptic Gregorian" calendar in Dershowitz
# and Reingold's "Calendrical Calculations", where it's the base calendar
# for all computations. See the book for algorithms for converting between
# proleptic Gregorian ordinals and many other calendar systems.
# -1 is a placeholder for indexing purposes.
_DAYS_IN_MONTH = [-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
_DAYS_BEFORE_MONTH = [-1] # -1 is a placeholder for indexing purposes.
dbm = 0
for dim in _DAYS_IN_MONTH[1:]:
_DAYS_BEFORE_MONTH.append(dbm)
dbm += dim
del dbm, dim
def _is_leap(year):
"year -> 1 if leap year, else 0."
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
def _days_before_year(year):
"year -> number of days before January 1st of year."
y = year - 1
return y*365 + y//4 - y//100 + y//400
def _days_in_month(year, month):
"year, month -> number of days in that month in that year."
assert 1 <= month <= 12, month
if month == 2 and _is_leap(year):
return 29
return _DAYS_IN_MONTH[month]
def _days_before_month(year, month):
"year, month -> number of days in year preceding first day of month."
assert 1 <= month <= 12, 'month must be in 1..12'
return _DAYS_BEFORE_MONTH[month] + (month > 2 and _is_leap(year))
def _ymd2ord(year, month, day):
"year, month, day -> ordinal, considering 01-Jan-0001 as day 1."
assert 1 <= month <= 12, 'month must be in 1..12'
dim = _days_in_month(year, month)
assert 1 <= day <= dim, ('day must be in 1..%d' % dim)
return (_days_before_year(year) +
_days_before_month(year, month) +
day)
_DI400Y = _days_before_year(401) # number of days in 400 years
_DI100Y = _days_before_year(101) # " " " " 100 "
_DI4Y = _days_before_year(5) # " " " " 4 "
# A 4-year cycle has an extra leap day over what we'd get from pasting
# together 4 single years.
assert _DI4Y == 4 * 365 + 1
# Similarly, a 400-year cycle has an extra leap day over what we'd get from
# pasting together 4 100-year cycles.
assert _DI400Y == 4 * _DI100Y + 1
# OTOH, a 100-year cycle has one fewer leap day than we'd get from
# pasting together 25 4-year cycles.
assert _DI100Y == 25 * _DI4Y - 1
def _ord2ymd(n):
"ordinal -> (year, month, day), considering 01-Jan-0001 as day 1."
# n is a 1-based index, starting at 1-Jan-1. The pattern of leap years
# repeats exactly every 400 years. The basic strategy is to find the
# closest 400-year boundary at or before n, then work with the offset
# from that boundary to n. Life is much clearer if we subtract 1 from
# n first -- then the values of n at 400-year boundaries are exactly
# those divisible by _DI400Y:
#
# D M Y n n-1
# -- --- ---- ---------- ----------------
# 31 Dec -400 -_DI400Y -_DI400Y -1
# 1 Jan -399 -_DI400Y +1 -_DI400Y 400-year boundary
# ...
# 30 Dec 000 -1 -2
# 31 Dec 000 0 -1
# 1 Jan 001 1 0 400-year boundary
# 2 Jan 001 2 1
# 3 Jan 001 3 2
# ...
# 31 Dec 400 _DI400Y _DI400Y -1
# 1 Jan 401 _DI400Y +1 _DI400Y 400-year boundary
n -= 1
n400, n = divmod(n, _DI400Y)
year = n400 * 400 + 1 # ..., -399, 1, 401, ...
# Now n is the (non-negative) offset, in days, from January 1 of year, to
# the desired date. Now compute how many 100-year cycles precede n.
# Note that it's possible for n100 to equal 4! In that case 4 full
# 100-year cycles precede the desired day, which implies the desired
# day is December 31 at the end of a 400-year cycle.
n100, n = divmod(n, _DI100Y)
# Now compute how many 4-year cycles precede it.
n4, n = divmod(n, _DI4Y)
# And now how many single years. Again n1 can be 4, and again meaning
# that the desired day is December 31 at the end of the 4-year cycle.
n1, n = divmod(n, 365)
year += n100 * 100 + n4 * 4 + n1
if n1 == 4 or n100 == 4:
assert n == 0
return year-1, 12, 31
# Now the year is correct, and n is the offset from January 1. We find
# the month via an estimate that's either exact or one too large.
leapyear = n1 == 3 and (n4 != 24 or n100 == 3)
assert leapyear == _is_leap(year)
month = (n + 50) >> 5
preceding = _DAYS_BEFORE_MONTH[month] + (month > 2 and leapyear)
if preceding > n: # estimate is too large
month -= 1
preceding -= _DAYS_IN_MONTH[month] + (month == 2 and leapyear)
n -= preceding
assert 0 <= n < _days_in_month(year, month)
# Now the year and month are correct, and n is the offset from the
# start of that month: we're done!
return year, month, n+1
# Month and day names. For localized versions, see the calendar module.
_MONTHNAMES = [None, "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
_DAYNAMES = [None, "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
def _build_struct_time(y, m, d, hh, mm, ss, dstflag):
wday = (_ymd2ord(y, m, d) + 6) % 7
dnum = _days_before_month(y, m) + d
return _time.struct_time((y, m, d, hh, mm, ss, wday, dnum, dstflag))
def _format_time(hh, mm, ss, us, timespec='auto'):
specs = {
'hours': '{:02d}',
'minutes': '{:02d}:{:02d}',
'seconds': '{:02d}:{:02d}:{:02d}',
'milliseconds': '{:02d}:{:02d}:{:02d}.{:03d}',
'microseconds': '{:02d}:{:02d}:{:02d}.{:06d}'
}
if timespec == 'auto':
# Skip trailing microseconds when us==0.
timespec = 'microseconds' if us else 'seconds'
elif timespec == 'milliseconds':
us //= 1000
try:
fmt = specs[timespec]
except KeyError:
raise ValueError('Unknown timespec value')
else:
return fmt.format(hh, mm, ss, us)
def _format_offset(off):
s = ''
if off is not None:
if off.days < 0:
sign = "-"
off = -off
else:
sign = "+"
hh, mm = divmod(off, timedelta(hours=1))
mm, ss = divmod(mm, timedelta(minutes=1))
s += "%s%02d:%02d" % (sign, hh, mm)
if ss or ss.microseconds:
s += ":%02d" % ss.seconds
if ss.microseconds:
s += '.%06d' % ss.microseconds
return s
# Correctly substitute for %z and %Z escapes in strftime formats.
def _wrap_strftime(object, format, timetuple):
# Don't call utcoffset() or tzname() unless actually needed.
freplace = None # the string to use for %f
zreplace = None # the string to use for %z
Zreplace = None # the string to use for %Z
# Scan format for %z and %Z escapes, replacing as needed.
newformat = []
push = newformat.append
i, n = 0, len(format)
while i < n:
ch = format[i]
i += 1
if ch == '%':
if i < n:
ch = format[i]
i += 1
if ch == 'f':
if freplace is None:
freplace = '%06d' % getattr(object,
'microsecond', 0)
newformat.append(freplace)
elif ch == 'z':
if zreplace is None:
zreplace = ""
if hasattr(object, "utcoffset"):
offset = object.utcoffset()
if offset is not None:
sign = '+'
if offset.days < 0:
offset = -offset
sign = '-'
h, rest = divmod(offset, timedelta(hours=1))
m, rest = divmod(rest, timedelta(minutes=1))
s = rest.seconds
u = offset.microseconds
if u:
zreplace = '%c%02d%02d%02d.%06d' % (sign, h, m, s, u)
elif s:
zreplace = '%c%02d%02d%02d' % (sign, h, m, s)
else:
zreplace = '%c%02d%02d' % (sign, h, m)
assert '%' not in zreplace
newformat.append(zreplace)
elif ch == 'Z':
if Zreplace is None:
Zreplace = ""
if hasattr(object, "tzname"):
s = object.tzname()
if s is not None:
# strftime is going to have at this: escape %
Zreplace = s.replace('%', '%%')
newformat.append(Zreplace)
else:
push('%')
push(ch)
else:
push('%')
else:
push(ch)
newformat = "".join(newformat)
return _time.strftime(newformat, timetuple)
# Helpers for parsing the result of isoformat()
def _is_ascii_digit(c):
return c in "0123456789"
def _find_isoformat_datetime_separator(dtstr):
# See the comment in _datetimemodule.c:_find_isoformat_datetime_separator
len_dtstr = len(dtstr)
if len_dtstr == 7:
return 7
assert len_dtstr > 7
date_separator = "-"
week_indicator = "W"
if dtstr[4] == date_separator:
if dtstr[5] == week_indicator:
if len_dtstr < 8:
raise ValueError("Invalid ISO string")
if len_dtstr > 8 and dtstr[8] == date_separator:
if len_dtstr == 9:
raise ValueError("Invalid ISO string")
if len_dtstr > 10 and _is_ascii_digit(dtstr[10]):
# This is as far as we need to resolve the ambiguity for
# the moment - if we have YYYY-Www-##, the separator is
# either a hyphen at 8 or a number at 10.
#
# We'll assume it's a hyphen at 8 because it's way more
# likely that someone will use a hyphen as a separator than
# a number, but at this point it's really best effort
# because this is an extension of the spec anyway.
# TODO(pganssle): Document this
return 8
return 10
else:
# YYYY-Www (8)
return 8
else:
# YYYY-MM-DD (10)
return 10
else:
if dtstr[4] == week_indicator:
# YYYYWww (7) or YYYYWwwd (8)
idx = 7
while idx < len_dtstr:
if not _is_ascii_digit(dtstr[idx]):
break
idx += 1
if idx < 9:
return idx
if idx % 2 == 0:
# If the index of the last number is even, it's YYYYWwwd
return 7
else:
return 8
else:
# YYYYMMDD (8)
return 8
def _parse_isoformat_date(dtstr):
# It is assumed that this is an ASCII-only string of lengths 7, 8 or 10,
# see the comment on Modules/_datetimemodule.c:_find_isoformat_datetime_separator
assert len(dtstr) in (7, 8, 10)
year = int(dtstr[0:4])
has_sep = dtstr[4] == '-'
pos = 4 + has_sep
if dtstr[pos:pos + 1] == "W":
# YYYY-?Www-?D?
pos += 1
weekno = int(dtstr[pos:pos + 2])
pos += 2
dayno = 1
if len(dtstr) > pos:
if (dtstr[pos:pos + 1] == '-') != has_sep:
raise ValueError("Inconsistent use of dash separator")
pos += has_sep
dayno = int(dtstr[pos:pos + 1])
return list(_isoweek_to_gregorian(year, weekno, dayno))
else:
month = int(dtstr[pos:pos + 2])
pos += 2
if (dtstr[pos:pos + 1] == "-") != has_sep:
raise ValueError("Inconsistent use of dash separator")
pos += has_sep
day = int(dtstr[pos:pos + 2])
return [year, month, day]
_FRACTION_CORRECTION = [100000, 10000, 1000, 100, 10]
def _parse_hh_mm_ss_ff(tstr):
# Parses things of the form HH[:?MM[:?SS[{.,}fff[fff]]]]
len_str = len(tstr)
time_comps = [0, 0, 0, 0]
pos = 0
for comp in range(0, 3):
if (len_str - pos) < 2:
raise ValueError("Incomplete time component")
time_comps[comp] = int(tstr[pos:pos+2])
pos += 2
next_char = tstr[pos:pos+1]
if comp == 0:
has_sep = next_char == ':'
if not next_char or comp >= 2:
break
if has_sep and next_char != ':':
raise ValueError("Invalid time separator: %c" % next_char)
pos += has_sep
if pos < len_str:
if tstr[pos] not in '.,':
raise ValueError("Invalid microsecond component")
else:
pos += 1
len_remainder = len_str - pos
if len_remainder >= 6:
to_parse = 6
else:
to_parse = len_remainder
time_comps[3] = int(tstr[pos:(pos+to_parse)])
if to_parse < 6:
time_comps[3] *= _FRACTION_CORRECTION[to_parse-1]
if (len_remainder > to_parse
and not all(map(_is_ascii_digit, tstr[(pos+to_parse):]))):
raise ValueError("Non-digit values in unparsed fraction")
return time_comps
def _parse_isoformat_time(tstr):
# Format supported is HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]
len_str = len(tstr)
if len_str < 2:
raise ValueError("Isoformat time too short")
# This is equivalent to re.search('[+-Z]', tstr), but faster
tz_pos = (tstr.find('-') + 1 or tstr.find('+') + 1 or tstr.find('Z') + 1)
timestr = tstr[:tz_pos-1] if tz_pos > 0 else tstr
time_comps = _parse_hh_mm_ss_ff(timestr)
tzi = None
if tz_pos == len_str and tstr[-1] == 'Z':
tzi = timezone.utc
elif tz_pos > 0:
tzstr = tstr[tz_pos:]
# Valid time zone strings are:
# HH len: 2
# HHMM len: 4
# HH:MM len: 5
# HHMMSS len: 6
# HHMMSS.f+ len: 7+
# HH:MM:SS len: 8
# HH:MM:SS.f+ len: 10+
if len(tzstr) in (0, 1, 3):
raise ValueError("Malformed time zone string")
tz_comps = _parse_hh_mm_ss_ff(tzstr)
if all(x == 0 for x in tz_comps):
tzi = timezone.utc
else:
tzsign = -1 if tstr[tz_pos - 1] == '-' else 1
td = timedelta(hours=tz_comps[0], minutes=tz_comps[1],
seconds=tz_comps[2], microseconds=tz_comps[3])
tzi = timezone(tzsign * td)
time_comps.append(tzi)
return time_comps
# tuple[int, int, int] -> tuple[int, int, int] version of date.fromisocalendar
def _isoweek_to_gregorian(year, week, day):
# Year is bounded this way because 9999-12-31 is (9999, 52, 5)
if not MINYEAR <= year <= MAXYEAR:
raise ValueError(f"Year is out of range: {year}")
if not 0 < week < 53:
out_of_range = True
if week == 53:
# ISO years have 53 weeks in them on years starting with a
# Thursday and leap years starting on a Wednesday
first_weekday = _ymd2ord(year, 1, 1) % 7
if (first_weekday == 4 or (first_weekday == 3 and
_is_leap(year))):
out_of_range = False
if out_of_range:
raise ValueError(f"Invalid week: {week}")
if not 0 < day < 8:
raise ValueError(f"Invalid weekday: {day} (range is [1, 7])")
# Now compute the offset from (Y, 1, 1) in days:
day_offset = (week - 1) * 7 + (day - 1)
# Calculate the ordinal day for monday, week 1
day_1 = _isoweek1monday(year)
ord_day = day_1 + day_offset
return _ord2ymd(ord_day)
# Just raise TypeError if the arg isn't None or a string.
def _check_tzname(name):
if name is not None and not isinstance(name, str):
raise TypeError("tzinfo.tzname() must return None or string, "
"not '%s'" % type(name))
# name is the offset-producing method, "utcoffset" or "dst".
# offset is what it returned.
# If offset isn't None or timedelta, raises TypeError.
# If offset is None, returns None.
# Else offset is checked for being in range.
# If it is, its integer value is returned. Else ValueError is raised.
def _check_utc_offset(name, offset):
assert name in ("utcoffset", "dst")
if offset is None:
return
if not isinstance(offset, timedelta):
raise TypeError("tzinfo.%s() must return None "
"or timedelta, not '%s'" % (name, type(offset)))
if not -timedelta(1) < offset < timedelta(1):
raise ValueError("%s()=%s, must be strictly between "
"-timedelta(hours=24) and timedelta(hours=24)" %
(name, offset))
def _check_date_fields(year, month, day):
year = _index(year)
month = _index(month)
day = _index(day)
if not MINYEAR <= year <= MAXYEAR:
raise ValueError('year must be in %d..%d' % (MINYEAR, MAXYEAR), year)
if not 1 <= month <= 12:
raise ValueError('month must be in 1..12', month)
dim = _days_in_month(year, month)
if not 1 <= day <= dim:
raise ValueError('day must be in 1..%d' % dim, day)
return year, month, day
def _check_time_fields(hour, minute, second, microsecond, fold):
hour = _index(hour)
minute = _index(minute)
second = _index(second)
microsecond = _index(microsecond)
if not 0 <= hour <= 23:
raise ValueError('hour must be in 0..23', hour)
if not 0 <= minute <= 59:
raise ValueError('minute must be in 0..59', minute)
if not 0 <= second <= 59:
raise ValueError('second must be in 0..59', second)
if not 0 <= microsecond <= 999999:
raise ValueError('microsecond must be in 0..999999', microsecond)
if fold not in (0, 1):
raise ValueError('fold must be either 0 or 1', fold)
return hour, minute, second, microsecond, fold
def _check_tzinfo_arg(tz):
if tz is not None and not isinstance(tz, tzinfo):
raise TypeError("tzinfo argument must be None or of a tzinfo subclass")
def _cmperror(x, y):
raise TypeError("can't compare '%s' to '%s'" % (
type(x).__name__, type(y).__name__))
def _divide_and_round(a, b):
"""divide a by b and round result to the nearest integer
When the ratio is exactly half-way between two integers,
the even integer is returned.
"""
# Based on the reference implementation for divmod_near
# in Objects/longobject.c.
q, r = divmod(a, b)
# round up if either r / b > 0.5, or r / b == 0.5 and q is odd.
# The expression r / b > 0.5 is equivalent to 2 * r > b if b is
# positive, 2 * r < b if b negative.
r *= 2
greater_than_half = r > b if b > 0 else r < b
if greater_than_half or r == b and q % 2 == 1:
q += 1
return q
class timedelta:
"""Represent the difference between two datetime objects.
Supported operators:
- add, subtract timedelta
- unary plus, minus, abs
- compare to timedelta
- multiply, divide by int
In addition, datetime supports subtraction of two datetime objects
returning a timedelta, and addition or subtraction of a datetime
and a timedelta giving a datetime.
Representation: (days, seconds, microseconds). Why? Because I
felt like it.
"""
__slots__ = '_days', '_seconds', '_microseconds', '_hashcode'
def __new__(cls, days=0, seconds=0, microseconds=0,
milliseconds=0, minutes=0, hours=0, weeks=0):
# Doing this efficiently and accurately in C is going to be difficult
# and error-prone, due to ubiquitous overflow possibilities, and that
# C double doesn't have enough bits of precision to represent
# microseconds over 10K years faithfully. The code here tries to make
# explicit where go-fast assumptions can be relied on, in order to
# guide the C implementation; it's way more convoluted than speed-
# ignoring auto-overflow-to-long idiomatic Python could be.
# XXX Check that all inputs are ints or floats.
# Final values, all integer.
# s and us fit in 32-bit signed ints; d isn't bounded.
d = s = us = 0
# Normalize everything to days, seconds, microseconds.
days += weeks*7
seconds += minutes*60 + hours*3600
microseconds += milliseconds*1000
# Get rid of all fractions, and normalize s and us.
# Take a deep breath <wink>.
if isinstance(days, float):
dayfrac, days = _math.modf(days)
daysecondsfrac, daysecondswhole = _math.modf(dayfrac * (24.*3600.))
assert daysecondswhole == int(daysecondswhole) # can't overflow
s = int(daysecondswhole)
assert days == int(days)
d = int(days)
else:
daysecondsfrac = 0.0
d = days
assert isinstance(daysecondsfrac, float)
assert abs(daysecondsfrac) <= 1.0
assert isinstance(d, int)
assert abs(s) <= 24 * 3600
# days isn't referenced again before redefinition
if isinstance(seconds, float):
secondsfrac, seconds = _math.modf(seconds)
assert seconds == int(seconds)
seconds = int(seconds)
secondsfrac += daysecondsfrac
assert abs(secondsfrac) <= 2.0
else:
secondsfrac = daysecondsfrac
# daysecondsfrac isn't referenced again
assert isinstance(secondsfrac, float)
assert abs(secondsfrac) <= 2.0
assert isinstance(seconds, int)
days, seconds = divmod(seconds, 24*3600)
d += days
s += int(seconds) # can't overflow
assert isinstance(s, int)
assert abs(s) <= 2 * 24 * 3600
# seconds isn't referenced again before redefinition
usdouble = secondsfrac * 1e6
assert abs(usdouble) < 2.1e6 # exact value not critical
# secondsfrac isn't referenced again
if isinstance(microseconds, float):
microseconds = round(microseconds + usdouble)
seconds, microseconds = divmod(microseconds, 1000000)
days, seconds = divmod(seconds, 24*3600)
d += days
s += seconds
else:
microseconds = int(microseconds)
seconds, microseconds = divmod(microseconds, 1000000)
days, seconds = divmod(seconds, 24*3600)
d += days
s += seconds
microseconds = round(microseconds + usdouble)
assert isinstance(s, int)
assert isinstance(microseconds, int)
assert abs(s) <= 3 * 24 * 3600
assert abs(microseconds) < 3.1e6
# Just a little bit of carrying possible for microseconds and seconds.
seconds, us = divmod(microseconds, 1000000)
s += seconds
days, s = divmod(s, 24*3600)
d += days
assert isinstance(d, int)
assert isinstance(s, int) and 0 <= s < 24*3600
assert isinstance(us, int) and 0 <= us < 1000000
if abs(d) > 999999999:
raise OverflowError("timedelta # of days is too large: %d" % d)
self = object.__new__(cls)
self._days = d
self._seconds = s
self._microseconds = us
self._hashcode = -1
return self
def __repr__(self):
args = []
if self._days:
args.append("days=%d" % self._days)
if self._seconds:
args.append("seconds=%d" % self._seconds)
if self._microseconds:
args.append("microseconds=%d" % self._microseconds)
if not args:
args.append('0')
return "%s.%s(%s)" % (self.__class__.__module__,
self.__class__.__qualname__,
', '.join(args))
def __str__(self):
mm, ss = divmod(self._seconds, 60)
hh, mm = divmod(mm, 60)
s = "%d:%02d:%02d" % (hh, mm, ss)
if self._days:
def plural(n):
return n, abs(n) != 1 and "s" or ""
s = ("%d day%s, " % plural(self._days)) + s
if self._microseconds:
s = s + ".%06d" % self._microseconds
return s
def total_seconds(self):
"""Total seconds in the duration."""
return ((self.days * 86400 + self.seconds) * 10**6 +
self.microseconds) / 10**6
# Read-only field accessors
@property
def days(self):
"""days"""
return self._days
@property
def seconds(self):
"""seconds"""
return self._seconds
@property
def microseconds(self):
"""microseconds"""
return self._microseconds
def __add__(self, other):
if isinstance(other, timedelta):
# for CPython compatibility, we cannot use
# our __class__ here, but need a real timedelta
return timedelta(self._days + other._days,
self._seconds + other._seconds,
self._microseconds + other._microseconds)
return NotImplemented
__radd__ = __add__
def __sub__(self, other):
if isinstance(other, timedelta):
# for CPython compatibility, we cannot use
# our __class__ here, but need a real timedelta
return timedelta(self._days - other._days,
self._seconds - other._seconds,
self._microseconds - other._microseconds)
return NotImplemented
def __rsub__(self, other):
if isinstance(other, timedelta):
return -self + other
return NotImplemented
def __neg__(self):
# for CPython compatibility, we cannot use
# our __class__ here, but need a real timedelta
return timedelta(-self._days,
-self._seconds,
-self._microseconds)
def __pos__(self):
return self
def __abs__(self):
if self._days < 0:
return -self
else:
return self
def __mul__(self, other):
if isinstance(other, int):
# for CPython compatibility, we cannot use
# our __class__ here, but need a real timedelta
return timedelta(self._days * other,
self._seconds * other,
self._microseconds * other)
if isinstance(other, float):
usec = self._to_microseconds()
a, b = other.as_integer_ratio()
return timedelta(0, 0, _divide_and_round(usec * a, b))
return NotImplemented
__rmul__ = __mul__
def _to_microseconds(self):
return ((self._days * (24*3600) + self._seconds) * 1000000 +
self._microseconds)
def __floordiv__(self, other):
if not isinstance(other, (int, timedelta)):
return NotImplemented
usec = self._to_microseconds()
if isinstance(other, timedelta):
return usec // other._to_microseconds()
if isinstance(other, int):
return timedelta(0, 0, usec // other)
def __truediv__(self, other):
if not isinstance(other, (int, float, timedelta)):
return NotImplemented
usec = self._to_microseconds()
if isinstance(other, timedelta):
return usec / other._to_microseconds()
if isinstance(other, int):
return timedelta(0, 0, _divide_and_round(usec, other))
if isinstance(other, float):
a, b = other.as_integer_ratio()
return timedelta(0, 0, _divide_and_round(b * usec, a))
def __mod__(self, other):
if isinstance(other, timedelta):
r = self._to_microseconds() % other._to_microseconds()
return timedelta(0, 0, r)
return NotImplemented
def __divmod__(self, other):
if isinstance(other, timedelta):
q, r = divmod(self._to_microseconds(),
other._to_microseconds())
return q, timedelta(0, 0, r)
return NotImplemented
# Comparisons of timedelta objects with other.
def __eq__(self, other):
if isinstance(other, timedelta):
return self._cmp(other) == 0
else:
return NotImplemented
def __le__(self, other):
if isinstance(other, timedelta):
return self._cmp(other) <= 0
else:
return NotImplemented
def __lt__(self, other):
if isinstance(other, timedelta):
return self._cmp(other) < 0
else:
return NotImplemented
def __ge__(self, other):
if isinstance(other, timedelta):
return self._cmp(other) >= 0
else:
return NotImplemented
def __gt__(self, other):
if isinstance(other, timedelta):
return self._cmp(other) > 0
else:
return NotImplemented
def _cmp(self, other):
assert isinstance(other, timedelta)
return _cmp(self._getstate(), other._getstate())
def __hash__(self):
if self._hashcode == -1:
self._hashcode = hash(self._getstate())
return self._hashcode
def __bool__(self):
return (self._days != 0 or
self._seconds != 0 or
self._microseconds != 0)
# Pickle support.
def _getstate(self):
return (self._days, self._seconds, self._microseconds)
def __reduce__(self):
return (self.__class__, self._getstate())
timedelta.min = timedelta(-999999999)
timedelta.max = timedelta(days=999999999, hours=23, minutes=59, seconds=59,
microseconds=999999)
timedelta.resolution = timedelta(microseconds=1)
class date:
"""Concrete date type.
Constructors:
__new__()
fromtimestamp()
today()
fromordinal()
Operators:
__repr__, __str__
__eq__, __le__, __lt__, __ge__, __gt__, __hash__
__add__, __radd__, __sub__ (add/radd only with timedelta arg)
Methods:
timetuple()
toordinal()
weekday()
isoweekday(), isocalendar(), isoformat()
ctime()
strftime()
Properties (readonly):
year, month, day
"""
__slots__ = '_year', '_month', '_day', '_hashcode'
def __new__(cls, year, month=None, day=None):
"""Constructor.
Arguments:
year, month, day (required, base 1)
"""
if (month is None and
isinstance(year, (bytes, str)) and len(year) == 4 and
1 <= ord(year[2:3]) <= 12):
# Pickle support
if isinstance(year, str):
try:
year = year.encode('latin1')
except UnicodeEncodeError:
# More informative error message.
raise ValueError(
"Failed to encode latin1 string when unpickling "
"a date object. "
"pickle.load(data, encoding='latin1') is assumed.")
self = object.__new__(cls)
self.__setstate(year)
self._hashcode = -1
return self
year, month, day = _check_date_fields(year, month, day)
self = object.__new__(cls)
self._year = year
self._month = month
self._day = day
self._hashcode = -1
return self
# Additional constructors
@classmethod
def fromtimestamp(cls, t):
"Construct a date from a POSIX timestamp (like time.time())."
y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t)
return cls(y, m, d)
@classmethod
def today(cls):
"Construct a date from time.time()."
t = _time.time()
return cls.fromtimestamp(t)
@classmethod
def fromordinal(cls, n):
"""Construct a date from a proleptic Gregorian ordinal.
January 1 of year 1 is day 1. Only the year, month and day are
non-zero in the result.
"""
y, m, d = _ord2ymd(n)
return cls(y, m, d)
@classmethod
def fromisoformat(cls, date_string):
"""Construct a date from a string in ISO 8601 format."""
if not isinstance(date_string, str):
raise TypeError('fromisoformat: argument must be str')
if len(date_string) not in (7, 8, 10):
raise ValueError(f'Invalid isoformat string: {date_string!r}')
try:
return cls(*_parse_isoformat_date(date_string))
except Exception:
raise ValueError(f'Invalid isoformat string: {date_string!r}')
@classmethod
def fromisocalendar(cls, year, week, day):
"""Construct a date from the ISO year, week number and weekday.
This is the inverse of the date.isocalendar() function"""
return cls(*_isoweek_to_gregorian(year, week, day))
# Conversions to string
def __repr__(self):
"""Convert to formal string, for repr().
>>> d = date(2010, 1, 1)
>>> repr(d)
'datetime.date(2010, 1, 1)'
"""
return "%s.%s(%d, %d, %d)" % (self.__class__.__module__,
self.__class__.__qualname__,
self._year,
self._month,
self._day)
# XXX These shouldn't depend on time.localtime(), because that
# clips the usable dates to [1970 .. 2038). At least ctime() is
# easily done without using strftime() -- that's better too because
# strftime("%c", ...) is locale specific.
def ctime(self):
"Return ctime() style string."
weekday = self.toordinal() % 7 or 7
return "%s %s %2d 00:00:00 %04d" % (
_DAYNAMES[weekday],
_MONTHNAMES[self._month],
self._day, self._year)
def strftime(self, fmt):
"""
Format using strftime().
Example: "%d/%m/%Y, %H:%M:%S"
"""
return _wrap_strftime(self, fmt, self.timetuple())
def __format__(self, fmt):
if not isinstance(fmt, str):
raise TypeError("must be str, not %s" % type(fmt).__name__)
if len(fmt) != 0:
return self.strftime(fmt)
return str(self)
def isoformat(self):
"""Return the date formatted according to ISO.
This is 'YYYY-MM-DD'.
References:
- http://www.w3.org/TR/NOTE-datetime
- http://www.cl.cam.ac.uk/~mgk25/iso-time.html
"""
return "%04d-%02d-%02d" % (self._year, self._month, self._day)
__str__ = isoformat
# Read-only field accessors
@property
def year(self):
"""year (1-9999)"""
return self._year
@property
def month(self):
"""month (1-12)"""
return self._month
@property
def day(self):
"""day (1-31)"""
return self._day
# Standard conversions, __eq__, __le__, __lt__, __ge__, __gt__,
# __hash__ (and helpers)
def timetuple(self):
"Return local time tuple compatible with time.localtime()."
return _build_struct_time(self._year, self._month, self._day,
0, 0, 0, -1)
def toordinal(self):
"""Return proleptic Gregorian ordinal for the year, month and day.
January 1 of year 1 is day 1. Only the year, month and day values
contribute to the result.
"""
return _ymd2ord(self._year, self._month, self._day)
def replace(self, year=None, month=None, day=None):
"""Return a new date with new values for the specified fields."""
if year is None:
year = self._year
if month is None:
month = self._month
if day is None:
day = self._day
return type(self)(year, month, day)
# Comparisons of date objects with other.
def __eq__(self, other):
if isinstance(other, date):
return self._cmp(other) == 0
return NotImplemented
def __le__(self, other):
if isinstance(other, date):
return self._cmp(other) <= 0
return NotImplemented
def __lt__(self, other):
if isinstance(other, date):
return self._cmp(other) < 0
return NotImplemented
def __ge__(self, other):
if isinstance(other, date):
return self._cmp(other) >= 0
return NotImplemented
def __gt__(self, other):
if isinstance(other, date):
return self._cmp(other) > 0
return NotImplemented
def _cmp(self, other):
assert isinstance(other, date)
y, m, d = self._year, self._month, self._day
y2, m2, d2 = other._year, other._month, other._day
return _cmp((y, m, d), (y2, m2, d2))
def __hash__(self):
"Hash."
if self._hashcode == -1:
self._hashcode = hash(self._getstate())
return self._hashcode
# Computations
def __add__(self, other):
"Add a date to a timedelta."
if isinstance(other, timedelta):
o = self.toordinal() + other.days
if 0 < o <= _MAXORDINAL:
return type(self).fromordinal(o)
raise OverflowError("result out of range")
return NotImplemented
__radd__ = __add__
def __sub__(self, other):
"""Subtract two dates, or a date and a timedelta."""
if isinstance(other, timedelta):
return self + timedelta(-other.days)
if isinstance(other, date):
days1 = self.toordinal()
days2 = other.toordinal()
return timedelta(days1 - days2)
return NotImplemented
def weekday(self):
"Return day of the week, where Monday == 0 ... Sunday == 6."
return (self.toordinal() + 6) % 7
# Day-of-the-week and week-of-the-year, according to ISO
def isoweekday(self):
"Return day of the week, where Monday == 1 ... Sunday == 7."
# 1-Jan-0001 is a Monday
return self.toordinal() % 7 or 7
def isocalendar(self):
"""Return a named tuple containing ISO year, week number, and weekday.
The first ISO week of the year is the (Mon-Sun) week
containing the year's first Thursday; everything else derives
from that.
The first week is 1; Monday is 1 ... Sunday is 7.
ISO calendar algorithm taken from
http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
(used with permission)
"""
year = self._year
week1monday = _isoweek1monday(year)
today = _ymd2ord(self._year, self._month, self._day)
# Internally, week and day have origin 0
week, day = divmod(today - week1monday, 7)
if week < 0:
year -= 1
week1monday = _isoweek1monday(year)
week, day = divmod(today - week1monday, 7)
elif week >= 52:
if today >= _isoweek1monday(year+1):
year += 1
week = 0
return _IsoCalendarDate(year, week+1, day+1)
# Pickle support.
def _getstate(self):
yhi, ylo = divmod(self._year, 256)
return bytes([yhi, ylo, self._month, self._day]),
def __setstate(self, string):
yhi, ylo, self._month, self._day = string
self._year = yhi * 256 + ylo
def __reduce__(self):
return (self.__class__, self._getstate())
_date_class = date # so functions w/ args named "date" can get at the class
date.min = date(1, 1, 1)
date.max = date(9999, 12, 31)
date.resolution = timedelta(days=1)
class tzinfo:
"""Abstract base class for time zone info classes.
Subclasses must override the tzname(), utcoffset() and dst() methods.
"""
__slots__ = ()
def tzname(self, dt):
"datetime -> string name of time zone."
raise NotImplementedError("tzinfo subclass must override tzname()")
def utcoffset(self, dt):
"datetime -> timedelta, positive for east of UTC, negative for west of UTC"
raise NotImplementedError("tzinfo subclass must override utcoffset()")
def dst(self, dt):
"""datetime -> DST offset as timedelta, positive for east of UTC.
Return 0 if DST not in effect. utcoffset() must include the DST
offset.
"""
raise NotImplementedError("tzinfo subclass must override dst()")
def fromutc(self, dt):
"datetime in UTC -> datetime in local time."
if not isinstance(dt, datetime):
raise TypeError("fromutc() requires a datetime argument")
if dt.tzinfo is not self:
raise ValueError("dt.tzinfo is not self")
dtoff = dt.utcoffset()
if dtoff is None:
raise ValueError("fromutc() requires a non-None utcoffset() "
"result")
# See the long comment block at the end of this file for an
# explanation of this algorithm.
dtdst = dt.dst()
if dtdst is None:
raise ValueError("fromutc() requires a non-None dst() result")
delta = dtoff - dtdst
if delta:
dt += delta
dtdst = dt.dst()
if dtdst is None:
raise ValueError("fromutc(): dt.dst gave inconsistent "
"results; cannot convert")
return dt + dtdst
# Pickle support.
def __reduce__(self):
getinitargs = getattr(self, "__getinitargs__", None)
if getinitargs:
args = getinitargs()
else:
args = ()
return (self.__class__, args, self.__getstate__())
class IsoCalendarDate(tuple):
def __new__(cls, year, week, weekday, /):
return super().__new__(cls, (year, week, weekday))
@property
def year(self):
return self[0]
@property
def week(self):
return self[1]
@property
def weekday(self):
return self[2]
def __reduce__(self):
# This code is intended to pickle the object without making the
# class public. See https://bugs.python.org/msg352381
return (tuple, (tuple(self),))
def __repr__(self):
return (f'{self.__class__.__name__}'
f'(year={self[0]}, week={self[1]}, weekday={self[2]})')
_IsoCalendarDate = IsoCalendarDate
del IsoCalendarDate
_tzinfo_class = tzinfo
class time:
"""Time with time zone.
Constructors:
__new__()
Operators:
__repr__, __str__
__eq__, __le__, __lt__, __ge__, __gt__, __hash__
Methods:
strftime()
isoformat()
utcoffset()
tzname()
dst()
Properties (readonly):
hour, minute, second, microsecond, tzinfo, fold
"""
__slots__ = '_hour', '_minute', '_second', '_microsecond', '_tzinfo', '_hashcode', '_fold'
def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0):
"""Constructor.
Arguments:
hour, minute (required)
second, microsecond (default to zero)
tzinfo (default to None)
fold (keyword only, default to zero)
"""
if (isinstance(hour, (bytes, str)) and len(hour) == 6 and
ord(hour[0:1])&0x7F < 24):
# Pickle support
if isinstance(hour, str):
try:
hour = hour.encode('latin1')
except UnicodeEncodeError:
# More informative error message.
raise ValueError(
"Failed to encode latin1 string when unpickling "
"a time object. "
"pickle.load(data, encoding='latin1') is assumed.")
self = object.__new__(cls)
self.__setstate(hour, minute or None)
self._hashcode = -1
return self
hour, minute, second, microsecond, fold = _check_time_fields(
hour, minute, second, microsecond, fold)
_check_tzinfo_arg(tzinfo)
self = object.__new__(cls)
self._hour = hour
self._minute = minute
self._second = second
self._microsecond = microsecond
self._tzinfo = tzinfo
self._hashcode = -1
self._fold = fold
return self
# Read-only field accessors
@property
def hour(self):
"""hour (0-23)"""
return self._hour
@property
def minute(self):
"""minute (0-59)"""
return self._minute
@property
def second(self):
"""second (0-59)"""
return self._second
@property
def microsecond(self):
"""microsecond (0-999999)"""
return self._microsecond
@property
def tzinfo(self):
"""timezone info object"""
return self._tzinfo
@property
def fold(self):
return self._fold
# Standard conversions, __hash__ (and helpers)
# Comparisons of time objects with other.
def __eq__(self, other):
if isinstance(other, time):
return self._cmp(other, allow_mixed=True) == 0
else:
return NotImplemented
def __le__(self, other):
if isinstance(other, time):
return self._cmp(other) <= 0
else:
return NotImplemented
def __lt__(self, other):
if isinstance(other, time):
return self._cmp(other) < 0
else:
return NotImplemented
def __ge__(self, other):
if isinstance(other, time):
return self._cmp(other) >= 0
else:
return NotImplemented
def __gt__(self, other):
if isinstance(other, time):
return self._cmp(other) > 0
else:
return NotImplemented
def _cmp(self, other, allow_mixed=False):
assert isinstance(other, time)
mytz = self._tzinfo
ottz = other._tzinfo
myoff = otoff = None
if mytz is ottz:
base_compare = True
else:
myoff = self.utcoffset()
otoff = other.utcoffset()
base_compare = myoff == otoff
if base_compare:
return _cmp((self._hour, self._minute, self._second,
self._microsecond),
(other._hour, other._minute, other._second,
other._microsecond))
if myoff is None or otoff is None:
if allow_mixed:
return 2 # arbitrary non-zero value
else:
raise TypeError("cannot compare naive and aware times")
myhhmm = self._hour * 60 + self._minute - myoff//timedelta(minutes=1)
othhmm = other._hour * 60 + other._minute - otoff//timedelta(minutes=1)
return _cmp((myhhmm, self._second, self._microsecond),
(othhmm, other._second, other._microsecond))
def __hash__(self):
"""Hash."""
if self._hashcode == -1:
if self.fold:
t = self.replace(fold=0)
else:
t = self
tzoff = t.utcoffset()
if not tzoff: # zero or None
self._hashcode = hash(t._getstate()[0])
else:
h, m = divmod(timedelta(hours=self.hour, minutes=self.minute) - tzoff,
timedelta(hours=1))
assert not m % timedelta(minutes=1), "whole minute"
m //= timedelta(minutes=1)
if 0 <= h < 24:
self._hashcode = hash(time(h, m, self.second, self.microsecond))
else:
self._hashcode = hash((h, m, self.second, self.microsecond))
return self._hashcode
# Conversion to string
def _tzstr(self):
"""Return formatted timezone offset (+xx:xx) or an empty string."""
off = self.utcoffset()
return _format_offset(off)
def __repr__(self):
"""Convert to formal string, for repr()."""
if self._microsecond != 0:
s = ", %d, %d" % (self._second, self._microsecond)
elif self._second != 0:
s = ", %d" % self._second
else:
s = ""
s= "%s.%s(%d, %d%s)" % (self.__class__.__module__,
self.__class__.__qualname__,
self._hour, self._minute, s)
if self._tzinfo is not None:
assert s[-1:] == ")"
s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")"
if self._fold:
assert s[-1:] == ")"
s = s[:-1] + ", fold=1)"
return s
def isoformat(self, timespec='auto'):
"""Return the time formatted according to ISO.
The full format is 'HH:MM:SS.mmmmmm+zz:zz'. By default, the fractional
part is omitted if self.microsecond == 0.
The optional argument timespec specifies the number of additional
terms of the time to include. Valid options are 'auto', 'hours',
'minutes', 'seconds', 'milliseconds' and 'microseconds'.
"""
s = _format_time(self._hour, self._minute, self._second,
self._microsecond, timespec)
tz = self._tzstr()
if tz:
s += tz
return s
__str__ = isoformat
@classmethod
def fromisoformat(cls, time_string):
"""Construct a time from a string in one of the ISO 8601 formats."""
if not isinstance(time_string, str):
raise TypeError('fromisoformat: argument must be str')
# The spec actually requires that time-only ISO 8601 strings start with
# T, but the extended format allows this to be omitted as long as there
# is no ambiguity with date strings.
time_string = time_string.removeprefix('T')
try:
return cls(*_parse_isoformat_time(time_string))
except Exception:
raise ValueError(f'Invalid isoformat string: {time_string!r}')
def strftime(self, fmt):
"""Format using strftime(). The date part of the timestamp passed
to underlying strftime should not be used.
"""
# The year must be >= 1000 else Python's strftime implementation
# can raise a bogus exception.
timetuple = (1900, 1, 1,
self._hour, self._minute, self._second,
0, 1, -1)
return _wrap_strftime(self, fmt, timetuple)
def __format__(self, fmt):
if not isinstance(fmt, str):
raise TypeError("must be str, not %s" % type(fmt).__name__)
if len(fmt) != 0:
return self.strftime(fmt)
return str(self)
# Timezone functions
def utcoffset(self):
"""Return the timezone offset as timedelta, positive east of UTC
(negative west of UTC)."""
if self._tzinfo is None:
return None
offset = self._tzinfo.utcoffset(None)
_check_utc_offset("utcoffset", offset)
return offset
def tzname(self):
"""Return the timezone name.
Note that the name is 100% informational -- there's no requirement that
it mean anything in particular. For example, "GMT", "UTC", "-500",
"-5:00", "EDT", "US/Eastern", "America/New York" are all valid replies.
"""
if self._tzinfo is None:
return None
name = self._tzinfo.tzname(None)
_check_tzname(name)
return name
def dst(self):
"""Return 0 if DST is not in effect, or the DST offset (as timedelta
positive eastward) if DST is in effect.
This is purely informational; the DST offset has already been added to
the UTC offset returned by utcoffset() if applicable, so there's no
need to consult dst() unless you're interested in displaying the DST
info.
"""
if self._tzinfo is None:
return None
offset = self._tzinfo.dst(None)
_check_utc_offset("dst", offset)
return offset
def replace(self, hour=None, minute=None, second=None, microsecond=None,
tzinfo=True, *, fold=None):
"""Return a new time with new values for the specified fields."""
if hour is None:
hour = self.hour
if minute is None:
minute = self.minute
if second is None:
second = self.second
if microsecond is None:
microsecond = self.microsecond
if tzinfo is True:
tzinfo = self.tzinfo
if fold is None:
fold = self._fold
return type(self)(hour, minute, second, microsecond, tzinfo, fold=fold)
# Pickle support.
def _getstate(self, protocol=3):
us2, us3 = divmod(self._microsecond, 256)
us1, us2 = divmod(us2, 256)
h = self._hour
if self._fold and protocol > 3:
h += 128
basestate = bytes([h, self._minute, self._second,
us1, us2, us3])
if self._tzinfo is None:
return (basestate,)
else:
return (basestate, self._tzinfo)
def __setstate(self, string, tzinfo):
if tzinfo is not None and not isinstance(tzinfo, _tzinfo_class):
raise TypeError("bad tzinfo state arg")
h, self._minute, self._second, us1, us2, us3 = string
if h > 127:
self._fold = 1
self._hour = h - 128
else:
self._fold = 0
self._hour = h
self._microsecond = (((us1 << 8) | us2) << 8) | us3
self._tzinfo = tzinfo
def __reduce_ex__(self, protocol):
return (self.__class__, self._getstate(protocol))
def __reduce__(self):
return self.__reduce_ex__(2)
_time_class = time # so functions w/ args named "time" can get at the class
time.min = time(0, 0, 0)
time.max = time(23, 59, 59, 999999)
time.resolution = timedelta(microseconds=1)
class datetime(date):
"""datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]])
The year, month and day arguments are required. tzinfo may be None, or an
instance of a tzinfo subclass. The remaining arguments may be ints.
"""
__slots__ = date.__slots__ + time.__slots__
def __new__(cls, year, month=None, day=None, hour=0, minute=0, second=0,
microsecond=0, tzinfo=None, *, fold=0):
if (isinstance(year, (bytes, str)) and len(year) == 10 and
1 <= ord(year[2:3])&0x7F <= 12):
# Pickle support
if isinstance(year, str):
try:
year = bytes(year, 'latin1')
except UnicodeEncodeError:
# More informative error message.
raise ValueError(
"Failed to encode latin1 string when unpickling "
"a datetime object. "
"pickle.load(data, encoding='latin1') is assumed.")
self = object.__new__(cls)
self.__setstate(year, month)
self._hashcode = -1
return self
year, month, day = _check_date_fields(year, month, day)
hour, minute, second, microsecond, fold = _check_time_fields(
hour, minute, second, microsecond, fold)
_check_tzinfo_arg(tzinfo)
self = object.__new__(cls)
self._year = year
self._month = month
self._day = day
self._hour = hour
self._minute = minute
self._second = second
self._microsecond = microsecond
self._tzinfo = tzinfo
self._hashcode = -1
self._fold = fold
return self
# Read-only field accessors
@property
def hour(self):
"""hour (0-23)"""
return self._hour
@property
def minute(self):
"""minute (0-59)"""
return self._minute
@property
def second(self):
"""second (0-59)"""
return self._second
@property
def microsecond(self):
"""microsecond (0-999999)"""
return self._microsecond
@property
def tzinfo(self):
"""timezone info object"""
return self._tzinfo
@property
def fold(self):
return self._fold
@classmethod
def _fromtimestamp(cls, t, utc, tz):
"""Construct a datetime from a POSIX timestamp (like time.time()).
A timezone info object may be passed in as well.
"""
frac, t = _math.modf(t)
us = round(frac * 1e6)
if us >= 1000000:
t += 1
us -= 1000000
elif us < 0:
t -= 1
us += 1000000
converter = _time.gmtime if utc else _time.localtime
y, m, d, hh, mm, ss, weekday, jday, dst = converter(t)
ss = min(ss, 59) # clamp out leap seconds if the platform has them
result = cls(y, m, d, hh, mm, ss, us, tz)
if tz is None and not utc:
# As of version 2015f max fold in IANA database is
# 23 hours at 1969-09-30 13:00:00 in Kwajalein.
# Let's probe 24 hours in the past to detect a transition:
max_fold_seconds = 24 * 3600
# On Windows localtime_s throws an OSError for negative values,
# thus we can't perform fold detection for values of time less
# than the max time fold. See comments in _datetimemodule's
# version of this method for more details.
if t < max_fold_seconds and sys.platform.startswith("win"):
return result
y, m, d, hh, mm, ss = converter(t - max_fold_seconds)[:6]
probe1 = cls(y, m, d, hh, mm, ss, us, tz)
trans = result - probe1 - timedelta(0, max_fold_seconds)
if trans.days < 0:
y, m, d, hh, mm, ss = converter(t + trans // timedelta(0, 1))[:6]
probe2 = cls(y, m, d, hh, mm, ss, us, tz)
if probe2 == result:
result._fold = 1
elif tz is not None:
result = tz.fromutc(result)
return result
@classmethod
def fromtimestamp(cls, t, tz=None):
"""Construct a datetime from a POSIX timestamp (like time.time()).
A timezone info object may be passed in as well.
"""
_check_tzinfo_arg(tz)
return cls._fromtimestamp(t, tz is not None, tz)
@classmethod
def utcfromtimestamp(cls, t):
"""Construct a naive UTC datetime from a POSIX timestamp."""
return cls._fromtimestamp(t, True, None)
@classmethod
def now(cls, tz=None):
"Construct a datetime from time.time() and optional time zone info."
t = _time.time()
return cls.fromtimestamp(t, tz)
@classmethod
def utcnow(cls):
"Construct a UTC datetime from time.time()."
t = _time.time()
return cls.utcfromtimestamp(t)
@classmethod
def combine(cls, date, time, tzinfo=True):
"Construct a datetime from a given date and a given time."
if not isinstance(date, _date_class):
raise TypeError("date argument must be a date instance")
if not isinstance(time, _time_class):
raise TypeError("time argument must be a time instance")
if tzinfo is True:
tzinfo = time.tzinfo
return cls(date.year, date.month, date.day,
time.hour, time.minute, time.second, time.microsecond,
tzinfo, fold=time.fold)
@classmethod
def fromisoformat(cls, date_string):
"""Construct a datetime from a string in one of the ISO 8601 formats."""
if not isinstance(date_string, str):
raise TypeError('fromisoformat: argument must be str')
if len(date_string) < 7:
raise ValueError(f'Invalid isoformat string: {date_string!r}')
# Split this at the separator
try:
separator_location = _find_isoformat_datetime_separator(date_string)
dstr = date_string[0:separator_location]
tstr = date_string[(separator_location+1):]
date_components = _parse_isoformat_date(dstr)
except ValueError:
raise ValueError(
f'Invalid isoformat string: {date_string!r}') from None
if tstr:
try:
time_components = _parse_isoformat_time(tstr)
except ValueError:
raise ValueError(
f'Invalid isoformat string: {date_string!r}') from None
else:
time_components = [0, 0, 0, 0, None]
return cls(*(date_components + time_components))
def timetuple(self):
"Return local time tuple compatible with time.localtime()."
dst = self.dst()
if dst is None:
dst = -1
elif dst:
dst = 1
else:
dst = 0
return _build_struct_time(self.year, self.month, self.day,
self.hour, self.minute, self.second,
dst)
def _mktime(self):
"""Return integer POSIX timestamp."""
epoch = datetime(1970, 1, 1)
max_fold_seconds = 24 * 3600
t = (self - epoch) // timedelta(0, 1)
def local(u):
y, m, d, hh, mm, ss = _time.localtime(u)[:6]
return (datetime(y, m, d, hh, mm, ss) - epoch) // timedelta(0, 1)
# Our goal is to solve t = local(u) for u.
a = local(t) - t
u1 = t - a
t1 = local(u1)
if t1 == t:
# We found one solution, but it may not be the one we need.
# Look for an earlier solution (if `fold` is 0), or a
# later one (if `fold` is 1).
u2 = u1 + (-max_fold_seconds, max_fold_seconds)[self.fold]
b = local(u2) - u2
if a == b:
return u1
else:
b = t1 - u1
assert a != b
u2 = t - b
t2 = local(u2)
if t2 == t:
return u2
if t1 == t:
return u1
# We have found both offsets a and b, but neither t - a nor t - b is
# a solution. This means t is in the gap.
return (max, min)[self.fold](u1, u2)
def timestamp(self):
"Return POSIX timestamp as float"
if self._tzinfo is None:
s = self._mktime()
return s + self.microsecond / 1e6
else:
return (self - _EPOCH).total_seconds()
def utctimetuple(self):
"Return UTC time tuple compatible with time.gmtime()."
offset = self.utcoffset()
if offset:
self -= offset
y, m, d = self.year, self.month, self.day
hh, mm, ss = self.hour, self.minute, self.second
return _build_struct_time(y, m, d, hh, mm, ss, 0)
def date(self):
"Return the date part."
return date(self._year, self._month, self._day)
def time(self):
"Return the time part, with tzinfo None."
return time(self.hour, self.minute, self.second, self.microsecond, fold=self.fold)
def timetz(self):
"Return the time part, with same tzinfo."
return time(self.hour, self.minute, self.second, self.microsecond,
self._tzinfo, fold=self.fold)
def replace(self, year=None, month=None, day=None, hour=None,
minute=None, second=None, microsecond=None, tzinfo=True,
*, fold=None):
"""Return a new datetime with new values for the specified fields."""
if year is None:
year = self.year
if month is None:
month = self.month
if day is None:
day = self.day
if hour is None:
hour = self.hour
if minute is None:
minute = self.minute
if second is None:
second = self.second
if microsecond is None:
microsecond = self.microsecond
if tzinfo is True:
tzinfo = self.tzinfo
if fold is None:
fold = self.fold
return type(self)(year, month, day, hour, minute, second,
microsecond, tzinfo, fold=fold)
def _local_timezone(self):
if self.tzinfo is None:
ts = self._mktime()
else:
ts = (self - _EPOCH) // timedelta(seconds=1)
localtm = _time.localtime(ts)
local = datetime(*localtm[:6])
# Extract TZ data
gmtoff = localtm.tm_gmtoff
zone = localtm.tm_zone
return timezone(timedelta(seconds=gmtoff), zone)
def astimezone(self, tz=None):
if tz is None:
tz = self._local_timezone()
elif not isinstance(tz, tzinfo):
raise TypeError("tz argument must be an instance of tzinfo")
mytz = self.tzinfo
if mytz is None:
mytz = self._local_timezone()
myoffset = mytz.utcoffset(self)
else:
myoffset = mytz.utcoffset(self)
if myoffset is None:
mytz = self.replace(tzinfo=None)._local_timezone()
myoffset = mytz.utcoffset(self)
if tz is mytz:
return self
# Convert self to UTC, and attach the new time zone object.
utc = (self - myoffset).replace(tzinfo=tz)
# Convert from UTC to tz's local time.
return tz.fromutc(utc)
# Ways to produce a string.
def ctime(self):
"Return ctime() style string."
weekday = self.toordinal() % 7 or 7
return "%s %s %2d %02d:%02d:%02d %04d" % (
_DAYNAMES[weekday],
_MONTHNAMES[self._month],
self._day,
self._hour, self._minute, self._second,
self._year)
def isoformat(self, sep='T', timespec='auto'):
"""Return the time formatted according to ISO.
The full format looks like 'YYYY-MM-DD HH:MM:SS.mmmmmm'.
By default, the fractional part is omitted if self.microsecond == 0.
If self.tzinfo is not None, the UTC offset is also attached, giving
giving a full format of 'YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM'.
Optional argument sep specifies the separator between date and
time, default 'T'.
The optional argument timespec specifies the number of additional
terms of the time to include. Valid options are 'auto', 'hours',
'minutes', 'seconds', 'milliseconds' and 'microseconds'.
"""
s = ("%04d-%02d-%02d%c" % (self._year, self._month, self._day, sep) +
_format_time(self._hour, self._minute, self._second,
self._microsecond, timespec))
off = self.utcoffset()
tz = _format_offset(off)
if tz:
s += tz
return s
def __repr__(self):
"""Convert to formal string, for repr()."""
L = [self._year, self._month, self._day, # These are never zero
self._hour, self._minute, self._second, self._microsecond]
if L[-1] == 0:
del L[-1]
if L[-1] == 0:
del L[-1]
s = "%s.%s(%s)" % (self.__class__.__module__,
self.__class__.__qualname__,
", ".join(map(str, L)))
if self._tzinfo is not None:
assert s[-1:] == ")"
s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")"
if self._fold:
assert s[-1:] == ")"
s = s[:-1] + ", fold=1)"
return s
def __str__(self):
"Convert to string, for str()."
return self.isoformat(sep=' ')
@classmethod
def strptime(cls, date_string, format):
'string, format -> new datetime parsed from a string (like time.strptime()).'
import _strptime
return _strptime._strptime_datetime(cls, date_string, format)
def utcoffset(self):
"""Return the timezone offset as timedelta positive east of UTC (negative west of
UTC)."""
if self._tzinfo is None:
return None
offset = self._tzinfo.utcoffset(self)
_check_utc_offset("utcoffset", offset)
return offset
def tzname(self):
"""Return the timezone name.
Note that the name is 100% informational -- there's no requirement that
it mean anything in particular. For example, "GMT", "UTC", "-500",
"-5:00", "EDT", "US/Eastern", "America/New York" are all valid replies.
"""
if self._tzinfo is None:
return None
name = self._tzinfo.tzname(self)
_check_tzname(name)
return name
def dst(self):
"""Return 0 if DST is not in effect, or the DST offset (as timedelta
positive eastward) if DST is in effect.
This is purely informational; the DST offset has already been added to
the UTC offset returned by utcoffset() if applicable, so there's no
need to consult dst() unless you're interested in displaying the DST
info.
"""
if self._tzinfo is None:
return None
offset = self._tzinfo.dst(self)
_check_utc_offset("dst", offset)
return offset
# Comparisons of datetime objects with other.
def __eq__(self, other):
if isinstance(other, datetime):
return self._cmp(other, allow_mixed=True) == 0
elif not isinstance(other, date):
return NotImplemented
else:
return False
def __le__(self, other):
if isinstance(other, datetime):
return self._cmp(other) <= 0
elif not isinstance(other, date):
return NotImplemented
else:
_cmperror(self, other)
def __lt__(self, other):
if isinstance(other, datetime):
return self._cmp(other) < 0
elif not isinstance(other, date):
return NotImplemented
else:
_cmperror(self, other)
def __ge__(self, other):
if isinstance(other, datetime):
return self._cmp(other) >= 0
elif not isinstance(other, date):
return NotImplemented
else:
_cmperror(self, other)
def __gt__(self, other):
if isinstance(other, datetime):
return self._cmp(other) > 0
elif not isinstance(other, date):
return NotImplemented
else:
_cmperror(self, other)
def _cmp(self, other, allow_mixed=False):
assert isinstance(other, datetime)
mytz = self._tzinfo
ottz = other._tzinfo
myoff = otoff = None
if mytz is ottz:
base_compare = True
else:
myoff = self.utcoffset()
otoff = other.utcoffset()
# Assume that allow_mixed means that we are called from __eq__
if allow_mixed:
if myoff != self.replace(fold=not self.fold).utcoffset():
return 2
if otoff != other.replace(fold=not other.fold).utcoffset():
return 2
base_compare = myoff == otoff
if base_compare:
return _cmp((self._year, self._month, self._day,
self._hour, self._minute, self._second,
self._microsecond),
(other._year, other._month, other._day,
other._hour, other._minute, other._second,
other._microsecond))
if myoff is None or otoff is None:
if allow_mixed:
return 2 # arbitrary non-zero value
else:
raise TypeError("cannot compare naive and aware datetimes")
# XXX What follows could be done more efficiently...
diff = self - other # this will take offsets into account
if diff.days < 0:
return -1
return diff and 1 or 0
def __add__(self, other):
"Add a datetime and a timedelta."
if not isinstance(other, timedelta):
return NotImplemented
delta = timedelta(self.toordinal(),
hours=self._hour,
minutes=self._minute,
seconds=self._second,
microseconds=self._microsecond)
delta += other
hour, rem = divmod(delta.seconds, 3600)
minute, second = divmod(rem, 60)
if 0 < delta.days <= _MAXORDINAL:
return type(self).combine(date.fromordinal(delta.days),
time(hour, minute, second,
delta.microseconds,
tzinfo=self._tzinfo))
raise OverflowError("result out of range")
__radd__ = __add__
def __sub__(self, other):
"Subtract two datetimes, or a datetime and a timedelta."
if not isinstance(other, datetime):
if isinstance(other, timedelta):
return self + -other
return NotImplemented
days1 = self.toordinal()
days2 = other.toordinal()
secs1 = self._second + self._minute * 60 + self._hour * 3600
secs2 = other._second + other._minute * 60 + other._hour * 3600
base = timedelta(days1 - days2,
secs1 - secs2,
self._microsecond - other._microsecond)
if self._tzinfo is other._tzinfo:
return base
myoff = self.utcoffset()
otoff = other.utcoffset()
if myoff == otoff:
return base
if myoff is None or otoff is None:
raise TypeError("cannot mix naive and timezone-aware time")
return base + otoff - myoff
def __hash__(self):
if self._hashcode == -1:
if self.fold:
t = self.replace(fold=0)
else:
t = self
tzoff = t.utcoffset()
if tzoff is None:
self._hashcode = hash(t._getstate()[0])
else:
days = _ymd2ord(self.year, self.month, self.day)
seconds = self.hour * 3600 + self.minute * 60 + self.second
self._hashcode = hash(timedelta(days, seconds, self.microsecond) - tzoff)
return self._hashcode
# Pickle support.
def _getstate(self, protocol=3):
yhi, ylo = divmod(self._year, 256)
us2, us3 = divmod(self._microsecond, 256)
us1, us2 = divmod(us2, 256)
m = self._month
if self._fold and protocol > 3:
m += 128
basestate = bytes([yhi, ylo, m, self._day,
self._hour, self._minute, self._second,
us1, us2, us3])
if self._tzinfo is None:
return (basestate,)
else:
return (basestate, self._tzinfo)
def __setstate(self, string, tzinfo):
if tzinfo is not None and not isinstance(tzinfo, _tzinfo_class):
raise TypeError("bad tzinfo state arg")
(yhi, ylo, m, self._day, self._hour,
self._minute, self._second, us1, us2, us3) = string
if m > 127:
self._fold = 1
self._month = m - 128
else:
self._fold = 0
self._month = m
self._year = yhi * 256 + ylo
self._microsecond = (((us1 << 8) | us2) << 8) | us3
self._tzinfo = tzinfo
def __reduce_ex__(self, protocol):
return (self.__class__, self._getstate(protocol))
def __reduce__(self):
return self.__reduce_ex__(2)
datetime.min = datetime(1, 1, 1)
datetime.max = datetime(9999, 12, 31, 23, 59, 59, 999999)
datetime.resolution = timedelta(microseconds=1)
def _isoweek1monday(year):
# Helper to calculate the day number of the Monday starting week 1
# XXX This could be done more efficiently
THURSDAY = 3
firstday = _ymd2ord(year, 1, 1)
firstweekday = (firstday + 6) % 7 # See weekday() above
week1monday = firstday - firstweekday
if firstweekday > THURSDAY:
week1monday += 7
return week1monday
class timezone(tzinfo):
__slots__ = '_offset', '_name'
# Sentinel value to disallow None
_Omitted = object()
def __new__(cls, offset, name=_Omitted):
if not isinstance(offset, timedelta):
raise TypeError("offset must be a timedelta")
if name is cls._Omitted:
if not offset:
return cls.utc
name = None
elif not isinstance(name, str):
raise TypeError("name must be a string")
if not cls._minoffset <= offset <= cls._maxoffset:
raise ValueError("offset must be a timedelta "
"strictly between -timedelta(hours=24) and "
"timedelta(hours=24).")
return cls._create(offset, name)
@classmethod
def _create(cls, offset, name=None):
self = tzinfo.__new__(cls)
self._offset = offset
self._name = name
return self
def __getinitargs__(self):
"""pickle support"""
if self._name is None:
return (self._offset,)
return (self._offset, self._name)
def __eq__(self, other):
if isinstance(other, timezone):
return self._offset == other._offset
return NotImplemented
def __hash__(self):
return hash(self._offset)
def __repr__(self):
"""Convert to formal string, for repr().
>>> tz = timezone.utc
>>> repr(tz)
'datetime.timezone.utc'
>>> tz = timezone(timedelta(hours=-5), 'EST')
>>> repr(tz)
"datetime.timezone(datetime.timedelta(-1, 68400), 'EST')"
"""
if self is self.utc:
return 'datetime.timezone.utc'
if self._name is None:
return "%s.%s(%r)" % (self.__class__.__module__,
self.__class__.__qualname__,
self._offset)
return "%s.%s(%r, %r)" % (self.__class__.__module__,
self.__class__.__qualname__,
self._offset, self._name)
def __str__(self):
return self.tzname(None)
def utcoffset(self, dt):
if isinstance(dt, datetime) or dt is None:
return self._offset
raise TypeError("utcoffset() argument must be a datetime instance"
" or None")
def tzname(self, dt):
if isinstance(dt, datetime) or dt is None:
if self._name is None:
return self._name_from_offset(self._offset)
return self._name
raise TypeError("tzname() argument must be a datetime instance"
" or None")
def dst(self, dt):
if isinstance(dt, datetime) or dt is None:
return None
raise TypeError("dst() argument must be a datetime instance"
" or None")
def fromutc(self, dt):
if isinstance(dt, datetime):
if dt.tzinfo is not self:
raise ValueError("fromutc: dt.tzinfo "
"is not self")
return dt + self._offset
raise TypeError("fromutc() argument must be a datetime instance"
" or None")
_maxoffset = timedelta(hours=24, microseconds=-1)
_minoffset = -_maxoffset
@staticmethod
def _name_from_offset(delta):
if not delta:
return 'UTC'
if delta < timedelta(0):
sign = '-'
delta = -delta
else:
sign = '+'
hours, rest = divmod(delta, timedelta(hours=1))
minutes, rest = divmod(rest, timedelta(minutes=1))
seconds = rest.seconds
microseconds = rest.microseconds
if microseconds:
return (f'UTC{sign}{hours:02d}:{minutes:02d}:{seconds:02d}'
f'.{microseconds:06d}')
if seconds:
return f'UTC{sign}{hours:02d}:{minutes:02d}:{seconds:02d}'
return f'UTC{sign}{hours:02d}:{minutes:02d}'
UTC = timezone.utc = timezone._create(timedelta(0))
# bpo-37642: These attributes are rounded to the nearest minute for backwards
# compatibility, even though the constructor will accept a wider range of
# values. This may change in the future.
timezone.min = timezone._create(-timedelta(hours=23, minutes=59))
timezone.max = timezone._create(timedelta(hours=23, minutes=59))
_EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
# Some time zone algebra. For a datetime x, let
# x.n = x stripped of its timezone -- its naive time.
# x.o = x.utcoffset(), and assuming that doesn't raise an exception or
# return None
# x.d = x.dst(), and assuming that doesn't raise an exception or
# return None
# x.s = x's standard offset, x.o - x.d
#
# Now some derived rules, where k is a duration (timedelta).
#
# 1. x.o = x.s + x.d
# This follows from the definition of x.s.
#
# 2. If x and y have the same tzinfo member, x.s = y.s.
# This is actually a requirement, an assumption we need to make about
# sane tzinfo classes.
#
# 3. The naive UTC time corresponding to x is x.n - x.o.
# This is again a requirement for a sane tzinfo class.
#
# 4. (x+k).s = x.s
# This follows from #2, and that datetime.timetz+timedelta preserves tzinfo.
#
# 5. (x+k).n = x.n + k
# Again follows from how arithmetic is defined.
#
# Now we can explain tz.fromutc(x). Let's assume it's an interesting case
# (meaning that the various tzinfo methods exist, and don't blow up or return
# None when called).
#
# The function wants to return a datetime y with timezone tz, equivalent to x.
# x is already in UTC.
#
# By #3, we want
#
# y.n - y.o = x.n [1]
#
# The algorithm starts by attaching tz to x.n, and calling that y. So
# x.n = y.n at the start. Then it wants to add a duration k to y, so that [1]
# becomes true; in effect, we want to solve [2] for k:
#
# (y+k).n - (y+k).o = x.n [2]
#
# By #1, this is the same as
#
# (y+k).n - ((y+k).s + (y+k).d) = x.n [3]
#
# By #5, (y+k).n = y.n + k, which equals x.n + k because x.n=y.n at the start.
# Substituting that into [3],
#
# x.n + k - (y+k).s - (y+k).d = x.n; the x.n terms cancel, leaving
# k - (y+k).s - (y+k).d = 0; rearranging,
# k = (y+k).s - (y+k).d; by #4, (y+k).s == y.s, so
# k = y.s - (y+k).d
#
# On the RHS, (y+k).d can't be computed directly, but y.s can be, and we
# approximate k by ignoring the (y+k).d term at first. Note that k can't be
# very large, since all offset-returning methods return a duration of magnitude
# less than 24 hours. For that reason, if y is firmly in std time, (y+k).d must
# be 0, so ignoring it has no consequence then.
#
# In any case, the new value is
#
# z = y + y.s [4]
#
# It's helpful to step back at look at [4] from a higher level: it's simply
# mapping from UTC to tz's standard time.
#
# At this point, if
#
# z.n - z.o = x.n [5]
#
# we have an equivalent time, and are almost done. The insecurity here is
# at the start of daylight time. Picture US Eastern for concreteness. The wall
# time jumps from 1:59 to 3:00, and wall hours of the form 2:MM don't make good
# sense then. The docs ask that an Eastern tzinfo class consider such a time to
# be EDT (because it's "after 2"), which is a redundant spelling of 1:MM EST
# on the day DST starts. We want to return the 1:MM EST spelling because that's
# the only spelling that makes sense on the local wall clock.
#
# In fact, if [5] holds at this point, we do have the standard-time spelling,
# but that takes a bit of proof. We first prove a stronger result. What's the
# difference between the LHS and RHS of [5]? Let
#
# diff = x.n - (z.n - z.o) [6]
#
# Now
# z.n = by [4]
# (y + y.s).n = by #5
# y.n + y.s = since y.n = x.n
# x.n + y.s = since z and y are have the same tzinfo member,
# y.s = z.s by #2
# x.n + z.s
#
# Plugging that back into [6] gives
#
# diff =
# x.n - ((x.n + z.s) - z.o) = expanding
# x.n - x.n - z.s + z.o = cancelling
# - z.s + z.o = by #2
# z.d
#
# So diff = z.d.
#
# If [5] is true now, diff = 0, so z.d = 0 too, and we have the standard-time
# spelling we wanted in the endcase described above. We're done. Contrarily,
# if z.d = 0, then we have a UTC equivalent, and are also done.
#
# If [5] is not true now, diff = z.d != 0, and z.d is the offset we need to
# add to z (in effect, z is in tz's standard time, and we need to shift the
# local clock into tz's daylight time).
#
# Let
#
# z' = z + z.d = z + diff [7]
#
# and we can again ask whether
#
# z'.n - z'.o = x.n [8]
#
# If so, we're done. If not, the tzinfo class is insane, according to the
# assumptions we've made. This also requires a bit of proof. As before, let's
# compute the difference between the LHS and RHS of [8] (and skipping some of
# the justifications for the kinds of substitutions we've done several times
# already):
#
# diff' = x.n - (z'.n - z'.o) = replacing z'.n via [7]
# x.n - (z.n + diff - z'.o) = replacing diff via [6]
# x.n - (z.n + x.n - (z.n - z.o) - z'.o) =
# x.n - z.n - x.n + z.n - z.o + z'.o = cancel x.n
# - z.n + z.n - z.o + z'.o = cancel z.n
# - z.o + z'.o = #1 twice
# -z.s - z.d + z'.s + z'.d = z and z' have same tzinfo
# z'.d - z.d
#
# So z' is UTC-equivalent to x iff z'.d = z.d at this point. If they are equal,
# we've found the UTC-equivalent so are done. In fact, we stop with [7] and
# return z', not bothering to compute z'.d.
#
# How could z.d and z'd differ? z' = z + z.d [7], so merely moving z' by
# a dst() offset, and starting *from* a time already in DST (we know z.d != 0),
# would have to change the result dst() returns: we start in DST, and moving
# a little further into it takes us out of DST.
#
# There isn't a sane case where this can happen. The closest it gets is at
# the end of DST, where there's an hour in UTC with no spelling in a hybrid
# tzinfo class. In US Eastern, that's 5:MM UTC = 0:MM EST = 1:MM EDT. During
# that hour, on an Eastern clock 1:MM is taken as being in standard time (6:MM
# UTC) because the docs insist on that, but 0:MM is taken as being in daylight
# time (4:MM UTC). There is no local time mapping to 5:MM UTC. The local
# clock jumps from 1:59 back to 1:00 again, and repeats the 1:MM hour in
# standard time. Since that's what the local clock *does*, we want to map both
# UTC hours 5:MM and 6:MM to 1:MM Eastern. The result is ambiguous
# in local time, but so it goes -- it's the way the local clock works.
#
# When x = 5:MM UTC is the input to this algorithm, x.o=0, y.o=-5 and y.d=0,
# so z=0:MM. z.d=60 (minutes) then, so [5] doesn't hold and we keep going.
# z' = z + z.d = 1:MM then, and z'.d=0, and z'.d - z.d = -60 != 0 so [8]
# (correctly) concludes that z' is not UTC-equivalent to x.
#
# Because we know z.d said z was in daylight time (else [5] would have held and
# we would have stopped then), and we know z.d != z'.d (else [8] would have held
# and we have stopped then), and there are only 2 possible values dst() can
# return in Eastern, it follows that z'.d must be 0 (which it is in the example,
# but the reasoning doesn't depend on the example -- it depends on there being
# two possible dst() outcomes, one zero and the other non-zero). Therefore
# z' must be in standard time, and is the spelling we want in this case.
#
# Note again that z' is not UTC-equivalent as far as the hybrid tzinfo class is
# concerned (because it takes z' as being in standard time rather than the
# daylight time we intend here), but returning it gives the real-life "local
# clock repeats an hour" behavior when mapping the "unspellable" UTC hour into
# tz.
#
# When the input is 6:MM, z=1:MM and z.d=0, and we stop at once, again with
# the 1:MM standard time spelling we want.
#
# So how can this break? One of the assumptions must be violated. Two
# possibilities:
#
# 1) [2] effectively says that y.s is invariant across all y belong to a given
# time zone. This isn't true if, for political reasons or continental drift,
# a region decides to change its base offset from UTC.
#
# 2) There may be versions of "double daylight" time where the tail end of
# the analysis gives up a step too early. I haven't thought about that
# enough to say.
#
# In any case, it's clear that the default fromutc() is strong enough to handle
# "almost all" time zones: so long as the standard offset is invariant, it
# doesn't matter if daylight time transition points change from year to year, or
# if daylight time is skipped in some years; it doesn't matter how large or
# small dst() may get within its bounds; and it doesn't even matter if some
# perverse time zone returns a negative dst()). So a breaking case must be
# pretty bizarre, and a tzinfo subclass can override fromutc() if it is.
try:
from _datetime import *
except ImportError:
pass
else:
# Clean up unused names
del (_DAYNAMES, _DAYS_BEFORE_MONTH, _DAYS_IN_MONTH, _DI100Y, _DI400Y,
_DI4Y, _EPOCH, _MAXORDINAL, _MONTHNAMES, _build_struct_time,
_check_date_fields, _check_time_fields,
_check_tzinfo_arg, _check_tzname, _check_utc_offset, _cmp, _cmperror,
_date_class, _days_before_month, _days_before_year, _days_in_month,
_format_time, _format_offset, _index, _is_leap, _isoweek1monday, _math,
_ord2ymd, _time, _time_class, _tzinfo_class, _wrap_strftime, _ymd2ord,
_divide_and_round, _parse_isoformat_date, _parse_isoformat_time,
_parse_hh_mm_ss_ff, _IsoCalendarDate, _isoweek_to_gregorian,
_find_isoformat_datetime_separator, _FRACTION_CORRECTION,
_is_ascii_digit)
# XXX Since import * above excludes names that start with _,
# docstring does not get overwritten. In the future, it may be
# appropriate to maintain a single module level docstring and
# remove the following line.
from _datetime import __doc__
fractions.py
# Originally contributed by Sjoerd Mullender.
# Significantly modified by Jeffrey Yasskin <jyasskin at gmail.com>.
"""Fraction, infinite-precision, rational numbers."""
from decimal import Decimal
import functools
import math
import numbers
import operator
import re
import sys
__all__ = ['Fraction']
# Constants related to the hash implementation; hash(x) is based
# on the reduction of x modulo the prime _PyHASH_MODULUS.
_PyHASH_MODULUS = sys.hash_info.modulus
# Value to be used for rationals that reduce to infinity modulo
# _PyHASH_MODULUS.
_PyHASH_INF = sys.hash_info.inf
@functools.lru_cache(maxsize = 1 << 14)
def _hash_algorithm(numerator, denominator):
# To make sure that the hash of a Fraction agrees with the hash
# of a numerically equal integer, float or Decimal instance, we
# follow the rules for numeric hashes outlined in the
# documentation. (See library docs, 'Built-in Types').
try:
dinv = pow(denominator, -1, _PyHASH_MODULUS)
except ValueError:
# ValueError means there is no modular inverse.
hash_ = _PyHASH_INF
else:
# The general algorithm now specifies that the absolute value of
# the hash is
# (|N| * dinv) % P
# where N is self._numerator and P is _PyHASH_MODULUS. That's
# optimized here in two ways: first, for a non-negative int i,
# hash(i) == i % P, but the int hash implementation doesn't need
# to divide, and is faster than doing % P explicitly. So we do
# hash(|N| * dinv)
# instead. Second, N is unbounded, so its product with dinv may
# be arbitrarily expensive to compute. The final answer is the
# same if we use the bounded |N| % P instead, which can again
# be done with an int hash() call. If 0 <= i < P, hash(i) == i,
# so this nested hash() call wastes a bit of time making a
# redundant copy when |N| < P, but can save an arbitrarily large
# amount of computation for large |N|.
hash_ = hash(hash(abs(numerator)) * dinv)
result = hash_ if numerator >= 0 else -hash_
return -2 if result == -1 else result
_RATIONAL_FORMAT = re.compile(r"""
\A\s* # optional whitespace at the start,
(?P<sign>[-+]?) # an optional sign, then
(?=\d|\.\d) # lookahead for digit or .digit
(?P<num>\d*|\d+(_\d+)*) # numerator (possibly empty)
(?: # followed by
(?:\s*/\s*(?P<denom>\d+(_\d+)*))? # an optional denominator
| # or
(?:\.(?P<decimal>\d*|\d+(_\d+)*))? # an optional fractional part
(?:E(?P<exp>[-+]?\d+(_\d+)*))? # and optional exponent
)
\s*\Z # and optional whitespace to finish
""", re.VERBOSE | re.IGNORECASE)
# Helpers for formatting
def _round_to_exponent(n, d, exponent, no_neg_zero=False):
"""Round a rational number to the nearest multiple of a given power of 10.
Rounds the rational number n/d to the nearest integer multiple of
10**exponent, rounding to the nearest even integer multiple in the case of
a tie. Returns a pair (sign: bool, significand: int) representing the
rounded value (-1)**sign * significand * 10**exponent.
If no_neg_zero is true, then the returned sign will always be False when
the significand is zero. Otherwise, the sign reflects the sign of the
input.
d must be positive, but n and d need not be relatively prime.
"""
if exponent >= 0:
d *= 10**exponent
else:
n *= 10**-exponent
# The divmod quotient is correct for round-ties-towards-positive-infinity;
# In the case of a tie, we zero out the least significant bit of q.
q, r = divmod(n + (d >> 1), d)
if r == 0 and d & 1 == 0:
q &= -2
sign = q < 0 if no_neg_zero else n < 0
return sign, abs(q)
def _round_to_figures(n, d, figures):
"""Round a rational number to a given number of significant figures.
Rounds the rational number n/d to the given number of significant figures
using the round-ties-to-even rule, and returns a triple
(sign: bool, significand: int, exponent: int) representing the rounded
value (-1)**sign * significand * 10**exponent.
In the special case where n = 0, returns a significand of zero and
an exponent of 1 - figures, for compatibility with formatting.
Otherwise, the returned significand satisfies
10**(figures - 1) <= significand < 10**figures.
d must be positive, but n and d need not be relatively prime.
figures must be positive.
"""
# Special case for n == 0.
if n == 0:
return False, 0, 1 - figures
# Find integer m satisfying 10**(m - 1) <= abs(n)/d <= 10**m. (If abs(n)/d
# is a power of 10, either of the two possible values for m is fine.)
str_n, str_d = str(abs(n)), str(d)
m = len(str_n) - len(str_d) + (str_d <= str_n)
# Round to a multiple of 10**(m - figures). The significand we get
# satisfies 10**(figures - 1) <= significand <= 10**figures.
exponent = m - figures
sign, significand = _round_to_exponent(n, d, exponent)
# Adjust in the case where significand == 10**figures, to ensure that
# 10**(figures - 1) <= significand < 10**figures.
if len(str(significand)) == figures + 1:
significand //= 10
exponent += 1
return sign, significand, exponent
# Pattern for matching float-style format specifications;
# supports 'e', 'E', 'f', 'F', 'g', 'G' and '%' presentation types.
_FLOAT_FORMAT_SPECIFICATION_MATCHER = re.compile(r"""
(?:
(?P<fill>.)?
(?P<align>[<>=^])
)?
(?P<sign>[-+ ]?)
(?P<no_neg_zero>z)?
(?P<alt>\#)?
# A '0' that's *not* followed by another digit is parsed as a minimum width
# rather than a zeropad flag.
(?P<zeropad>0(?=[0-9]))?
(?P<minimumwidth>0|[1-9][0-9]*)?
(?P<thousands_sep>[,_])?
(?:\.(?P<precision>0|[1-9][0-9]*))?
(?P<presentation_type>[eEfFgG%])
""", re.DOTALL | re.VERBOSE).fullmatch
class Fraction(numbers.Rational):
"""This class implements rational numbers.
In the two-argument form of the constructor, Fraction(8, 6) will
produce a rational number equivalent to 4/3. Both arguments must
be Rational. The numerator defaults to 0 and the denominator
defaults to 1 so that Fraction(3) == 3 and Fraction() == 0.
Fractions can also be constructed from:
- numeric strings similar to those accepted by the
float constructor (for example, '-2.3' or '1e10')
- strings of the form '123/456'
- float and Decimal instances
- other Rational instances (including integers)
"""
__slots__ = ('_numerator', '_denominator')
# We're immutable, so use __new__ not __init__
def __new__(cls, numerator=0, denominator=None):
"""Constructs a Rational.
Takes a string like '3/2' or '1.5', another Rational instance, a
numerator/denominator pair, or a float.
Examples
--------
>>> Fraction(10, -8)
Fraction(-5, 4)
>>> Fraction(Fraction(1, 7), 5)
Fraction(1, 35)
>>> Fraction(Fraction(1, 7), Fraction(2, 3))
Fraction(3, 14)
>>> Fraction('314')
Fraction(314, 1)
>>> Fraction('-35/4')
Fraction(-35, 4)
>>> Fraction('3.1415') # conversion from numeric string
Fraction(6283, 2000)
>>> Fraction('-47e-2') # string may include a decimal exponent
Fraction(-47, 100)
>>> Fraction(1.47) # direct construction from float (exact conversion)
Fraction(6620291452234629, 4503599627370496)
>>> Fraction(2.25)
Fraction(9, 4)
>>> Fraction(Decimal('1.47'))
Fraction(147, 100)
"""
self = super(Fraction, cls).__new__(cls)
if denominator is None:
if type(numerator) is int:
self._numerator = numerator
self._denominator = 1
return self
elif isinstance(numerator, numbers.Rational):
self._numerator = numerator.numerator
self._denominator = numerator.denominator
return self
elif isinstance(numerator, (float, Decimal)):
# Exact conversion
self._numerator, self._denominator = numerator.as_integer_ratio()
return self
elif isinstance(numerator, str):
# Handle construction from strings.
m = _RATIONAL_FORMAT.match(numerator)
if m is None:
raise ValueError('Invalid literal for Fraction: %r' %
numerator)
numerator = int(m.group('num') or '0')
denom = m.group('denom')
if denom:
denominator = int(denom)
else:
denominator = 1
decimal = m.group('decimal')
if decimal:
decimal = decimal.replace('_', '')
scale = 10**len(decimal)
numerator = numerator * scale + int(decimal)
denominator *= scale
exp = m.group('exp')
if exp:
exp = int(exp)
if exp >= 0:
numerator *= 10**exp
else:
denominator *= 10**-exp
if m.group('sign') == '-':
numerator = -numerator
else:
raise TypeError("argument should be a string "
"or a Rational instance")
elif type(numerator) is int is type(denominator):
pass # *very* normal case
elif (isinstance(numerator, numbers.Rational) and
isinstance(denominator, numbers.Rational)):
numerator, denominator = (
numerator.numerator * denominator.denominator,
denominator.numerator * numerator.denominator
)
else:
raise TypeError("both arguments should be "
"Rational instances")
if denominator == 0:
raise ZeroDivisionError('Fraction(%s, 0)' % numerator)
g = math.gcd(numerator, denominator)
if denominator < 0:
g = -g
numerator //= g
denominator //= g
self._numerator = numerator
self._denominator = denominator
return self
@classmethod
def from_float(cls, f):
"""Converts a finite float to a rational number, exactly.
Beware that Fraction.from_float(0.3) != Fraction(3, 10).
"""
if isinstance(f, numbers.Integral):
return cls(f)
elif not isinstance(f, float):
raise TypeError("%s.from_float() only takes floats, not %r (%s)" %
(cls.__name__, f, type(f).__name__))
return cls._from_coprime_ints(*f.as_integer_ratio())
@classmethod
def from_decimal(cls, dec):
"""Converts a finite Decimal instance to a rational number, exactly."""
from decimal import Decimal
if isinstance(dec, numbers.Integral):
dec = Decimal(int(dec))
elif not isinstance(dec, Decimal):
raise TypeError(
"%s.from_decimal() only takes Decimals, not %r (%s)" %
(cls.__name__, dec, type(dec).__name__))
return cls._from_coprime_ints(*dec.as_integer_ratio())
@classmethod
def _from_coprime_ints(cls, numerator, denominator, /):
"""Convert a pair of ints to a rational number, for internal use.
The ratio of integers should be in lowest terms and the denominator
should be positive.
"""
obj = super(Fraction, cls).__new__(cls)
obj._numerator = numerator
obj._denominator = denominator
return obj
def is_integer(self):
"""Return True if the Fraction is an integer."""
return self._denominator == 1
def as_integer_ratio(self):
"""Return a pair of integers, whose ratio is equal to the original Fraction.
The ratio is in lowest terms and has a positive denominator.
"""
return (self._numerator, self._denominator)
def limit_denominator(self, max_denominator=1000000):
"""Closest Fraction to self with denominator at most max_denominator.
>>> Fraction('3.141592653589793').limit_denominator(10)
Fraction(22, 7)
>>> Fraction('3.141592653589793').limit_denominator(100)
Fraction(311, 99)
>>> Fraction(4321, 8765).limit_denominator(10000)
Fraction(4321, 8765)
"""
# Algorithm notes: For any real number x, define a *best upper
# approximation* to x to be a rational number p/q such that:
#
# (1) p/q >= x, and
# (2) if p/q > r/s >= x then s > q, for any rational r/s.
#
# Define *best lower approximation* similarly. Then it can be
# proved that a rational number is a best upper or lower
# approximation to x if, and only if, it is a convergent or
# semiconvergent of the (unique shortest) continued fraction
# associated to x.
#
# To find a best rational approximation with denominator <= M,
# we find the best upper and lower approximations with
# denominator <= M and take whichever of these is closer to x.
# In the event of a tie, the bound with smaller denominator is
# chosen. If both denominators are equal (which can happen
# only when max_denominator == 1 and self is midway between
# two integers) the lower bound---i.e., the floor of self, is
# taken.
if max_denominator < 1:
raise ValueError("max_denominator should be at least 1")
if self._denominator <= max_denominator:
return Fraction(self)
p0, q0, p1, q1 = 0, 1, 1, 0
n, d = self._numerator, self._denominator
while True:
a = n//d
q2 = q0+a*q1
if q2 > max_denominator:
break
p0, q0, p1, q1 = p1, q1, p0+a*p1, q2
n, d = d, n-a*d
k = (max_denominator-q0)//q1
# Determine which of the candidates (p0+k*p1)/(q0+k*q1) and p1/q1 is
# closer to self. The distance between them is 1/(q1*(q0+k*q1)), while
# the distance from p1/q1 to self is d/(q1*self._denominator). So we
# need to compare 2*(q0+k*q1) with self._denominator/d.
if 2*d*(q0+k*q1) <= self._denominator:
return Fraction._from_coprime_ints(p1, q1)
else:
return Fraction._from_coprime_ints(p0+k*p1, q0+k*q1)
@property
def numerator(a):
return a._numerator
@property
def denominator(a):
return a._denominator
def __repr__(self):
"""repr(self)"""
return '%s(%s, %s)' % (self.__class__.__name__,
self._numerator, self._denominator)
def __str__(self):
"""str(self)"""
if self._denominator == 1:
return str(self._numerator)
else:
return '%s/%s' % (self._numerator, self._denominator)
def __format__(self, format_spec, /):
"""Format this fraction according to the given format specification."""
# Backwards compatiblility with existing formatting.
if not format_spec:
return str(self)
# Validate and parse the format specifier.
match = _FLOAT_FORMAT_SPECIFICATION_MATCHER(format_spec)
if match is None:
raise ValueError(
f"Invalid format specifier {format_spec!r} "
f"for object of type {type(self).__name__!r}"
)
elif match["align"] is not None and match["zeropad"] is not None:
# Avoid the temptation to guess.
raise ValueError(
f"Invalid format specifier {format_spec!r} "
f"for object of type {type(self).__name__!r}; "
"can't use explicit alignment when zero-padding"
)
fill = match["fill"] or " "
align = match["align"] or ">"
pos_sign = "" if match["sign"] == "-" else match["sign"]
no_neg_zero = bool(match["no_neg_zero"])
alternate_form = bool(match["alt"])
zeropad = bool(match["zeropad"])
minimumwidth = int(match["minimumwidth"] or "0")
thousands_sep = match["thousands_sep"]
precision = int(match["precision"] or "6")
presentation_type = match["presentation_type"]
trim_zeros = presentation_type in "gG" and not alternate_form
trim_point = not alternate_form
exponent_indicator = "E" if presentation_type in "EFG" else "e"
# Round to get the digits we need, figure out where to place the point,
# and decide whether to use scientific notation. 'point_pos' is the
# relative to the _end_ of the digit string: that is, it's the number
# of digits that should follow the point.
if presentation_type in "fF%":
exponent = -precision
if presentation_type == "%":
exponent -= 2
negative, significand = _round_to_exponent(
self._numerator, self._denominator, exponent, no_neg_zero)
scientific = False
point_pos = precision
else: # presentation_type in "eEgG"
figures = (
max(precision, 1)
if presentation_type in "gG"
else precision + 1
)
negative, significand, exponent = _round_to_figures(
self._numerator, self._denominator, figures)
scientific = (
presentation_type in "eE"
or exponent > 0
or exponent + figures <= -4
)
point_pos = figures - 1 if scientific else -exponent
# Get the suffix - the part following the digits, if any.
if presentation_type == "%":
suffix = "%"
elif scientific:
suffix = f"{exponent_indicator}{exponent + point_pos:+03d}"
else:
suffix = ""
# String of output digits, padded sufficiently with zeros on the left
# so that we'll have at least one digit before the decimal point.
digits = f"{significand:0{point_pos + 1}d}"
# Before padding, the output has the form f"{sign}{leading}{trailing}",
# where `leading` includes thousands separators if necessary and
# `trailing` includes the decimal separator where appropriate.
sign = "-" if negative else pos_sign
leading = digits[: len(digits) - point_pos]
frac_part = digits[len(digits) - point_pos :]
if trim_zeros:
frac_part = frac_part.rstrip("0")
separator = "" if trim_point and not frac_part else "."
trailing = separator + frac_part + suffix
# Do zero padding if required.
if zeropad:
min_leading = minimumwidth - len(sign) - len(trailing)
# When adding thousands separators, they'll be added to the
# zero-padded portion too, so we need to compensate.
leading = leading.zfill(
3 * min_leading // 4 + 1 if thousands_sep else min_leading
)
# Insert thousands separators if required.
if thousands_sep:
first_pos = 1 + (len(leading) - 1) % 3
leading = leading[:first_pos] + "".join(
thousands_sep + leading[pos : pos + 3]
for pos in range(first_pos, len(leading), 3)
)
# We now have a sign and a body. Pad with fill character if necessary
# and return.
body = leading + trailing
padding = fill * (minimumwidth - len(sign) - len(body))
if align == ">":
return padding + sign + body
elif align == "<":
return sign + body + padding
elif align == "^":
half = len(padding) // 2
return padding[:half] + sign + body + padding[half:]
else: # align == "="
return sign + padding + body
def _operator_fallbacks(monomorphic_operator, fallback_operator):
"""Generates forward and reverse operators given a purely-rational
operator and a function from the operator module.
Use this like:
__op__, __rop__ = _operator_fallbacks(just_rational_op, operator.op)
In general, we want to implement the arithmetic operations so
that mixed-mode operations either call an implementation whose
author knew about the types of both arguments, or convert both
to the nearest built in type and do the operation there. In
Fraction, that means that we define __add__ and __radd__ as:
def __add__(self, other):
# Both types have numerators/denominator attributes,
# so do the operation directly
if isinstance(other, (int, Fraction)):
return Fraction(self.numerator * other.denominator +
other.numerator * self.denominator,
self.denominator * other.denominator)
# float and complex don't have those operations, but we
# know about those types, so special case them.
elif isinstance(other, float):
return float(self) + other
elif isinstance(other, complex):
return complex(self) + other
# Let the other type take over.
return NotImplemented
def __radd__(self, other):
# radd handles more types than add because there's
# nothing left to fall back to.
if isinstance(other, numbers.Rational):
return Fraction(self.numerator * other.denominator +
other.numerator * self.denominator,
self.denominator * other.denominator)
elif isinstance(other, Real):
return float(other) + float(self)
elif isinstance(other, Complex):
return complex(other) + complex(self)
return NotImplemented
There are 5 different cases for a mixed-type addition on
Fraction. I'll refer to all of the above code that doesn't
refer to Fraction, float, or complex as "boilerplate". 'r'
will be an instance of Fraction, which is a subtype of
Rational (r : Fraction <: Rational), and b : B <:
Complex. The first three involve 'r + b':
1. If B <: Fraction, int, float, or complex, we handle
that specially, and all is well.
2. If Fraction falls back to the boilerplate code, and it
were to return a value from __add__, we'd miss the
possibility that B defines a more intelligent __radd__,
so the boilerplate should return NotImplemented from
__add__. In particular, we don't handle Rational
here, even though we could get an exact answer, in case
the other type wants to do something special.
3. If B <: Fraction, Python tries B.__radd__ before
Fraction.__add__. This is ok, because it was
implemented with knowledge of Fraction, so it can
handle those instances before delegating to Real or
Complex.
The next two situations describe 'b + r'. We assume that b
didn't know about Fraction in its implementation, and that it
uses similar boilerplate code:
4. If B <: Rational, then __radd_ converts both to the
builtin rational type (hey look, that's us) and
proceeds.
5. Otherwise, __radd__ tries to find the nearest common
base ABC, and fall back to its builtin type. Since this
class doesn't subclass a concrete type, there's no
implementation to fall back to, so we need to try as
hard as possible to return an actual value, or the user
will get a TypeError.
"""
def forward(a, b):
if isinstance(b, Fraction):
return monomorphic_operator(a, b)
elif isinstance(b, int):
return monomorphic_operator(a, Fraction(b))
elif isinstance(b, float):
return fallback_operator(float(a), b)
elif isinstance(b, complex):
return fallback_operator(complex(a), b)
else:
return NotImplemented
forward.__name__ = '__' + fallback_operator.__name__ + '__'
forward.__doc__ = monomorphic_operator.__doc__
def reverse(b, a):
if isinstance(a, numbers.Rational):
# Includes ints.
return monomorphic_operator(Fraction(a), b)
elif isinstance(a, numbers.Real):
return fallback_operator(float(a), float(b))
elif isinstance(a, numbers.Complex):
return fallback_operator(complex(a), complex(b))
else:
return NotImplemented
reverse.__name__ = '__r' + fallback_operator.__name__ + '__'
reverse.__doc__ = monomorphic_operator.__doc__
return forward, reverse
# Rational arithmetic algorithms: Knuth, TAOCP, Volume 2, 4.5.1.
#
# Assume input fractions a and b are normalized.
#
# 1) Consider addition/subtraction.
#
# Let g = gcd(da, db). Then
#
# na nb na*db ± nb*da
# a ± b == -- ± -- == ------------- ==
# da db da*db
#
# na*(db//g) ± nb*(da//g) t
# == ----------------------- == -
# (da*db)//g d
#
# Now, if g > 1, we're working with smaller integers.
#
# Note, that t, (da//g) and (db//g) are pairwise coprime.
#
# Indeed, (da//g) and (db//g) share no common factors (they were
# removed) and da is coprime with na (since input fractions are
# normalized), hence (da//g) and na are coprime. By symmetry,
# (db//g) and nb are coprime too. Then,
#
# gcd(t, da//g) == gcd(na*(db//g), da//g) == 1
# gcd(t, db//g) == gcd(nb*(da//g), db//g) == 1
#
# Above allows us optimize reduction of the result to lowest
# terms. Indeed,
#
# g2 = gcd(t, d) == gcd(t, (da//g)*(db//g)*g) == gcd(t, g)
#
# t//g2 t//g2
# a ± b == ----------------------- == ----------------
# (da//g)*(db//g)*(g//g2) (da//g)*(db//g2)
#
# is a normalized fraction. This is useful because the unnormalized
# denominator d could be much larger than g.
#
# We should special-case g == 1 (and g2 == 1), since 60.8% of
# randomly-chosen integers are coprime:
# https://en.wikipedia.org/wiki/Coprime_integers#Probability_of_coprimality
# Note, that g2 == 1 always for fractions, obtained from floats: here
# g is a power of 2 and the unnormalized numerator t is an odd integer.
#
# 2) Consider multiplication
#
# Let g1 = gcd(na, db) and g2 = gcd(nb, da), then
#
# na*nb na*nb (na//g1)*(nb//g2)
# a*b == ----- == ----- == -----------------
# da*db db*da (db//g1)*(da//g2)
#
# Note, that after divisions we're multiplying smaller integers.
#
# Also, the resulting fraction is normalized, because each of
# two factors in the numerator is coprime to each of the two factors
# in the denominator.
#
# Indeed, pick (na//g1). It's coprime with (da//g2), because input
# fractions are normalized. It's also coprime with (db//g1), because
# common factors are removed by g1 == gcd(na, db).
#
# As for addition/subtraction, we should special-case g1 == 1
# and g2 == 1 for same reason. That happens also for multiplying
# rationals, obtained from floats.
def _add(a, b):
"""a + b"""
na, da = a._numerator, a._denominator
nb, db = b._numerator, b._denominator
g = math.gcd(da, db)
if g == 1:
return Fraction._from_coprime_ints(na * db + da * nb, da * db)
s = da // g
t = na * (db // g) + nb * s
g2 = math.gcd(t, g)
if g2 == 1:
return Fraction._from_coprime_ints(t, s * db)
return Fraction._from_coprime_ints(t // g2, s * (db // g2))
__add__, __radd__ = _operator_fallbacks(_add, operator.add)
def _sub(a, b):
"""a - b"""
na, da = a._numerator, a._denominator
nb, db = b._numerator, b._denominator
g = math.gcd(da, db)
if g == 1:
return Fraction._from_coprime_ints(na * db - da * nb, da * db)
s = da // g
t = na * (db // g) - nb * s
g2 = math.gcd(t, g)
if g2 == 1:
return Fraction._from_coprime_ints(t, s * db)
return Fraction._from_coprime_ints(t // g2, s * (db // g2))
__sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub)
def _mul(a, b):
"""a * b"""
na, da = a._numerator, a._denominator
nb, db = b._numerator, b._denominator
g1 = math.gcd(na, db)
if g1 > 1:
na //= g1
db //= g1
g2 = math.gcd(nb, da)
if g2 > 1:
nb //= g2
da //= g2
return Fraction._from_coprime_ints(na * nb, db * da)
__mul__, __rmul__ = _operator_fallbacks(_mul, operator.mul)
def _div(a, b):
"""a / b"""
# Same as _mul(), with inversed b.
nb, db = b._numerator, b._denominator
if nb == 0:
raise ZeroDivisionError('Fraction(%s, 0)' % db)
na, da = a._numerator, a._denominator
g1 = math.gcd(na, nb)
if g1 > 1:
na //= g1
nb //= g1
g2 = math.gcd(db, da)
if g2 > 1:
da //= g2
db //= g2
n, d = na * db, nb * da
if d < 0:
n, d = -n, -d
return Fraction._from_coprime_ints(n, d)
__truediv__, __rtruediv__ = _operator_fallbacks(_div, operator.truediv)
def _floordiv(a, b):
"""a // b"""
return (a.numerator * b.denominator) // (a.denominator * b.numerator)
__floordiv__, __rfloordiv__ = _operator_fallbacks(_floordiv, operator.floordiv)
def _divmod(a, b):
"""(a // b, a % b)"""
da, db = a.denominator, b.denominator
div, n_mod = divmod(a.numerator * db, da * b.numerator)
return div, Fraction(n_mod, da * db)
__divmod__, __rdivmod__ = _operator_fallbacks(_divmod, divmod)
def _mod(a, b):
"""a % b"""
da, db = a.denominator, b.denominator
return Fraction((a.numerator * db) % (b.numerator * da), da * db)
__mod__, __rmod__ = _operator_fallbacks(_mod, operator.mod)
def __pow__(a, b):
"""a ** b
If b is not an integer, the result will be a float or complex
since roots are generally irrational. If b is an integer, the
result will be rational.
"""
if isinstance(b, numbers.Rational):
if b.denominator == 1:
power = b.numerator
if power >= 0:
return Fraction._from_coprime_ints(a._numerator ** power,
a._denominator ** power)
elif a._numerator > 0:
return Fraction._from_coprime_ints(a._denominator ** -power,
a._numerator ** -power)
elif a._numerator == 0:
raise ZeroDivisionError('Fraction(%s, 0)' %
a._denominator ** -power)
else:
return Fraction._from_coprime_ints((-a._denominator) ** -power,
(-a._numerator) ** -power)
else:
# A fractional power will generally produce an
# irrational number.
return float(a) ** float(b)
elif isinstance(b, (float, complex)):
return float(a) ** b
else:
return NotImplemented
def __rpow__(b, a):
"""a ** b"""
if b._denominator == 1 and b._numerator >= 0:
# If a is an int, keep it that way if possible.
return a ** b._numerator
if isinstance(a, numbers.Rational):
return Fraction(a.numerator, a.denominator) ** b
if b._denominator == 1:
return a ** b._numerator
return a ** float(b)
def __pos__(a):
"""+a: Coerces a subclass instance to Fraction"""
return Fraction._from_coprime_ints(a._numerator, a._denominator)
def __neg__(a):
"""-a"""
return Fraction._from_coprime_ints(-a._numerator, a._denominator)
def __abs__(a):
"""abs(a)"""
return Fraction._from_coprime_ints(abs(a._numerator), a._denominator)
def __int__(a, _index=operator.index):
"""int(a)"""
if a._numerator < 0:
return _index(-(-a._numerator // a._denominator))
else:
return _index(a._numerator // a._denominator)
def __trunc__(a):
"""math.trunc(a)"""
if a._numerator < 0:
return -(-a._numerator // a._denominator)
else:
return a._numerator // a._denominator
def __floor__(a):
"""math.floor(a)"""
return a._numerator // a._denominator
def __ceil__(a):
"""math.ceil(a)"""
# The negations cleverly convince floordiv to return the ceiling.
return -(-a._numerator // a._denominator)
def __round__(self, ndigits=None):
"""round(self, ndigits)
Rounds half toward even.
"""
if ndigits is None:
d = self._denominator
floor, remainder = divmod(self._numerator, d)
if remainder * 2 < d:
return floor
elif remainder * 2 > d:
return floor + 1
# Deal with the half case:
elif floor % 2 == 0:
return floor
else:
return floor + 1
shift = 10**abs(ndigits)
# See _operator_fallbacks.forward to check that the results of
# these operations will always be Fraction and therefore have
# round().
if ndigits > 0:
return Fraction(round(self * shift), shift)
else:
return Fraction(round(self / shift) * shift)
def __hash__(self):
"""hash(self)"""
return _hash_algorithm(self._numerator, self._denominator)
def __eq__(a, b):
"""a == b"""
if type(b) is int:
return a._numerator == b and a._denominator == 1
if isinstance(b, numbers.Rational):
return (a._numerator == b.numerator and
a._denominator == b.denominator)
if isinstance(b, numbers.Complex) and b.imag == 0:
b = b.real
if isinstance(b, float):
if math.isnan(b) or math.isinf(b):
# comparisons with an infinity or nan should behave in
# the same way for any finite a, so treat a as zero.
return 0.0 == b
else:
return a == a.from_float(b)
else:
# Since a doesn't know how to compare with b, let's give b
# a chance to compare itself with a.
return NotImplemented
def _richcmp(self, other, op):
"""Helper for comparison operators, for internal use only.
Implement comparison between a Rational instance `self`, and
either another Rational instance or a float `other`. If
`other` is not a Rational instance or a float, return
NotImplemented. `op` should be one of the six standard
comparison operators.
"""
# convert other to a Rational instance where reasonable.
if isinstance(other, numbers.Rational):
return op(self._numerator * other.denominator,
self._denominator * other.numerator)
if isinstance(other, float):
if math.isnan(other) or math.isinf(other):
return op(0.0, other)
else:
return op(self, self.from_float(other))
else:
return NotImplemented
def __lt__(a, b):
"""a < b"""
return a._richcmp(b, operator.lt)
def __gt__(a, b):
"""a > b"""
return a._richcmp(b, operator.gt)
def __le__(a, b):
"""a <= b"""
return a._richcmp(b, operator.le)
def __ge__(a, b):
"""a >= b"""
return a._richcmp(b, operator.ge)
def __bool__(a):
"""a != 0"""
# bpo-39274: Use bool() because (a._numerator != 0) can return an
# object which is not a bool.
return bool(a._numerator)
# support for pickling, copy, and deepcopy
def __reduce__(self):
return (self.__class__, (self._numerator, self._denominator))
def __copy__(self):
if type(self) == Fraction:
return self # I'm immutable; therefore I am my own clone
return self.__class__(self._numerator, self._denominator)
def __deepcopy__(self, memo):
if type(self) == Fraction:
return self # My components are also immutable
return self.__class__(self._numerator, self._denominator)
The object
class has the following identifiers:
In [1]: dir(object)
Out[1]: [
'__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'
]
It is the base
class of every other class in Python. Now you should have a basic understanding of the data model and how each of these identifiers define the behaviour of a builtins
function:
In [2]: object.
# -------------------------------
# Available Identifiers for `object`:
# -------------------------------------
# 🔧 Functions:
# - __init__(self, /, *args, **kwargs) : Initializes the object.
# - __new__(*args, **kwargs) : Creates a new instance of the class.
# - __delattr__(self, name, /) : Defines behavior for when an attribute is deleted.
# - __dir__(self, /) : Default dir() implementation.
# - __sizeof__(self, /) : Returns the size of the object in memory, in bytes.
# - __eq__(self, value, /) : Checks for equality with another object.
# - __ne__(self, value, /) : Checks for inequality with another object.
# - __lt__(self, value, /) : Checks if the object is less than another.
# - __le__(self, value, /) : Checks if the object is less than or equal to another.
# - __gt__(self, value, /) : Checks if the object is greater than another.
# - __ge__(self, value, /) : Checks if the object is greater than or equal to another.
# - __repr__(self, /) : Returns a string representation of the object.
# - __str__(self, /) : Returns a string for display purposes.
# - __format__(self, format_spec, /) : Returns a formatted string representation of the object.
# - __hash__(self, /) : Returns a hash of the object.
# - __getattribute__(self, name, /) : Gets an attribute from the object.
# - __setattr__(self, name, value, /) : Sets an attribute on the object.
# - __delattr__(self, name, /) : Deletes an attribute from the object.
# - __reduce__(self, /) : Prepares the object for pickling.
# - __reduce_ex__(self, protocol, /) : Similar to __reduce__, with a protocol argument.
# - __init_subclass__(...) : Called when a class is subclassed; default
# implementation does nothing.
# - __subclasshook__(...) : Customize issubclass() for abstract classes.
#
# 🔍 Attributes:
# - __class__ : The class of the object.
# - __doc__ : The docstring of the object.
# -------------------------------------
If help
is used on object
, a brief overview is given about each identifier and the builtins
it maps to:
In [1]: help(object)
Help on class object in module builtins:
class object
| The base class of the class hierarchy.
|
| When called, it accepts no arguments and returns a new featureless
| instance that has no instance attributes and cannot be given any.
|
| Built-in subclasses:
| anext_awaitable
| async_generator
| async_generator_asend
| async_generator_athrow
| ... and 94 other subclasses
|
| Methods defined here:
|
| __delattr__(self, name, /)
| Implement delattr(self, name).
|
| __dir__(self, /)
| Default dir() implementation.
|
| __eq__(self, value, /)
| Return self==value.
|
| __format__(self, format_spec, /)
| Default object formatter.
|
| Return str(self) if format_spec is empty. Raise TypeError otherwise.
|
| __ge__(self, value, /)
| Return self>=value.
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __getstate__(self, /)
| Helper for pickle.
|
| __gt__(self, value, /)
| Return self>value.
|
| __hash__(self, /)
| Return hash(self).
|
| __init__(self, /, *args, **kwargs)
| Initialize self. See help(type(self)) for accurate signature.
|
| __le__(self, value, /)
| Return self<=value.
|
| __lt__(self, value, /)
| Return self<value.
|
| __ne__(self, value, /)
| Return self!=value.
|
| __reduce__(self, /)
| Helper for pickle.
|
| __reduce_ex__(self, protocol, /)
| Helper for pickle.
|
| __repr__(self, /)
| Return repr(self).
|
| __setattr__(self, name, value, /)
| Implement setattr(self, name, value).
|
| __sizeof__(self, /)
| Size of object in memory, in bytes.
|
| __str__(self, /)
| Return str(self).
|
| ----------------------------------------------------------------------
| Class methods defined here:
|
| __init_subclass__(...)
| This method is called when a class is subclassed.
|
| The default implementation does nothing. It may be
| overridden to extend subclasses.
|
| __subclasshook__(...)
| Abstract classes can override this to customize issubclass().
|
| This is invoked early on by abc.ABCMeta.__subclasscheck__().
| It should return True, False or NotImplemented. If it returns
| NotImplemented, the normal algorithm is used. Otherwise, it
| overrides the normal algorithm (and the outcome is cached).
|
| ----------------------------------------------------------------------
| Static methods defined here:
|
| __new__(*args, **kwargs)
| Create and return a new object. See help(type) for accurate signature.
|
| ----------------------------------------------------------------------
| Data and other attributes defined here:
|
| __class__ = <class 'type'>
| type(object) -> the object's type
| type(name, bases, dict, **kwds) -> a new type
Some operators were seen to be present in the int
and str
clases that were defined in the object
class. etails about these are in the operator
module:
In [2]: import operator
In [3]: help(operator)
Help on module operator:
NAME
operator - Operator interface.
MODULE REFERENCE
https://docs.python.org/3.12/library/operator.html
The following documentation is automatically generated from the Python
source files. It may be incomplete, incorrect or include features that
are considered implementation detail and may vary between Python
implementations. When in doubt, consult the module reference at the
location listed above.
DESCRIPTION
This module exports a set of functions implemented in C corresponding
to the intrinsic operators of Python. For example, operator.add(x, y)
is equivalent to the expression x+y. The function names are those
used for special methods; variants without leading and trailing
'__' are also provided for convenience.
CLASSES
builtins.object
attrgetter
itemgetter
methodcaller
class attrgetter(builtins.object)
| attrgetter(attr, /, *attrs)
|
| Return a callable object that fetches the given attribute(s) from its operand.
| After f = attrgetter('name'), the call f(r) returns r.name.
| After g = attrgetter('name', 'date'), the call g(r) returns (r.name, r.date).
| After h = attrgetter('name.first', 'name.last'), the call h(r) returns
| (r.name.first, r.name.last).
|
| Methods defined here:
|
| __call__(self, /, *args, **kwargs)
| Call self as a function.
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __reduce__(...)
| Return state information for pickling
|
| __repr__(self, /)
| Return repr(self).
|
| ----------------------------------------------------------------------
| Static methods defined here:
|
| __new__(*args, **kwargs)
| Create and return a new object. See help(type) for accurate signature.
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __vectorcalloffset__
class itemgetter(builtins.object)
| itemgetter(item, /, *items)
|
| Return a callable object that fetches the given item(s) from its operand.
| After f = itemgetter(2), the call f(r) returns r[2].
| After g = itemgetter(2, 5, 3), the call g(r) returns (r[2], r[5], r[3])
|
| Methods defined here:
|
| __call__(self, /, *args, **kwargs)
| Call self as a function.
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __reduce__(...)
| Return state information for pickling
|
| __repr__(self, /)
| Return repr(self).
|
| ----------------------------------------------------------------------
| Static methods defined here:
|
| __new__(*args, **kwargs)
| Create and return a new object. See help(type) for accurate signature.
|
| ----------------------------------------------------------------------
| Data descriptors defined here:
|
| __vectorcalloffset__
class methodcaller(builtins.object)
| methodcaller(name, /, *args, **kwargs)
|
| Return a callable object that calls the given method on its operand.
| After f = methodcaller('name'), the call f(r) returns r.name().
| After g = methodcaller('name', 'date', foo=1), the call g(r) returns
| r.name('date', foo=1).
|
| Methods defined here:
|
| __call__(self, /, *args, **kwargs)
| Call self as a function.
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __reduce__(...)
| Return state information for pickling
|
| __repr__(self, /)
| Return repr(self).
|
| ----------------------------------------------------------------------
| Static methods defined here:
|
| __new__(*args, **kwargs)
| Create and return a new object. See help(type) for accurate signature.
FUNCTIONS
__abs__ = abs(a, /)
Same as abs(a).
__add__ = add(a, b, /)
Same as a + b.
__and__ = and_(a, b, /)
Same as a & b.
__call__ = call(obj, /, *args, **kwargs)
Same as obj(*args, **kwargs).
__concat__ = concat(a, b, /)
Same as a + b, for a and b sequences.
__contains__ = contains(a, b, /)
Same as b in a (note reversed operands).
__delitem__ = delitem(a, b, /)
Same as del a[b].
__eq__ = eq(a, b, /)
Same as a == b.
__floordiv__ = floordiv(a, b, /)
Same as a // b.
__ge__ = ge(a, b, /)
Same as a >= b.
__getitem__ = getitem(a, b, /)
Same as a[b].
__gt__ = gt(a, b, /)
Same as a > b.
__iadd__ = iadd(a, b, /)
Same as a += b.
__iand__ = iand(a, b, /)
Same as a &= b.
__iconcat__ = iconcat(a, b, /)
Same as a += b, for a and b sequences.
__ifloordiv__ = ifloordiv(a, b, /)
Same as a //= b.
__ilshift__ = ilshift(a, b, /)
Same as a <<= b.
__imatmul__ = imatmul(a, b, /)
Same as a @= b.
__imod__ = imod(a, b, /)
Same as a %= b.
__imul__ = imul(a, b, /)
Same as a *= b.
__index__ = index(a, /)
Same as a.__index__()
__inv__ = inv(a, /)
Same as ~a.
__invert__ = invert(a, /)
Same as ~a.
__ior__ = ior(a, b, /)
Same as a |= b.
__ipow__ = ipow(a, b, /)
Same as a **= b.
__irshift__ = irshift(a, b, /)
Same as a >>= b.
__isub__ = isub(a, b, /)
Same as a -= b.
__itruediv__ = itruediv(a, b, /)
Same as a /= b.
__ixor__ = ixor(a, b, /)
Same as a ^= b.
__le__ = le(a, b, /)
Same as a <= b.
__lshift__ = lshift(a, b, /)
Same as a << b.
__lt__ = lt(a, b, /)
Same as a < b.
__matmul__ = matmul(a, b, /)
Same as a @ b.
__mod__ = mod(a, b, /)
Same as a % b.
__mul__ = mul(a, b, /)
Same as a * b.
__ne__ = ne(a, b, /)
Same as a != b.
__neg__ = neg(a, /)
Same as -a.
__not__ = not_(a, /)
Same as not a.
__or__ = or_(a, b, /)
Same as a | b.
__pos__ = pos(a, /)
Same as +a.
__pow__ = pow(a, b, /)
Same as a ** b.
__rshift__ = rshift(a, b, /)
Same as a >> b.
__setitem__ = setitem(a, b, c, /)
Same as a[b] = c.
__sub__ = sub(a, b, /)
Same as a - b.
__truediv__ = truediv(a, b, /)
Same as a / b.
__xor__ = xor(a, b, /)
Same as a ^ b.
abs(a, /)
Same as abs(a).
add(a, b, /)
Same as a + b.
and_(a, b, /)
Same as a & b.
call(obj, /, *args, **kwargs)
Same as obj(*args, **kwargs).
concat(a, b, /)
Same as a + b, for a and b sequences.
contains(a, b, /)
Same as b in a (note reversed operands).
countOf(a, b, /)
Return the number of items in a which are, or which equal, b.
delitem(a, b, /)
Same as del a[b].
eq(a, b, /)
Same as a == b.
floordiv(a, b, /)
Same as a // b.
ge(a, b, /)
Same as a >= b.
getitem(a, b, /)
Same as a[b].
gt(a, b, /)
Same as a > b.
iadd(a, b, /)
Same as a += b.
iand(a, b, /)
Same as a &= b.
iconcat(a, b, /)
Same as a += b, for a and b sequences.
ifloordiv(a, b, /)
Same as a //= b.
ilshift(a, b, /)
Same as a <<= b.
imatmul(a, b, /)
Same as a @= b.
imod(a, b, /)
Same as a %= b.
imul(a, b, /)
Same as a *= b.
index(a, /)
Same as a.__index__()
indexOf(a, b, /)
Return the first index of b in a.
inv(a, /)
Same as ~a.
invert(a, /)
Same as ~a.
ior(a, b, /)
Same as a |= b.
ipow(a, b, /)
Same as a **= b.
irshift(a, b, /)
Same as a >>= b.
is_(a, b, /)
Same as a is b.
is_not(a, b, /)
Same as a is not b.
isub(a, b, /)
Same as a -= b.
itruediv(a, b, /)
Same as a /= b.
ixor(a, b, /)
Same as a ^= b.
le(a, b, /)
Same as a <= b.
length_hint(obj, default=0, /)
Return an estimate of the number of items in obj.
This is useful for presizing containers when building from an iterable.
If the object supports len(), the result will be exact.
Otherwise, it may over- or under-estimate by an arbitrary amount.
The result will be an integer >= 0.
lshift(a, b, /)
Same as a << b.
lt(a, b, /)
Same as a < b.
matmul(a, b, /)
Same as a @ b.
mod(a, b, /)
Same as a % b.
mul(a, b, /)
Same as a * b.
ne(a, b, /)
Same as a != b.
neg(a, /)
Same as -a.
not_(a, /)
Same as not a.
or_(a, b, /)
Same as a | b.
pos(a, /)
Same as +a.
pow(a, b, /)
Same as a ** b.
rshift(a, b, /)
Same as a >> b.
setitem(a, b, c, /)
Same as a[b] = c.
sub(a, b, /)
Same as a - b.
truediv(a, b, /)
Same as a / b.
truth(a, /)
Return True if a is true, False otherwise.
xor(a, b, /)
Same as a ^ b.
DATA
__all__ = ['abs', 'add', 'and_', 'attrgetter', 'call', 'concat', 'cont...
FILE
c:\users\appslab\miniforge3\envs\spyder-env\lib\operator.py
These will be further explored when examining text based classes, numeric based classes and collection based classes in the next tutorials.