I'm trying to create a dynamic class in python that also has dynamic properties; but I'm struggling with this.
Here's a simple non-working sample:
class baseA(object):
def __init__(self):
self._a_info = 1
def dump(self, pref=""):
print("%s %d" % (pref, self._a_info))
def b_init(self, parent_class):
parent_class.__init__(self)
def _dump(self, pref=""):
print("%s: %d" % self._b_info)
attrs = {"__init__": b_init,
"dump": lambda self, pref: _dump(self, pref=pref)}
for field in ["field1", "field2"]:
attrs["_%s" % field] = field
attrs[field] = lambda self: getattr(self, "_%s" % f)
tmpb = type("baseB",
(baseA, ),
attrs)
t = tmpb()
t.dump(pref="Field: ")
Obviously, the above doesn't work. For one thing print(t.field1) will print an unbounded method warning, since attrs[prop] is a function and not a value. (I was hoping to simulate
what #property does to methods).
What I'm trying to do is to create a class dynamically while setting properties
for it. That said, I now realize that "attrs[prop] = lambda self: getattr(self, "_%s" % prop)
is wrong as that makes attrs[prop] a function.
Is it even possible to use the type() function to create a dynamic class that has
the following property getter/setters?
So like converting the following:
class baseB(baseA):
def __init__(self):
self._field1 = "field1"
self._field2 = "field2"
self._field3 = "field3"
#property
def field1(self):
return self._field1
#field1.setter
def field1(self, in_val):
self._field1 = in_val
#property
def field2(self):
return self._field2
#field2.setter
def field2(self, in_val):
self._field2 = in_val
#property
def field3(self):
return self._field3
#field3.setter
def field3(self, in_val):
self._field3 = in_val
to
type("baseB",
(baseA, ),
someattributes_dictionary)
?
If it was a one off script, then sure I'd just do the long way; but if I need
to dynamically create different classes with different properties, the typical
'class ...' will be tedious and cumbersome.
Any clarifications appreciated,
Ed.
--
[1] - https://www.python-course.eu/python3_classes_and_type.php
Related
I'm using the following class (it's from Airflow code, so I can't modify it):
class TriggerRule:
"""Class with task's trigger rules."""
ALL_SUCCESS = 'all_success'
ALL_FAILED = 'all_failed'
ALL_DONE = 'all_done'
ONE_SUCCESS = 'one_success'
ONE_FAILED = 'one_failed'
NONE_FAILED = 'none_failed'
NONE_FAILED_OR_SKIPPED = 'none_failed_or_skipped'
NONE_SKIPPED = 'none_skipped'
DUMMY = 'dummy'
_ALL_TRIGGER_RULES: Set[str] = set()
#classmethod
def is_valid(cls, trigger_rule):
"""Validates a trigger rule."""
return trigger_rule in cls.all_triggers()
#classmethod
def all_triggers(cls):
"""Returns all trigger rules."""
if not cls._ALL_TRIGGER_RULES:
cls._ALL_TRIGGER_RULES = {
getattr(cls, attr)
for attr in dir(cls)
if not attr.startswith("_") and not callable(getattr(cls, attr))
}
return cls._ALL_TRIGGER_RULES
Let's say I have this function:
def print_rule(rule):
print(rule)
and I want to type hint the parameter rule so it must be one of the rules listed in the TriggerRule class.
print_color(TriggerRule.ALL_FAILED) # OK
print_color('one_success') # I don't care if it complains or not
print_color('foo') # I want it to complain
Is this possible? I don't want to type it as str because I don't want to allow any string.
I'm using Python 3.8, but if this is possible in a newer version, I'd like to know as well.
I'm trying to find a good pattern for resolving methods from a class variable in such a way that the methods can be chained. The two requirements are:
Methods should be defined dynamically by a class variable.
These dynamic methods should return self so that they can be chained.
For example, I want to be able to do something like this:
color("WARNING").bold().yellow()
The code below gets pretty close:
class color:
colors = {
"fg":{"black":"30","red": "31","green": "32","yellow": "33","blue": "34","magenta": "35","cyan": "36","white": "37"},
"bg":{"black":"40","red": "41","green": "42","yellow": "43","blue": "44","magenta": "45","cyan": "46","white": "47"}
}
def __init__(self, text):
self.text = text
self.bright = "0"
self.fore = "39"
self.back = "49"
def __getattr__(self, name):
if name[-2:].lower() == "bg":
self.back = self.colors['bg'][name[:-2]]
elif name == "bold":
self.bright = 1
else:
self.fore = self.colors['fg'][name]
return self
def __repr__(self):
return f"\033[{self.bright};{self.fore};{self.back}m{self.text}\033[0m"
The problem is, this code sets the values when the attribute is accessed, as follows:
color("WARNING").bold.yellow
I feel like this is a "surprising" behavior, and one that should be avoided (read actions shouldn't change state). However, I'm not sure how to cleanly return a function prepopulated with the right values that will also return self. I've toyed with functools.partial and with using __setattr__() but I can't seem to get it right.
Note: This question is about correct patterns, not now to color text. The coloring of text is just a nice example.
Of course... I figured it out right after posting a question. Here's what I came up with:
from functools import partial
class color:
colors = {
"fg":{"black":"30","red": "31","green": "32","yellow": "33","blue": "34","magenta": "35","cyan": "36","white": "37"},
"bg":{"black":"40","red": "41","green": "42","yellow": "43","blue": "44","magenta": "45","cyan": "46","white": "47"}
}
def __init__(self, text):
self.text = text
self.bright = "0"
self.fore = "39"
self.back = "49"
def _set(self, name, val):
# This method just calls __setattr__ to actually change the value,
# but importantly, it returns self, so the methods can be chained.
self.__setattr__(name, val)
return self
def __getattr__(self, name):
# Return a partial function with the values already populated
if name[-2:].lower() == "bg":
return partial(self._set, "back", self.colors['bg'][name[:-2]])
elif name == "bold":
return partial(self._set, "bright", 1)
return partial(self._set, "fore", self.colors['fg'][name])
def __repr__(self):
return f"\033[{self.bright};{self.fore};{self.back}m{self.text}\033[0m"
I have a class which gets created through a constructor at runtime. Is there a way that I can prevent some of its attributes to be set to certain values after construction?
For example, in the below code, I'd like to raise an exception if the value for currRating is set to value greater than maxRating or lower than minRating. I can do that easily during instantiation, however, I am looking to do this after class has already been constructed.
class Machine(object):
def __init__(self,Name,maxRating,minRating,currRating=1):
self.name=name
self.maxRating = maxRating
self.minRating = minRating
self.currRating = currRating
I read through some of the previous replies on StackOverflow and understand that this might be a touchy topic for Python veterans. I am just interested in knowing if there is a non-hackish way of achieving this in Python or if this is one of those things that is not meant to be done in Python.
You can achieve this using the built in function property
Essentially, instead you create 'setter' methods for your currRating so that when that property is set by calling obj.currRating you can do your check to see if its within min and max:
For example:
class Machine(object):
def __init__(self, name, max_rating, min_rating, cur_rating=1):
self.max_rating = max_rating
self.min_rating = min_rating
self._cur_rating = cur_rating
#property
def cur_rating(self):
return self._cur_rating
#cur_rating.setter
def cur_rating(self, value):
if value > self.max_rating or value < self.min_rating:
raise Exception()
self._cur_rating = value
You will still access your variable as obj.cur_rating = 10
Can I try this way:
#!/usr/bin/python
class Machine(object):
def __init__(self, Name, maxRating, minRating, currRating=1):
self.name=Name
self.maxRating = maxRating
self.minRating = minRating
self.currRating = currRating
def __setattr__(self, name, value):
if name in ["maxRating", "minRating"]:
#raise TypeError, "You can't change this attribute: %s = %s" % (name, value)
print "You can't change this attribute: %s = %s" % (name, value)
self.__dict__[name] = value
obj = Machine("Grinder", 5, 1)
try:
#obj.maxRating = 6
obj.minRating = 6
except Exception as err:
print err
currRating = 2
print currRating
You can change the print to raise an exception.
I am defining models for my app and I need to a column named 'status' for various verification procedures. Here is a simplified user model.
class User
id(int)
name(str)
status(int) # 0- New 1-Active 2-Inactive 3-Reported 4-Deleted
I asked a fellow Python developer to review my code; and he suggested that I avoided 'magic numbers'. His solution is this:
class Choices:
#classmethod
def get_value(cls, key):
# get the string display if need to show
for k, v in cls.CHOICES:
if k == key:
return v
return ""
class UserStatusChoices(Choices):
NEW = 0
ACTIVE = 1
INACTIVE = 2
REPORTED = 3
DELETED = 4
CHOICES = (
(NEW, "NEW"),
(ACTIVE, "ACTIVE"),
(INACTIVE, "INACTIVE"),
(REPORTED, "REPORTED"),
(DELETED, "DELETED"),
)
Couldn't I use simple dictionaries instead? Does anyone see a good reason for 'class'y solution?
Building on Python Enum class (with tostring fromstring)
class Enum(object):
#classmethod
def tostring(cls, val):
for k,v in vars(cls).iteritems():
if v==val:
return k
#classmethod
def fromstring(cls, str):
return getattr(cls, str.upper(), None)
#classmethod
def build(cls, str):
for val, name in enumerate(str.split()):
setattr(cls, name, val)
class MyEnum(Enum):
VAL1, VAL2, VAL3 = range(3)
class YourEnum(Enum):
CAR, BOAT, TRUCK = range(3)
class MoreEnum(Enum):
pass
print MyEnum.fromstring('Val1')
print MyEnum.tostring(2)
print MyEnum.VAL1
print YourEnum.BOAT
print YourEnum.fromstring('TRUCK')
# Dodgy semantics for creating enums.
# Should really be
# MoreEnum = Enum.build("CIRCLE SQUARE")
MoreEnum.build("CIRCLE SQUARE")
print MoreEnum.CIRCLE
print MoreEnum.tostring(1)
print MoreEnum.tostring(MoreEnum.CIRCLE)
EDIT Added build class method so that a string could be used to build the enums.
Although there are probably better solutions out there.
I've been working on a way to get tests produced from a generator in nose to have descriptions that are customized for the specific iteration being tested. I have something that works, as long as my generator target method never tries to access self from my generator class. I'm seeing that all my generator target instances have a common test class instance while nose is generating a one-offed instance of the test class for each test run from the generator. This is resulting in setUp being run on each test instance nose creates, but never running on the instance the generator target is bound to (of course, the real problem is that I can't see how to bind the nose-created instance to the generator target). Here's the code I'm using to try to figure this all out (yes, I know the decorator would probably be better as a callable class, but nose, at least version 1.2.1 that I have, explicitly checks that tests are either functions or methods, so a callable class won't run at all):
import inspect
def labelable_yielded_case(case):
argspec = inspect.getargspec(case)
if argspec.defaults is not None:
defaults_list = [''] * (len(argspec.args) - len(argspec.defaults)) + argspec.defaults
else:
defaults_list = [''] * len(argspec.args)
argument_defaults_list = zip(argspec.args, defaults_list)
case_wrappers = []
def add_description(wrapper_id, argument_dict):
case_wrappers[wrapper_id].description = case.__doc__.format(**argument_dict)
def case_factory(*factory_args, **factory_kwargs):
def case_wrapper_wrapper():
wrapper_id = len(case_wrappers)
def case_wrapper(*args, **kwargs):
args = factory_args + args
argument_list = []
for argument in argument_defaults_list:
argument_list.append(list(argument))
for index, value in enumerate(args):
argument_list[index][1] = value
argument_dict = dict(argument_list)
argument_dict.update(factory_kwargs)
argument_dict.update(kwargs)
add_description(wrapper_id, argument_dict)
return case(*args, **kwargs)
case_wrappers.append(case_wrapper)
case_wrapper.__name__ = case.__name__
return case_wrapper
return case_wrapper_wrapper()
return case_factory
class TestTest(object):
def __init__(self):
self.data = None
def setUp(self):
print 'setup', self
self.data = (1,2,3)
def test_all(self):
for index, value in enumerate((1,2,3)):
yield self.validate_equality(), index, value
def test_all_again(self):
for index, value in enumerate((1,2,3)):
yield self.validate_equality_again, index, value
#labelable_yielded_case
def validate_equality(self, index, value):
'''element {index} equals {value}'''
print 'test', self
assert self.data[index] == value, 'expected %d got %d' % (value, self.data[index])
def validate_equality_again(self, index, value):
print 'test', self
assert self.data[index] == value, 'expected %d got %d' % (value, self.data[index])
validate_equality_again.description = 'again'
When run through nose, the again tests work just fine, but the set of tests using the decorated generator target all fail because self.data is None (because setUp is never run because the instance of TestTest stored in the closures is not the instances run by nose). I tried making the decorator an instance member of a base class for TestTest, but then nose threw errors about having too few arguments (no self) passed to the unbound labelable_yielded_case. Is there any way I can make this work (short of hacking nose), or am I stuck choosing between either not being able to have the yield target be an instance member or not having per-test labeling for each yielded test?
Fixed it (at least for the case here, though I think I got it for all cases). I had to fiddle with case_wrapper_wrapper and case_wrapper to get the factory to return the wrapped cases attached to the correct class, but not bound to any given instance in any way. I also had another code issue because I was building the argument dict in wrapper wrapper, but then not passing it to the case. Working code:
import inspect
def labelable_yielded_case(case):
argspec = inspect.getargspec(case)
if argspec.defaults is not None:
defaults_list = [''] * (len(argspec.args) - len(argspec.defaults)) + argspec.defaults
else:
defaults_list = [''] * len(argspec.args)
argument_defaults_list = zip(argspec.args, defaults_list)
case_wrappers = []
def add_description(wrapper_id, argument_dict):
case_wrappers[wrapper_id].description = case.__doc__.format(**argument_dict)
def case_factory(*factory_args, **factory_kwargs):
def case_wrapper_wrapper():
wrapper_id = len(case_wrappers)
def case_wrapper(*args, **kwargs):
argument_list = []
for argument in argument_defaults_list:
argument_list.append(list(argument))
for index, value in enumerate(args):
argument_list[index][1] = value
argument_dict = dict(argument_list)
argument_dict.update(kwargs)
add_description(wrapper_id, argument_dict)
return case(**argument_dict)
case_wrappers.append(case_wrapper)
case_name = case.__name__ + str(wrapper_id)
case_wrapper.__name__ = case_name
if factory_args:
setattr(factory_args[0].__class__, case_name, case_wrapper)
return getattr(factory_args[0].__class__, case_name)
else:
return case_wrapper
return case_wrapper_wrapper()
return case_factory
class TestTest(object):
def __init__(self):
self.data = None
def setUp(self):
self.data = (1,2,3)
def test_all(self):
for index, value in enumerate((1,2,3)):
yield self.validate_equality(), index, value
#labelable_yielded_case
def validate_equality(self, index, value):
'''element {index} equals {value}'''
assert self.data[index] == value, 'expected %d got %d' % (value, self.data[index])