python class read-only attributes (in datetime) - python

I know that a quick way of setting an attribute to be private is to use __ before an attribute (corrected later as this is actually for name mangling, not for restriction of access), or use #property
But, I found that for a python standard library module, for example, datetime, this was set a different way?
To explain my question, please go to the source code of datetime
Let's take class timedelta as an example:
class timedelta:
...
...
...
timedelta.min = timedelta(-999999999)
timedelta.max = timedelta(days=999999999, hours=23, minutes=59, seconds=59,
microseconds=999999)
timedelta.resolution = timedelta(microseconds=1)
The class attributes was set outside of the class? why?
and if I:
import datetime
d= datetime.timedelta(days=1, hours=12)
print(d)
print(d.max) # >>> 999999999 days, 23:59:59.999999
print(type(d.max)) # >>> <class 'datetime.timedelta'>
d.max = 1000 # regardless of the reason, if I just want to do this
# >>> AttributeError: 'datetime.timedelta' object attribute 'max' is read-only
I wonder where does this AttributeError coming from? I can not find in anywhere in the source code that this error message will be raised?
Thanks!

The class attributes was set outside of the class? why?
timedelta doesn't exist when the body of the timedelta class is being executed. You have to execute all of the code in the class timedelta: block before the class object is created and can be used on its own.
I wonder where does this AttributeError coming from? I can not find in anywhere in the source code that this error message will be raised?
The datetime module is written in pure Python but tries to use a faster module written in C if it can. The pure Python code works as you'd expect:
>>> import sys
>>> sys.modules['_datetime'] = None # prevent the C module from loading
>>> from datetime import timedelta
>>> timedelta.min = 5
>>> timedelta.min
5
The timedelta class has tp_flags set to Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE. You can only set the attributes of objects which include the Py_TPFLAGS_HEAPTYPE flag.

That is not the source code of the date-time module used by the CPython interpreter. The CPython source implements a lot of the standard library in C for performance reasons. They do provide Python-only implementations (which I believe are relied upon by PyPy in many instances, for example).
The source code for datetime is actually here:
https://github.com/python/cpython/blob/3.6/Modules/_datetimemodule.c
Access is restricted at the C-level.
Note, double-underscore name-mangling does name-mangling, it doesn't restrict access.

Related

Refactoring a Python class to take a dynamic parameter without changing calling pattern dramatically

I have a Python class in a base_params.py module within an existing codebase, which looks like this:
import datetime
class BaseParams:
TIMESTAMP = datetime.datetime.now()
PATH1 = f'/foo1/bar/{TIMESTAMP}/baz'
PATH2 = f'/foo2/bar/{TIMESTAMP}/baz'
Callers utilize it this way:
from base_params import BaseParams as params
print(params.PATH1)
Now, I want to replace the TIMESTAMP value with one that is dynamically specified at runtime (through e.g. CLI arguments).
Is there a way to do this in Python without requiring my callers to refactor their code in a dramatic way? This is currently confounding me because the contents of the class BaseParams get executed at 'compile' time, so there is no opportunity there to pass in a dynamic value as it's currently structured. And in some of my existing code, this object is being treated as "fully ready" at 'compile' time, for example, its values are used as function argument defaults:
def some_function(value1, value2=params.PATH1):
...
I am wondering if there is some way to work with Python modules and/or abuse Python's __special_methods__ to get this existing code pattern working more or less as-is, without a deeper refactoring of some kind.
My current expectation is "this is not really possible" because of that last example, where the default value is being specified in the function signature. But I thought I should check with the Python wizards to see if there may be a suitably Pythonic way around this.
Yes, you just need to make sure that the command line argument is parsed before the class is defined and before any function that uses the class's attribute as a default argument is defined (but that should already be the case).
(using sys.argv for sake of simplicity. It is better to use an actual argument parser such as argparse)
import datetime
import sys
class BaseParams:
try:
TIMESTAMP = sys.argv[1]
except IndexError:
TIMESTAMP = datetime.datetime.now()
PATH1 = f'/foo1/bar/{TIMESTAMP}/baz'
PATH2 = f'/foo2/bar/{TIMESTAMP}/baz'
print(BaseParams.TIMESTAMP)
$ python main.py dummy-argument-from-cli
outputs
dummy-argument-from-cli
while
$ python main.py
outputs
2021-06-26 02:32:12.882601
You can still totally replace the value of a class attribute after the class has been defined:
BaseParams.TIMESTAMP = <whatever>
There are definitely some more "magic" things you can do though, such as a class factory of some kind. Since Python 3.7 you can also take advantage of module __getattr__ to create a kind of factory for the BaseParams class (PEP 562)
In base_params.py you might rename BaseParams to _BaseParams or BaseParamsBase or something like that :)
Then at the module level define:
def __getattr__(attr):
if attr == 'BaseParams':
params = ... # whatever code you need to determine class attributes for BaseParams
return type('BaseParams', (_BaseParams,), params)
raise AttributeError(attr)

How do I define a type which encapsulates/describes the behaviour/signature of a specific module?

How do I define a type which encapsulates/describes the behaviour/signature of a specific module?
The problem originates from the fact that I have to sleep in my code and I want to abstract this away. I want to inject a mock for the time library (It's quite likely I want other functions from time later on) and I also want to define types as to enable static type checking (MyPy is used for type checking). I'd also like my IDE to have proper autocompletion and documentation (which would the case if it can guess the 'type' correctly)
I currently have the following code which works and passes my checks, however this just ignores the MyPy error (time is not a valid type):
import time
[other relevant imports]
...
#dataclass
class RPUService:
config: RPUConfig
_time: time = time # type: ignore[valid-type]
...
def some_method(self):
self._time.sleep(self.config.sleepytime)
I can also write the _time definition as follows:
from types import ModuleType
_time: ModuleType = time
Or similarly with Any:
from typing import Any
_time: Any = time
These are both valid, but this removes my IDE's ability to recognize self._time as being the time library. (Or something which looks like the time library)
I could define a Protocol with the signature of time.sleep but I'd rather not have the overhead of maintaining such a Protocol. (PEP-544)
So my question, how do I define a type which encapsulates/describes the behaviour of a specific module?
(If anything is unclear, please ask)

How Python deals with objects that come from "elsewhere"

This may be a stupid question, but I don't get in Python how we can use object that we did not some define or imported.
Consider the following example, using Python's datetime module:
from datetime import date
date1 = date(2019,1,1)
date2 = date(2019,1,5)
type(date2-date1) #<class 'datetime.timedelta'>
type(date2) #<class 'datetime.date'>
Then date2-date1 is of timedelta class, even though we haven't imported this.
(I could probably also cook up other examples, where we obtain objects, even though we haven't defined them.)
How can this be?
Should I think about these new objects that pop up simply as pieces in memory that are being returned by other functions, that, even though we haven't defined them, contain "in themselves" enough information so that the Python interpreter can meaningfully apply the type() and other functions to them?
You are incorrectly assuming that import limits what is loaded into memory. import limits what names are bound in your module globals.
The whole module is still loaded, as are dependencies of that module. Just because your namespace doesn't bind a reference to the datetime.timedelta object doesn't mean it is not available to the datetime module.
See the import statement documentation:
The from form uses a slightly more complex process:
find the module specified in the from clause, loading and initializing it if necessary;
for each of the identifiers specified in the import clauses:
check if the imported module has an attribute by that name
if not, attempt to import a submodule with that name and then check the imported module again for that attribute
if the attribute is not found, ImportError is raised.
otherwise, a reference to that value is stored in the local namespace, using the name in the as clause if it is present, otherwise using the attribute name
So loading and initialising of modules is a separate step, executed once per module. The second step binds names in your namespace.
from datetime import date makes sure the datetime module is loaded, then finds datetime.date and adds date = datetime.date to your namespace.
If you want to see what modules are loaded, check out the sys.modules mapping. That's the location that the import statement machinery checks to see if a given module has been loaded yet.
from datetime import date
Date is importing timedelta somewhere, as it might be a dependency, even if you don't see it.

Python modules usage

I was writing a code in python and got stuck with a doubt. Seems irrelevant but can't get over it. The thing is when I import a module and use it as below:
import math
print math.sqrt(9)
Here I see math(module) as a class which had a method sqrt(). If that is the case then how can I directly use the class without creating an object of it. I am basically unable to understand here the abstraction between class and and object.
Modules are more like objects, not like classes. You don't "instantiate" a module, there's only one instance of each module and you can access it using the import statement.
Specifically, modules are objects of type 'module':
>>> import math
>>> type(math)
<type 'module'>
Each module is going to have a different set of variables and methods.
Modules are instantiated by Python, whenever they are first imported. Modules that have been instantiated are stored in sys.modules:
>>> import sys
>>> 'math' in sys.modules
False
>>> import math
>>> 'math' in sys.modules
True
>>> sys.modules['math'] is math
True
AFAIK all python modules (like math and million more) are instantiated when you have imported it. How many times are they instantiated you ask ? Just once! All modules are singletons.
Just saying the above statement isn't enough so let's dive deep into it.
Create a python module ( module is basically any file ending with ".py" extension ) say "p.py" containing some code as follows:
In p.py
print "Instantiating p.py module. Please wait..."
# your good pythonic optimized functions, classes goes here
print "Instantiating of p.py module is complete."
and in q.py try importing it
import p
and when you run q.py you will see..
Instantiating p.py module. Please wait...
Instantiating of p.py module is complete.
Now have you created an instance of it ? NO! But still you have it up and running ready to be used.
In your case math is not a class. When you import math the whole module math is imported. You can see it like the inclusion of a library (the concept of it).
If you want to avoid to import the whole module (in order to not have everything included in your program), you can do something like this:
from math import sqrt
print sqrt(9)
This way only sqrt is imported and not everything from the math module.
Here I see math(module) as a class which had a method sqrt(). If that is the case then how can I directly use the class without creating an object of it. I am basically unable to understand here the abstraction between class and and object.
When you import a module, the module object is created. Just like when you use open('file.txt') a file object will be created.
You can use a class without creating an object from it by referencing the class name:
class A:
value = 2 + 2
A.value
class A is an object of class type--the built-in class used to create classes. Everything in Python is an object.
When you call the class A() that's how you create an object. *Sometimes objects are created by statements like import creates a module object, def creates a function object, classcreates a class object that creates other objects and many other statements...

circular import dependencies in a package with inheritances

I have basically the following setup in my package:
thing.py:
from otherthing import *
class Thing(Base):
def action(self):
...do something with Otherthing()...
subthing.py:
from thing import *
class Subthing(Thing):
pass
otherthing.py:
from subthing import *
class Otherthing(Base):
def action(self):
... do something with Subthing()...
If I put all objects into one file, it will work, but that file would just become way too big and it'll be harder to maintain. How do I solve this problem?
This is treading into the dreaded Python circular imports argument but, IMHO, you can have an excellent design and still need circular references.
So, try this approach:
thing.py:
class Thing(Base):
def action(self):
...do something with otherthing.Otherthing()...
import otherthing
subthing.py:
import thing
class Subthing(thing.Thing):
pass
otherthing.py:
class Otherthing(Base):
def action(self):
... do something with subthing.Subthing()...
import subthing
There are a couple of things going on here. First, some background.
Due to the way importing works in Python, a module that is in the process of being imported (but has not been fully parsed yet) will be considered already imported when future import statements in other modules referencing that module are evaluated. So, you can end up with a reference to a symbol on a module that is still in the middle of being parsed - and if the parsing hasn't made it down to the symbol you need yet, it will not be found and will throw an exception.
One way to deal with this is to use "tail imports". The purpose of this technique is to define any symbols that other modules referring to this one might need before potentially triggering the import of those other modules.
Another way to deal with circular references is to move from from based imports to a normal import. How does this help? When you have a from style import, the target module will be imported and then the symbol referenced in the from statement will be looked up on the module object right at that moment.
With a normal import statement, the lookup of the reference is delayed until something does an actual attribute reference on the module. This can usually be pushed down into a function or method which should not normally be executed until all of your importing is complete.
The case where these two techniques don't work is when you have circular references in your class hierarchy. The import has to come before the subclass definition and the attribute representing the super class must be there when the class statement is hit. The best you can do is use a normal import, reference the super class via the module and hope you can rearrange enough of the rest of your code to make it work.
If you are still stuck at that point, another technique that can help is to use accessor functions to mediate the access between one module and another. For instance, if you have class A in one module and want to reference it from another module but can't due to a circular reference, you can sometimes create a third module with a function in it that just returns a reference to class A. If you generalize this into a suite of accessor functions, this doesn't end up as much of a hack as it sounds.
If all else fails, you can move import statements into your functions and methods - but I usually leave that as the very last resort.
--- EDIT ---
Just wanted to add something new I discovered recently. In a "class" statement, the super class is actually a Python expression. So, you can do something like this:
>>> b=lambda :object
>>> class A(b()):
... pass
...
>>> a=A()
>>> a
<__main__.A object at 0x1fbdad0>
>>> a.__class__.__mro__
(<class '__main__.A'>, <type 'object'>)
>>>
This allows you to define and import an accessor function to get access to a class from another class definition.
Stop writing circular imports. It's simple. thing cannot possible depend on everything that's in otherthing.
1) search for other questions exactly like yours.
2) read those answers.
3) rewrite otherthing so that thing depends on part of otherthing, not all of otherthing.

Categories

Resources