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"
Related
In this example, what should be done so that print(left_hand.number_of_fingers) returns 4 and not 5?
class Hand:
def __init__(self, fingers:list):
self.fingers = fingers
self.number_of_fingers = len(fingers)
left_hand = Hand(["thumb", "index", "middle", "ring", "pinkie"])
left_hand.fingers.pop()
print(left_hand.number_of_fingers) # I want this to actualize and be 4, not 5
I found a solution using #property
class Hand:
def __init__(self, fingers:list):
self.fingers = fingers
#property
def number_of_fingers(self):
return len(self.fingers)
But I'm not satisfied because of a computational power issue, if computing number_of_fingers was expensive we would only want to compute it whenever fingers is modified, not every time the user asks for the attribute number_of_fingers.
Now I found a not elegant solution to solve the issue with computational power:
class Hand:
def __init__(self, fingers:list):
self.fingers = fingers
self.old_fingers = fingers
self.number_of_fingers = len(fingers)
def get_number_of_fingers(self):
if self.fingers != self.old_fingers:
self.old_fingers = self.fingers
self.number_of_fingers = len(self.fingers)
return self.number_of_fingers
The problem is that the underlying list in your Hand class, i.e. self.fingers, is not sufficiently encapsulated so that any user can be modifying it, for example by calling left_hand.fingers.pop() or even by assigning to it a new list. Therefore, you cannot assume that it has not been modified between calls to number_of_fingers and therefore you have no choice but to compute its length in that call.
The solution is to control what clients of your class can and cannot do. The easiest way to do this is by using name mangling. That is, you prefix your attribute names with two leading underscore characters. This makes it difficult (although not impossible) for clients of your class to access these attributes from outside of the class (we assume that your users are not intentionally malicious). And therefore we have to provide now a pop method:
class Hand:
def __init__(self, fingers:list):
self.__fingers = fingers
self.__number_of_fingers = len(fingers)
def pop(self):
assert(self.__fingers)
self.__number_of_fingers -= 1
return self.__fingers.pop()
#property
def number_of_fingers(self):
return self.__number_of_fingers
left_hand = Hand(["thumb", "index", "middle", "ring", "pinkie"])
print(left_hand.pop())
print(left_hand.number_of_fingers)
Prints:
pinkie
4
I am not suggesting that you actually do the following, but if you wanted to you can get more elaborate by creating special class decorators #Private and #Public that will wrap your class in a new class and check access to your attributes ensuring that you are not accessing those attributes defined to be private. You use either the #Private decorator to define those attributes/methods that are private (everything else is considered public) or the #Public decorator to define those attributes/methods that are public (everything else is considered private), but not both. You would typically name your private attributes with a leading single underscore, which is the convention that tells users that the attribute/method is to be considered private.
This is meant more to catch inadvertent access of attributes that are meant to be private. If you execute the code with the -O Python flag, then no runtime checks will be made.
def accessControl(failIf):
def onDecorator(aClass):
if not __debug__:
return aClass
else:
class onInstance:
def __init__(self, *args, **kargs):
self.__wrapped = aClass(*args, **kargs)
def __getattr__(self, attr):
if failIf(attr):
raise TypeError('private attribute fetch: ' + attr)
else:
return getattr(self.__wrapped, attr)
def __setattr__(self, attr, value):
if attr == '_onInstance__wrapped':
self.__dict__[attr] = value
elif failIf(attr):
raise TypeError('private attribute change: ' + attr)
else:
setattr(self.__wrapped, attr, value)
return onInstance
return onDecorator
def Private(*attributes):
return accessControl(failIf=(lambda attr: attr in attributes))
def Public(*attributes):
return accessControl(failIf=(lambda attr: attr not in attributes))
#Private('_fingers', '_number_of_fingers')
class Hand:
def __init__(self, fingers:list):
self._fingers = fingers
self._number_of_fingers = len(fingers)
def pop(self):
assert(self._fingers)
self._number_of_fingers -= 1
return self._fingers.pop()
#property
def number_of_fingers(self):
return self._number_of_fingers
left_hand = Hand(["thumb", "index", "middle", "ring", "pinkie"])
print(left_hand.pop())
print(left_hand.number_of_fingers)
# Thsis will throw an exception:
print(left_hand._fingers)
Prints:
pinkie
4
Traceback (most recent call last):
File "C:\Booboo\test\test.py", line 50, in <module>
print(left_hand._fingers)
File "C:\Booboo\test\test.py", line 9, in __getattr__
raise TypeError('private attribute fetch: ' + attr)
TypeError: private attribute fetch: _fingers
Update
This is the OP's approach using a cache:
class Hand:
def __init__(self, fingers:list):
self._cache = {}
self.fingers = fingers
def get_number_of_fingers(self):
fingers = tuple(self.fingers) # can be key of a dictionary
fingers_length = self._cache.get(fingers)
if fingers_length:
print(self.fingers, 'in cache')
return fingers_length
fingers_length = len(fingers)
self._cache[fingers] = fingers_length
return fingers_length
left_hand_fingers = ["thumb", "index", "middle", "ring", "pinkie"]
right_hand_fingers = ["thumb", "middle", "ring", "pinkie"]
hand = Hand(left_hand_fingers)
print(hand.get_number_of_fingers())
hand.fingers = right_hand_fingers
print(hand.get_number_of_fingers())
hand.fingers = left_hand_fingers
print(hand.get_number_of_fingers())
hand.fingers = right_hand_fingers
print(hand.get_number_of_fingers())
hand.fingers = left_hand_fingers
print(hand.get_number_of_fingers())
Prints:
5
4
['thumb', 'index', 'middle', 'ring', 'pinkie'] in cache
5
['thumb', 'middle', 'ring', 'pinkie'] in cache
4
['thumb', 'index', 'middle', 'ring', 'pinkie'] in cache
5
So here in the first code (without using #property), you will get the output as 5 and not 4, because you are simply assigning the value of len(fingers) to number_of_fingers attribute while initialising a Hand object, and number_of_fingers attribute is not getting linked to fingers.
So even if left_hand.fingers is modified in between the code, it will have no effect on the value of number_of_fingers. One cannot change this behaviour.
Also you don't need that #property, I tested and found that there will be no error if it is not written.
And finally coming to
But I'm not satisfied, because if computing number_of_fingers was expensive we would only want to compute it whenever fingers is modified, not every time the user asks for the attribute number_of_fingers.
Where do you need so much computing power?
Does RobotFramework have legal way to register my own variable finder? For example, i want to register finder that resolves variables thats name starts with #. With robotframework==3.1 I was able to achieve this with code like this:
import robot
from robot.variables.finders import VariableFinder
class MyVariableResolver:
identifiers = '$'
def find(self, name):
if name[2] == '#':
return f'My resolution for {name}'
else:
raise KeyError(name)
VariableFinder._finders = property(
lambda self: (MyVariableResolver(),) + self.__dict__['_finders'],
lambda self, value: self.__dict__.setdefault('_finders', value)
)
class MyLibrary:
pass
With robotframework==4.0 my solution was broken and I had to change MyVariableResolver to:
from robot.variables.finders import NOT_FOUND
# other imports
class MyVariableResolver:
identifiers = '$'
def find(self, name):
if name[2] == '#':
return f'My resolution for {name}'
else:
return NOT_FOUND
But this code still looks very ugly (especially VariableFinder._finders = property(...). Is there a "right" way to do what I want?
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
The point of the library I'm creating is to return the Hexadecimal value of a colour when you enter the name of the colour.
The above program works fine with print although it doesn't return a value as soon as print is replaced with return.
But the entire point of returning a value is gone as it can not be used in conjunction with other programs.
return("#F2F3F4") doesn't work
And yes I tried it without the brackets and it doesn't make any difference.
Hope you can figure out the problem. Thank you in advance!
class ColourConst():
def __init__(self, colour):
col = ""
#Shades of White
def Anti_flash_white():
print("#F2F3F4")
def Antique_white():
print("#FAEBD7")
def Beige():
print("#F5F5DC")
def Blond():
print("#FAF0BE")
ColourCon = {
#Shades of White
"Anti-flash white": Anti_flash_white,
"Antique white": Antique_white,
"Beige": Beige,
"Blond" : Blond
}
myfunc = ColourCon[colour]
myfunc()
ColourConst("Anti-flash white")
It does return a value if you use return, but unless you also use print, it won't print it.
class ColourConst():
def __init__(self, colour):
def Anti_flash_white():
return "#F2F3F4" # return here
def Antique_white():
return "#FAEBD7" # and here
def Beige():
return "#F5F5DC" # and here
def Blond():
return "#FAF0BE" # you get the point...
ColourCon = {
"Anti-flash white": Anti_flash_white,
"Antique white": Antique_white,
"Beige": Beige,
"Blond" : Blond
}
myfunc = ColourCon[colour]
print(myfunc()) # add print here
ColourConst("Anti-flash white")
Having said that, this is a pretty horrible way of doing this. First, this is the constructor of a class, which by definition can only return the newly created instance of that class, self. Instead, you can just make it a function returning the value, and print the value when you call the function, making it much more reusable. Also, instead of mapping color names to functions, each returning the value, you can just map names to values directly.
def colour_const(name):
colour_codes = {
"Anti-flash white": "#F2F3F4",
"Antique white": "#FAEBD7",
"Beige": "#F5F5DC",
"Blond" : "#FAF0BE"
}
return colour_codes.get(name, "unknown color")
print(colour_const("Anti-flash white"))
Note: I see that I need to more clearly work out what it is that I want each property/descriptor/class/method to do before I ask how to do it! I don't think my question can be answered at this time. Thanks all for helping me out.
Thanks to icktoofay and BrenBarn, I'm starting to understand discriptors and properties, but now I have a slightly harder question to ask:
I see now how these work:
class Blub(object):
def __get__(self, instance, owner):
print('Blub gets ' + instance._blub)
return instance._blub
def __set__(self, instance, value):
print('Blub becomes ' + value)
instance._blub = value
class Quish(object):
blub = Blub()
def __init__(self, value):
self.blub = value
And how a = Quish('one') works (produces "Blub becomes one") but take a gander at this code:
import os
import glob
class Index(object):
def __init__(self, dir=os.getcwd()):
self.name = dir #index name is directory of indexes
# index is the list of indexes
self.index = glob.glob(os.path.join(self.name, 'BatchStarted*'))
# which is the pointer to the index (index[which] == BatchStarted_12312013_115959.txt)
self.which = 0
# self.file = self.File(self.index[self.which])
def get(self):
return self.index[self.which]
def next(self):
self.which += 1
if self.which < len(self.index):
return self.get()
else:
# loop back to the first
self.which = 0
return None
def back(self):
if self.which > 0:
self.which -= 1
return self.get()
class File(object):
def __init__(self, file):
# if the file exists, we'll use it.
if os.path.isfile(file):
self.name = file
# otherwise, our name is none and we return.
else:
self.name = None
return None
# 'file' attribute is the actual file object
self.file = open(self.name, 'r')
self.line = Lines(self.file)
class Lines(object):
# pass through the actual file object (not filename)
def __init__(self, file):
self.file = file
# line is the list if this file's lines
self.line = self.file.readlines()
self.which = 0
self.extension = Extension(self.line[self.which])
def __get__(self):
return self.line[self.which]
def __set__(self, value):
self.which = value
def next(self):
self.which += 1
return self.__get__()
def back(self):
self.which -= 1
return self.__get__()
class Extension(object):
def __init__(self, lineStr):
# check to make sure a string is passed
if lineStr:
self.lineStr = lineStr
self.line = self.lineStr.split('|')
self.pathStr = self.line[0]
self.path = self.pathStr.split('\\')
self.fileStr = self.path[-1]
self.file = self.fileStr.split('.')
else:
self.lineStr = None
def __get__(self):
self.line = self.lineStr.split('|')
self.pathStr = self.line[0]
self.path = self.pathStr.split('\\')
self.fileStr = self.path[-1]
self.file = self.fileStr.split('.')
return self.file[-1]
def __set__(self, ext):
self.file[-1] = ext
self.fileStr = '.'.join(self.file)
self.path[-1] = fileStr
self.pathStr = '\\'.join(self.path)
self.line[0] = self.pathStr
self.lineStr = '|'.join(self.line)
Firstly, there may be some typos in here because I've been working on it and leaving it half-arsed. That's not my point. My point is that in icktoofay's example, nothing gets passed to Blub(). Is there any way to do what I'm doing here, that is set some "self" attributes and after doing some processing, taking that and passing it to the next class? Would this be better suited for a property?
I would like to have it so that:
>>> i = Index() # i contains list of index files
>>> f = File(i.get()) # f is now one of those files
>>> f.line
'\\\\server\\share\\folder\\file0.txt|Name|Sean|Date|10-20-2000|Type|1'
>>> f.line.extension
'txt'
>>> f.line.extension = 'rtf'
>>> f.line
'\\\\server\\share\\folder\\file0.rtf|Name|Sean|Date|10-20-2000|Type|1'
You can do that, but the issue there is less about properties/descriptors and more about creating classes that give the behavior you want.
So, when you do f.line, that is some object. When you do f.line.extension, that is doing (f.line).extension --- that is, it first evalautes f.line and then gets the extension attribute of whatever f.line is.
The important thing here is that f.line cannot know whether you are later going to try to access its extension. So you can't have f.line do one thing for "plain" f.line and another thing for f.line.extension. The f.line part has to be the same in both, and the extension part can't change that.
The solution for what you seem to want to do is to make f.line return some kind of object that in some way looks or works like a string, but also allows setting attributes and updating itself accordingly. Exactly how you do this depends on how much you need f.lines to behave like a string and how much you need it to do other stuff. Basically you need f.line to be a "gatekeeper" object that handles some operations by acting like a string (e.g., you apparently want it to display as a string), and handles other objects in custom ways (e.g., you apparently want to be able to set an extension attribute on it and have that update its contents).
Here's a simplistic example:
class Line(object):
def __init__(self, txt):
self.base, self.extension = txt.split('.')
def __str__(self):
return self.base + "." + self.extension
Now you can do:
>>> line = Line('file.txt')
>>> print line
file.txt
>>> line.extension
'txt'
>>> line.extension = 'foo'
>>> print line
file.foo
However, notice that I did print line, not just line. By writing a __str__ method, I defined the behavior that happens when you do print line. But if you evaluate it "raw" without printing it, you'll see it's not really a string:
>>> line
<__main__.Line object at 0x000000000233D278>
You could override this behavior as well (by defining __repr__), but do you want to? That depends on how you want to use line. The point is that you need to decide what you want your line to do in what situations, and then craft a class that does that.