Related
I've been using this style of inheritance to validate values set on instances of objects, but I'm wondering if there is a more fluent way to do this.
I'm following a spec where items of a certain classification (Foo) contain elements of a certain composition (Fe).
class Typed:
def __set__(self, obj, value):
assert isinstance(value, self._type), 'Incorrect type'
class Integer(Typed):
_type = int
class Float(Typed):
_type = float
class Positive(Typed):
def __set__(self, obj, value):
super().__set__(obj, value)
assert value >= 0, 'Positive Values Only Accepted'
class PositiveInteger(Integer, Positive):
pass
class PositiveFloat(Float, Positive):
pass
class Sized(Typed):
def __set__(self, obj, value):
super().__set__(obj, value)
assert value <= 2**self.size-1, f'{value} is too High'
class Fe(Sized, PositiveInteger):
name = 'Integer, 8 bit unsigned'
size = 8
class Foo(Fe):
name = 'Classificaion1'
def __set__(self, obj, id):
super().__set__(obj, id)
obj._id = id
def __get__(self, obj, objType=None):
return obj._id
def __del__(self):
pass
If you really need this level of abstraction, this is possibly the best way you can do it. My suggestion bellow can maybe save one line per class.
If you can afford to have attributes like "size" and "type" to be defined
on the final class, a richer base class and a declarative structure containing the checks as "lambda functions" can be used like this.
Note the usage of __init_subclass__ to check if all the parametes
needed for the guard expressions are defined:
from typing import Sequence
GUARDS = {
"typed": ((lambda self, value: "Incorrect type" if not instance(value, self._type) else None), ("_typed",)),
"positive": ((lambda self, value: "Only positive values" if value < 0 else None), ()),
"sized": ((lambda self, value: None if value <= 2 ** self.size - 1 else f"{value} must be smaller than 2**{self.size}"), ("size",)),
}
class DescriptorBase:
guards: Sequence[str]
def __init_subclass__(cls):
_sentinel = object()
for guard_name in cls.guards:
guard = GUARDS[guard_name]
required_attrs = guard[1]
missing = []
for attr in required_attrs:
if getattr(cls, attr, _sentinel) is _sentinel:
missing.append(attr)
if missing:
raise TypeError("Guarded descriptor {cls.__name__} did not declare required attrs: {missing}")
def __set_name__(self, owner, name):
self._name = f"_{name}""
def __set__(self, instance, value):
errors = []
for guard_name in self.guards:
if (error:= GUARDS[guard_name](self, value)) is not None:
errors.append(error)
if errors:
raise ValueError("\n".join(errors))
setattr (instance, self._name, value)
def __get__(self, instance, owner):
if instance is None:
return self
return getattr(instance, self.name)
def __del__(self, instance):
delattr(instance, self._name)
class Foo(DescriptorBase):
guards = ("typed", "positive", "sized")
size = 8
type_ = int
# No other code required here: __get__, __set__, __del__ handled in superclass
class UseAttr:
# Actual smart-attr usage:
my_foo = Foo()
Actually, if you want the class hierarchy, with less lines (no need to declare a __set__ method in each class), this approach can be used as well:
just change __init_superclass__ to collect "guards" in all superclasses,
and consolidate a single guards list on the class being defined, and then
define your composable guard-classes just as:
class Positive(BaseDescriptor):
guards = ("positive",)
class Sized(BaseDescriptor):
guards = ("sized",)
size = None
class Foo(Positive, Sized):
size = 8
class Fe(Foo):
name = "Fe name"
Actually, the change needed for this to work can be as simple as:
def __init_subclass__(cls):
_sentinel = object()
all_guards = []
for supercls in cls.__mro__:
all_guards.extend(getattr(supercls, "guards", ()))
# filter unique:
seem = {}
new_guards = []
for guard in all_guards:
if guard not in seem:
new_guards.append(guard)
seem.add(guard)
cls.guards = new_guards
for guard_name in cls.guards:
Also note that you could also collect the contents of the "GUARDS" registry from each defined class, instead of having to declare everything as lambdas before hand. I think you can get the idea from here on.
Thats what i came from at first place:
class DismRestartType(DismEnum):
DismRestartNo = 0, 'No Restart'
DismRestartPossible = 1, 'Restart Possible'
DismRestartRequired = 2, 'Restart Required'
and using it like:
class DismFeatureInfo(DismStructure):
_pack_ = 4
_fields_ = [
("FeatureName", c_wchar_p),
("RestartRequired", DismRestartType)
]
class DismEnum(Enum):
def __new__(cls, value, description):
obj = object.__new__(cls)
obj._value_ = value
obj.description = description
return obj
Notice the additional parameter for the description. The plan is to display later the description instead of the value, so i don't have to prepare every structure itself.
The problem: I get an error because the structure expect a c type and got an enum.
I did some research and founds this i.e.:
Using an IntEnum as the type in a ctypes.Structure._fields_
So i tried:
class CEnumeration(c_int):
def __new__(cls, value, description):
obj = object.__new__(cls)
obj._value_ = value
obj.description = description
print("Will never be executed")
return obj
def __repr__(self):
return self.description
Unfortunately i get *** AttributeError: 'DismRestartType' object has no attribute 'description', also the new method never gets executed. Can someone explain me why it's not executed and help me to reach the goal?
EDIT1:
I don't get it! Why does __new__ not executed in TestEnum but gets executed when i inherit from Enum? The metaclass new gets executed.
class PointlessMetaClass(type(c_int)):
def __new__(meta, classname, bases, classDict):
cls = type(c_int).__new__(meta, classname, bases, classDict)
pdb.set_trace()
return cls
class TestEnum(metaclass=PointlessMetaClass):
_type_ = "i"
def __new__(cls, value):
print("Why not executed")
pdb.set_trace()
return cls
class DismRestartType(TestEnum):
DismRestartNo = 0, 'No Restart'
DismRestartPossible = 1, 'Restart Possible'
DismRestartRequired = 2, 'Restart Required'
Solution:
I took long but now I got it:
from ctypes import c_int
from types import DynamicClassAttribute
class _EnumDict(dict):
"""Track enum member order and ensure member names are not reused.
EnumMeta will use the names found in self._member_names as the
enumeration member names.
"""
def __init__(self):
super().__init__()
self._member_names = []
def __setitem__(self, key, value):
if not isinstance(value, DynamicClassAttribute) and not key.startswith("_"):
self._member_names.append(key)
super().__setitem__(key, value)
class EnumerationType(type(c_int)):
"""Metaclass for Enum."""
#classmethod
def __prepare__(metacls, cls, bases):
edict = _EnumDict()
return edict
def __new__(metacls, classname, bases, classdict):
# save enum items into separate mapping so they don't get baked into
# the new class
enum_members = {k: classdict[k] for k in classdict._member_names}
for name in classdict._member_names:
del classdict[name]
# returns an instance of the new class, i.e. an instance of my enum
enum_class = super().__new__(metacls, classname, bases, classdict)
# Reverse value->name map for hashable values.
enum_class._value2member_map_ = {}
for member_name in classdict._member_names:
value = enum_members[member_name][0]
enum_member = c_int.__new__(enum_class)
enum_member.value = value # overwrites the value attr of c_int class
enum_member._name_ = member_name
enum_member._description_ = enum_members[member_name][1]
enum_member.__objclass__ = enum_class
# i.e DismRestartType.DismRestartNo will return an object instead of the value
setattr(enum_class, member_name, enum_member)
# i.e. {'0': <class DismRestartType:DismRestartNo: 0>}
enum_class._value2member_map_[value] = enum_member
return enum_class
def __repr__(self):
return "<Enumeration %s>" % self.__name__
class CEnumeration(c_int, metaclass=EnumerationType):
"""Generic enumeration.
Derive from this class to define new enumerations.
"""
def __new__(cls, value):
# all enum instances are actually created during class construction
# without calling this method; this method is called by the metaclass'
# __call__ (i.e. Color(3) ), and by pickle
if type(value) is cls:
# For lookups like Color(Color.RED)
return value
# by-value search for a matching enum member
# see if it's in the reverse mapping (for hashable values)
try:
if value in cls._value2member_map_:
return cls._value2member_map_[value]
except TypeError:
pass
return cls._missing_(value)
#classmethod
def _missing_(cls, value):
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
# return only description
def __repr__(self):
return "<%s.%s: %r>" % (
self.__class__.__name__, self.name, self.value)
def __str__(self):
return "%s.%s" % (self.__class__.__name__, self.name)
# DynamicClassAttribute is used to provide access to the `name` and
# `value` properties of enum members while keeping some measure of
# protection from modification, while still allowing for an enumeration
# to have members named `name` and `value`. This works because enumeration
# members are not set directly on the enum class -- __getattr__ is
# used to look them up.
#DynamicClassAttribute
def name(self):
"""The name of the Enum member."""
try:
# get name on instance
return self._name_
except AttributeError:
# get name on class
return self._value2member_map_[self.value]._name_
#DynamicClassAttribute
def description(self):
"""The description of the Enum member."""
try:
# get description on instance
return self._description_
except AttributeError:
# get description on class
return self._value2member_map_[self.value]._description_
I want to build various setter and getter. Fot not copy and paste the code, I thought something to solve it. Can decorator do it?
#property
def !!variable_name!!(self):
return self.__!!variable_name!!
#!!variable_name!!.setter
def !!variable_name!!(self, input):
self.__!!variable_name!! = input
Is it possible like macro in C?
It's unclear why you would want to do something like this—create a property with setter that ignores its value argument—but the answer is "Yes", you can do it by creating a function that returns a custom property object:
However you can't use # syntax to apply it. Instead you have to utilize it as shown:
def attribute_property(name, input_value):
STORAGE_NAME = '_' + name
#property
def prop(self):
return getattr(self, STORAGE_NAME)
#prop.setter
def prop(self, ignored):
setattr(self, STORAGE_NAME, input_value)
return prop
# EXAMPLE USAGE
class Person(object):
name = attribute_property('name', 'Monty')
def __init__(self, name, age):
self.name = name # ignores value of passed "name" argument!
self.age = age
user = Person('Rodrigo', 42)
print('user.name: {!r}'.format(user.name))
print('user.age: {!r}'.format(user.age))
Output:
user.name: 'Monty'
user.age: 42
Simple answer: Yes, that's possible using the descriptor protocol. For example you want to save variables with a leading underscore and access them without the leading underscore such a descriptor would work:
from six import string_types
class DescriptorSingleLeadingUnderscore(object):
def __init__(self, attr, doc=""):
if not isinstance(attr, string_types):
# Not a string so take the documentation (if avaiable) and name
# from the method.
if attr.__doc__:
doc = attr.__doc__
attr = attr.__name__
self.__doc__ = doc # Set the documentation of the instance.
self.attr = '_' + attr # Add leading underscore to the attribute name
def __get__(self, instance, owner=None):
if instance is None:
return self
return getattr(instance, self.attr, None)
def __set__(self, instance, value):
setattr(instance, self.attr, value)
def __delete__(self, instance):
delattr(instance, self.attr)
class X(object):
someproperty = DescriptorSingleLeadingUnderscore('someproperty')
someproperty1 = DescriptorSingleLeadingUnderscore('someproperty1')
someproperty2 = DescriptorSingleLeadingUnderscore('someproperty2')
someproperty3 = DescriptorSingleLeadingUnderscore('someproperty3')
#DescriptorSingleLeadingUnderscore
def it_also_works_as_decorator(self):
pass # this code is never executed!
And a test case:
>>> x = X()
>>> x.someproperty = 100
>>> x.someproperty
100
>>> x._someproperty
100
>>> x.it_also_works_as_decorator = 100
>>> x.it_also_works_as_decorator
100
>>> x._it_also_works_as_decorator
100
I am writing a class in python for some settings wich looks like this:
class _CanvasSettings:
def __init__(self, **kwargs):
super().__init__()
self._size_x = _int(kwargs, 'size_x', 320)
self._size_y = _int(kwargs, 'size_y', 240)
self._lock_ratio = _bool(kwargs'lock_ratio', True)
def get_size_x_var(self):
return self._size_x
def _get_size_x(self):
return self._size_x.get()
def _set_size_x(self, value):
self._size_x.set(value)
size_x = property(_get_size_x, _set_size_x)
def get_size_y_var(self):
return self._size_y
def _get_size_y(self):
return self._size_y.get()
def _set_size_y(self, value):
self._size_y.set(value)
size_y = property(_get_size_y, _set_size_y)
def get_lock_ratio_var(self):
return self._lock_ratio
def _get_lock_ratio(self):
return self._lock_ratio.get()
def _set_lock_ratio(self, value):
self._lock_ratio.set(value)
lock_ratio = property(_get_lock_ratio, _set_lock_ratio)
as you can see I add the block:
def get_something_var(self):
return self._something
def _get_something(self):
return self._something.get()
def _set_something(self, value):
self._something.set(value)
something = property(_get_something, _set_something)
For every single setting.
Is it possible to automate this task with a decorator?
I would like to do it like this (pseudocode):
def my_settings_class(cls):
result = cls
for field in cls:
result.add_getter_setter_and_property( field )
return result
#my_settings_class
class _CanvasSettings:
def __init__(self, **kwargs):
super().__init__()
self._size_x = _int(kwargs, 'size_x', 320)
self._size_y = _int(kwargs, 'size_y', 240)
self._lock_ratio = _bool(kwargs'lock_ratio', True)
# Done !
Is this possible?
If yes, how?
How to implement the add_getter_setter_and_property() method?
Edit:
There is a pretty similar question here: Python Class Decorator
from the answers there I suspect that it is possible to achive something like I have asked, but can you give me a clue on how I could implement the add_getter_setter_and_property() function/method?
Note:
the _int(), _bool() functions just return a tkinter Int/Bool-var eighter from the kwargs if the string (f.e. 'size_x') exist or from the default value (f.e. 320).
My Final Solution:
I think i have found a pretty good solution. I have to add a settings name only once, which imo is awesome :-)
import tkinter as tk
def _add_var_getter_property(cls, attr):
""" this function is used in the settings_class decorator to add a
getter for the tk-stringvar and a read/write property to the class.
cls: is the class where the attributes are added.
attr: is the name of the property and for the get_XYZ_var() method.
"""
field = '_' + attr
setattr(cls, 'get_{}_var'.format(attr), lambda self: getattr(self, field))
setattr(cls, attr,
property(lambda self: getattr(self, field).get(),
lambda self, value: getattr(self, field).set(value)))
def settings_class(cls):
""" this is the decorator function for SettingsBase subclasses.
it adds getters for the tk-stringvars and properties. it reads the
names described in the class-variable _SETTINGS.
"""
for name in cls._SETTINGS:
_add_var_getter_property(cls, name)
return cls
class SettingsBase:
""" this is the base class for a settings class. it automatically
adds fields to the class described in the class variable _SETTINGS.
when you subclass SettingsBase you should overwrite _SETTINGS.
a minimal example could look like this:
#settings_class
class MySettings(SettingsBase):
_SETTINGS = {
'x': 42,
'y': 23}
this would result in a class with a _x tk-intvar and a _y tk-doublevar
field with the getters get_x_var() and get_y_var() and the properties
x and y.
"""
_SETTINGS = {}
def __init__(self, **kwargs):
""" creates the fields described in _SETTINGS and initialize
eighter from the kwargs or from the default values
"""
super().__init__()
fields = self._SETTINGS.copy()
if kwargs:
for key in kwargs:
if key in fields:
typ = type(fields[key])
fields[key] = typ(kwargs[key])
else:
raise KeyError(key)
for key in fields:
value = fields[key]
typ = type(value)
name = '_' + key
if typ is int:
var = tk.IntVar()
elif typ is str:
var = tk.StringVar()
elif typ is bool:
var = tk.BooleanVar()
elif typ is float:
var = tk.DoubleVar()
else:
raise TypeError(typ)
var.set(value)
setattr(self, name, var)
after that my settings classes simply look like this:
#settings_class
class _CanvasSettings(SettingsBase):
_SETTINGS = {
'size_x': 320,
'size_y': 240,
'lock_ratio': True
}
It's certainly possible to do what you want, using setattr to bind the functions and property as attributes of the class object:
def add_getter_setter_property(cls, attrib_name):
escaped_name = "_" + attrib_name
setattr(cls, "get_{}_var".format(attrib_name),
lambda self: getattr(self, escaped_name))
setattr(cls, attrib_name,
property(lambda self: getattr(self, escaped_name).get()
lambda self, value: getattr(self, escaped_name).set(value)))
Here I'm skipping giving names to the getter and setter methods used by the property. You could add them to the class if you really want to, but I think it's probably unnecessary.
The tricky bit may actually be finding which attribute names you need to apply this to. Unlike in your example, you can't iterate over a class object to get its attributes.
The easiest solution (from the implementation standpoint) would be to require the class to specify the names in a class variable:
def my_settings_class(cls):
for field in cls._settings_vars:
add_getter_setter_and_property(cls, field)
return cls
#my_settings_class
class _CanvasSettings:
_settings_vars = ["size_x", "size_y", "lock_ratio"]
def __init__(self, **kwargs):
super().__init__()
self._size_x = _int(kwargs, 'size_x', 320)
self._size_y = _int(kwargs, 'size_y', 240)
self._lock_ratio = _bool(kwargs, 'lock_ratio', True)
A more user-friendly approach might use dir or vars to examine the classes variables and pick out the ones that need to be wrapped automatically. You could use isinstance to check if the value has a specific type, or look for a specific pattern in the attribute name. I don't know what is best for your specific use, so I'll leave this up to you.
Decorator for the class.
def add_get_set(cls):
for prop in cls.PROPERTIES:
# Note cannot be "lambda self: getattr(self, prop)" because of scope prop changes to be the last item in PROPERTIES
setattr(cls, "get"+prop, lambda self, attr=prop: getattr(self, attr))
return cls
#add_get_set
class _CanvasSettings:
PROPERTIES = ["_size_x", "_size_y", "_lock_ratio"]
def __init__(self, **kwargs):
super().__init__()
for prop in self.PROPERTIES:
setattr(self, prop, 0)
c = _CanvasSettings()
print(c.get_size_y())
You could just set the functions as variables
class _CanvasSettings:
def __init__(self, **kwargs):
super().__init__()
self._size_x = _int(kwargs, 'size_x', 320)
self._size_y = _int(kwargs, 'size_y', 240)
self._lock_ratio = _bool(kwargs'lock_ratio', True)
for variable in ["_size_x", "_size_y", "_lock_ratio"]:
setattr(self, "get"+variable, lambda: getattr(self, variable))
# bind the method (Not sure if binding the method gets you anything)
#setattr(self, "get"+variable, (lambda self: getattr(self, variable)).__get__(self, self.__class__))
Alternate
class _CanvasSettings:
def __init__(self, **kwargs):
super().__init__()
self._size_x = _int(kwargs, 'size_x', 320)
self._size_y = _int(kwargs, 'size_y', 240)
self._lock_ratio = _bool(kwargs'lock_ratio', True)
for variable in dir(self):
if variable.startswith("_") and not variable.startswith("__"):
self.__dict__["get"+variable] = lambda: getattr(self, variable)
As an alternative to automating making properties you could overload __getattr__ and __setattr__ to detect when a private field is available with an appropriate getter or setter method:
class Field: # so I could test it.
def __init__(self,args,name,default):
self.name = name
self.value = default
def get(self):
return self.value
def set(self,value):
self.value = value
class CanvasSettings:
def __init__(self, **kwargs):
super().__init__()
self._size_x = Field(kwargs, 'size_x', 320)
self._size_y = Field(kwargs, 'size_y', 240)
self._lock_ratio = Field(kwargs, 'lock_ratio', True)
def __getattr__(self, attr):
private_name = "_" + attr
field = object.__getattribute__(self, private_name) #this will fail for non-special attributes
getter = getattr(field,"get",None)
if getter is None:
raise AttributeError("Private member did not have getter") #may want to change the error handling
else:
return getter()
def __setattr__(self,attr, value):
private_name = "_" + attr
try:
field = getattr(self,private_name)
except AttributeError:
# if there is no private field or there is but no setter
# resort back to defaualt behaviour.
return super().__setattr__(attr,value)
else:
setter = getattr(field, "set", None)
if setter is None:
raise AttributeError("private field does not have a setter")
setter(value)
Then when ever you try to get or set thing.size_x it will first look for a thing._size_x and check for an appropriate method, here is a demo:
>>> thing = CanvasSettings()
>>> thing._size_x.value
320
>>> thing.size_x
320
>>> thing.size_x = 5
>>> 5 == thing.size_x == thing._size_x.value
True
Checking for an existing field every time you retrieve the attribute may have penalties to performance but I just wanted to offer this as an alternative if you have many classes with private fields that fit this model.
I have a number of similar fields in one of my classes modelling json data. All fields are initialized to None to help static tools know they exist then helper functions help initialize them based on a piece of json data that they are modelling (The SecondHandSongs API if you want to know).
Some pieces of data only retrieves the uri of extra data you have to fetch. So I want to use the old trick of initializing a hidden variable to None and fetching/decoding data on first request. But setattr(self.__class__) looks ugly.
Is there any nicer way to do (setting property dynamically in python)?
def _initialize_url_fields(self, attrNamesToFactoryFunction, json_data):
for (name, factoryFunction) in attrNamesToFactoryFunction.iteritems():
try:
url = json_data[name]
except KeyError:
continue
setattr(self, name + "_url", url)
setattr(self, "_" + name, None)
setattr(self.__class__, name, property(lambda s: s._getter("_" + name, url, factoryFunction)))
def _getter(self, hidden_prop_name, url, factoryFunction):
if not getattr(self, hidden_prop_name):
json_data = SHSDataAcess.getSHSData(url)
setattr(self, hidden_prop_name, factoryFunction(json_data))
return getattr(self, hidden_prop_name)
edit:
I've just realized I was trying to set a property in a instance method called from init
. As could be expected it failed the second time around.
edit 2:
Here's how I fixed it after realizing that I was setting a property per object(impossible if not a singleton class)
class ShsData(object):
def _initialize_url_fields(self, attrNamesToFactoryFunctions, json_data):
for (name, factoryFunction) in attrNamesToFactoryFunctions.items():
self._getter_factory_functions[name] = factoryFunction
uri = None
try:
uri = json_data[name]
except KeyError:
pass
setattr(self, name + "_uri", uri)
setattr(self, "_" + name, None)
def _fetch_shs_data_on_first_access_getter(base_prop_name):
def getter(self):
factoryFunction = self._getter_factory_functions[base_prop_name]
hidden_prop_name = "_" + base_prop_name
uri_prop_name = base_prop_name + "_uri"
if not getattr(self, hidden_prop_name):
if getattr(self, uri_prop_name):
json_data = SHSDataAcess.getSHSData(getattr(self, uri_prop_name))
setattr(self, hidden_prop_name, factoryFunction(json_data))
else:
return None
return getattr(self, hidden_prop_name)
return getter
class ShsArtist(ShsData):
performances_data = property(_fetch_shs_data_on_first_access_getter("performances"))
creditedWorks_data = property(_fetch_shs_data_on_first_access_getter("creditedWorks"))
releases_data = property(_fetch_shs_data_on_first_access_getter("releases"))
def __init__(self, json_data):
...
self._initialize_url_fields({"performances": lambda xs: [ShsPerformance(x) for x in xs],
"creditedWorks": lambda xs: [ShsWork(x) for x in xs],
"releases": lambda xs: [ShsRelease(x) for x in xs]},
json_data)
I might subclass property to handle your common cases. Something like this:
class shs_klass_property(property):
def __init__(self, name, klass):
self.name = name
self.klass = klass
self.cached_name = '_%s' % name
super(shs_klass_property, self).__init__(self.compute)
def compute(self, obj):
if not hasattr(obj, self.cached_name):
if self.name in obj._json_data:
# if needed handle cases where this isn't a list
val = [self.klass(x) for x in obj._json_data[self.name]]
else:
val = None
setattr(obj, self.cached_name, val)
return getattr(obj, self.cached_name)
class ShsData(object):
def __init__(self, json_data):
self._json_data = json_data
class ShsWork(ShsData):
pass
class ShsArtist(ShsData):
works = shs_klass_property('works', ShsWork)
If you always want to set the uri as well, you could do something like:
# if you change this to pass in "_json_data" too,
# you'd have a simple general purpose delegation decorator
class shs_json_property(property):
def __init__(self, name):
self.name = name
super(shs_json_property, self).__init__(self.compute)
def compute(self, obj):
return obj._json_data.get(self.name, None)
# a helper to set both. not necessary but saves a line of code.
def shs_property_pair(name, klass):
return (shs_klass_property(name, klass),
shs_json_property(name))
class ShsArtist(ShsData):
works, works_uri = shs_property_pair('works', ShsWork)