Seemingly immutable dict in object instance - python

If I run the following cartopy code:
import cartopy.crs as ccrs
globe = ccrs.LambertCylindrical()
print(globe.proj4_params)
globe.proj4_params['a'] = 5
print(globe.proj4_params)
I get:
{'proj': 'cea', 'lon_0': 0.0, 'a': 57.29577951308232, 'ellps': 'WGS84'}
{'proj': 'cea', 'lon_0': 0.0, 'a': 57.29577951308232, 'ellps': 'WGS84'}
Impying that the proj4_params property is immutable.
But it's just a bog standard dict:
print(type(globe.proj4_params))
<class 'dict'>
Which, since it's implemented in C, can't be overwritten to have this kind of behavior (at least not safely).
Ok, but the code for this class is dead simple, so there's something wrong with my understanding. Can someone explain to me why I am getting this behavior?
Edit:
The following:
projection.proj4_params = dict(projection.proj4_params)
Results in:
*** AttributeError: attribute 'proj4_params' of 'cartopy._crs.CRS' objects is not writable

You could ensure your params are a dictionary by converting it to one first.
params = dict(globe.proj4_params)
Then if you print
print type(params)
<type 'dict'>
Notice the <type 'dict'> instead of the <class 'dict'> in your question. The dict you're using may be a custom class that's immutable but uses the same dict name.

The proj4_params object is finally defined in Projection's superclass CRS, which is supplied by the private, cython module _crs.pyx within cartopy. When a cartopy CRS' proj4_params is defined, it's just defined as a variable within the class rather than as a class variable (that is, it isn't defined as self.proj4_params). This is a fair part of the reason why you can't modify the values of the proj4 params.
For what it's worth, I believe this is the correct way for cartopy CRS' to behave. The proj4_params element of each CRS is a direct reference to a canonical definition of the underlying projection that cartopy is providing within Python. These canonical definitions are maintained by the proj.4 organisation (for example, Lambert Cylindrical is here), so modifying the proj4_params would mean your projection is no longer a Lambert Cylindrical projection.
If you particularly need a cartopy CRS that's almost a Lambert Cylindrical, you could always create your own crs by duplicating the code in cartopy's own LambertCylindrical class. So long as you maintain the inheritance in your own class you should be able to use it as you would one of cartopy's built-in CRS'. Note if you do modify the proj4_params for your own class it might not work because your specified params do not match to anything in proj.4 itself. In this case you could look into modifying your CRS' globe attribute to make the projection changes you need.

Related

AttributeError: module 'matplotlib' has no attribute 'AxesSubplot'

When looking at the documentation of the pandas method .hist(), it is written that it returns a matplotlib.AxesSubplot or numpy.ndarray. However when trying to type hint with hist: matplotlib.AxesSubplot = df.hist() it doesn't work (cf. error message in the title of the post).
When printing type() Python returns: Out[1]: matplotlib.axes._subplots.AxesSubplot. Should I type hint my variable hist with this (matplotlib.axes._subplots.AxesSubplot)?
Lovely question, made me dive into some new stuff I did not know. Here is my understanding of it:
It is not actually a class but a dynamic class created by a class factory, checking the mro (like type(df.hist()).mro()) of it you can see the whole inheritance.
Looking at the inheritance of AxesSubplot we see SubplotBase and Axes, so it inherits from both of these but essentially it is an Axes in a Subplot. Based on this I would have gone for matplotlib.axes._axes.Axes for type hinting.
Here is a good discussing that I derived these results from:
https://github.com/matplotlib/matplotlib/issues/18222

Comparing/identifying namedtuple types

Can I identify the namedtuple type of an object from another package using isinstance() or some other call? Consider the following code for checking the type of a namedtuple:
>>> vercingatorix=namedtuple('vercingatorix','x')
>>> v=vercingatorix(1)
>>> w=v
>>> isinstance(w,vercingatorix)
True
So far, so good. But what if the namedtuple is created by another package, e.g. pkg1.pkg2.pkg3.vercingatorix, as reported by type()? I tried isinstance(w, pkg1.pkg2.pkg3.vercingatorix) but I get an AttributeError. The namedtuple is not globally accessible (e.g. isinstance(w, vercingatorix)- -- NameError).
This is a simplified case of my problem. I have a namedtuple object (representing a message) that, depending on the kind of message, has a different subclass type. I need to be able to identify the kind of message, and the simplest way to do this seems to be to look at its type. But I can't formulate a way to do this.
I can grab the class's name (which reveals the message type) and use that for comparison:
>>> w.__class__.__name__
'vercingatorix'

How to use a __format__ method from a different class?

I have a class which I want to have the same __format__ method as another class in some module I have installed in my machine. What would be the correct way to "copy" it into my class, so that it works exactly the same as if I was using the module's class?
Edit: to be specific, I want to use the uncertainties package's uncertainties.UFloat.__format__ method in a class of my own.
MCVE:
class MyClass:
def __init__(self, arg):
self.v, self.u = arg
def __format__... ?
so that, like UFloat does:
>>> '{:L}'.format(uncertainties.ufloat(1, 0.1))
'1 \\pm 0.1'
expected behavior should be the same:
>>> '{:L}'.format(MyClass(1, 0.1))
'1 \\pm 0.1'
One way, as #juanpa.arrivillaga brought up, would be to simply change your method to point to the OtherClass method:
MyClass.__format__ = OtherClass.__format__
This is a pretty clumsy way of doing it, though. I would recommend using a wrapper method to accomplish the same thing, if it's a static method:
def __format__(cls, format_str):
return OtherClass.__format__(format_str)
or even convert your current object into the other class and simply call the method, if it's an instance method:
def __format__(self, format_str):
inst = OtherClass(self, format_str):
return inst.__format(format_str)
The other solution would be to find the source of OtherClass, or carefully observe the behavior, and then essentially rewrite the functionality. Normally I'd do this by looking in the source repository, but a quick pypi search of uncertainties and the associated documentation shows no signs of a git repository to draw from, so you'd have to do it the hard way. Python's inspect module could help with finding the source code of various components of the library, if that's helpful.
Looking at uncertainties in particular, as you present in your question, it looks like the ufloat type in the library uses the format function of AffineScalarFunc, which is accessible as uncertainties.UFloat. You can do this to look at the source code for uncertainties.UFloat.__format__:
>>> import inspect
>>> import uncertainties
>>> source = inspect.getsource(uncertainties.UFloat.__format__)
>>> print(source)
and you can either try to reverse-engineer/copy the algorithm or figure out how you can adapt your MyClass.__format__ to pass a value into uncertainties.UFloat.__format__ that won't crash the other class. I recommend this latter suggestion.
I'm not going to go any further with this solution because that method's code is 459 lines long and I don't feel like messing with that.

Evaluation of class returns false

I have written this class:
class DSMCalc(object):
def __init__(self, footprint):
if footprint.__class__.__base__.__module__ is not 'shapely.geometry.base':
raise TypeError('footprint input geometry is not a shapely geometry based object')
self.footprint = footprint
As best as I can tell I need to do the whole business of __class__.__base__.__module__ because I am trying to be inclusive of all shapely objects (shapely.geometry.polygon.Polygon and shapely.geometry.multipolygon.MultiPolygon, for example) and that combination of attributes was I found that seemed like it would work, since all the objects that I want to include output shapely.geometry.base.
When I run the code, however, I get a TypeError even when putting in a valid shapely.geometry.polygon.Polygon object. I have tried the above code with shapely.geometry.base both as a string and a module. How can this be?
Some example objects to reproduce the error:
valid_geojson_polygon_feature = {
'properties': {"name":"test"},
'type': 'Feature',
'geometry': {
'coordinates': [[(-122.4103173469268, 37.78337247419125), (-122.41042064203376, 37.7833590750075),
(-122.41046641056752, 37.78360478527359), (-122.41047393562782, 37.783644775039576),
(-122.4103759761863, 37.78365638609612), (-122.4103173469268, 37.78337247419125)]],
'type': 'Polygon'}}
from shapely.geometry import shape as get_shape
valid_shapely_polygon_feature = get_shape(valid_geojson_polygon_feature['geometry'])
print(valid_shapely_polygon_feature.__class__.__base__.__module__)
DSMCalc(valid_shapely_polygon_feature)
You can't rely on is working with string literals. Even when it works, it's an implementation detail of CPython, and in this case, even CPython's implementation doesn't support it, because CPython only automatically interns string literals that fit the rules for identifiers (that is, variable names). Your string contains .s, which means it's not automatically interned. You can see this easily at the interactive prompt:
>>> x = 'shapely.geometry.base' # Not a legal variable name
>>> y = 'shapely.geometry.base'
>>> x is y
False
>>> x = 'abc123' # Legal variable name
>>> y = 'abc123'
>>> x is y
True
Basically, change your test to != 'shapely.geometry.base', and if you need more details, read up on the difference between is and ==.
I'll note that your test is flawed in other ways. Right now, you require the immediate parent to be defined in shapely.geometry.base. But if you subclass a valid class, the child will be invalid (because the __base__ will refer to the subclass from the other module, not ultimate base class in shapely.geometry.base). A better solution is proper isinstance checking based on known good base classes, e.g.:
# Top of file
from shapely.geometry.base import BaseGeometry, GeometrySequence
# Test code (passing a tuple of legal bases classes is allowed)
if not isinstance(footprint, (BaseGeometry, GeometrySequence)):
raise TypeError('footprint input geometry is not a shapely geometry based object')
which, on top of being more explicit about what you want (explicitly enumerating legal base classes), and allowing indirect subclasses of the classes in question rather than only direct subclasses of a handful of base classes from that module, avoids allowing spurious types like CAP_STYLE and JOIN_STYLE (which, while defined in shapely.geometry.base, appear to exist largely as simple enum-like classes of constants, not actual geometry related things, and are likely not types you'd want to allow).

Mypy doesn't typecheck function with Type[NamedTuple]

I have a function that accepts a class that derives from NamedTuple and converts it into a schema. However when I run MyPy on the following code it fails with Argument 1 to "to_schema" has incompatible type "Type[Foo]"; expected "Type[NamedTuple]"
from typing import NamedTuple, Type
def to_schema(named_tuple: Type[NamedTuple]):
pass
class Foo(NamedTuple):
pass
to_schema(Foo)
Is there a way to properly type the code so that it typechecks with MyPy?
Edit:
Python documentation states that Type[Foo] accepts any subclasses of Foo (https://docs.python.org/3/library/typing.html#typing.Type). I have multiple subclasses of NamedTuple, for entities in our data model, so I'm looking for a way to annotate the function in a way that would typecheck.
The root issue with your code is that NamedTuple is not an actual type -- it's actually just a special sort of "type constructor" that synthesizes an entirely new class and type. E.g. if you try printing out the value of Foo.__mro__, you'll see (<class '__main__.Foo'>, <class 'tuple'>, <class 'object'>) -- NamedTuple is not present there at all.
That means that NamedTuple isn't actually a valid type to use at all -- in that regard, it's actually a little surprising to me that mypy just silently lets you construct Type[NamedTuple] to begin with.
To work around this, you have several potential approaches:
Rather then using Type[NamedTuple], use either Type[tuple] or Type[Tuple[Any]].
Your Foo genuinely is a subtype of a tuple, after all.
If you need methods or fields that are specifically present only in namedtuples, use a custom protocol. For example, if you particularly need the _asdict method in namedtuples, you could do:
from typing_extensions import Protocol
class NamedTupleProto(Protocol):
def _asdict(self) -> Dict[str, Any]: ...
def to_schema(x: Type[NamedTupleProto]) -> None: pass
class Foo(NamedTuple):
pass
to_schema(Foo)
Note that you will need to install the typing_extensions third party library to use this, though there are plans to formalize Protocols and add it to Python at some point. (I forget if the plan was Python 3.7 or 3.8).
Add a type ignore or a cast on the call to to_schema to silence mypy. This isn't the greatest solution, but is also the quickest.
For related discussion, see this issue. Basically, there's consensus on the mypy team that somebody ought to do something about this NamedTuple thing, whether it's by adding an error message or by adding an officially sanctioned protocol, but I think people are too busy with other tasks/bugs to push this forward. (So if you're bored and looking for something to do...)

Categories