Class extension in python like in swift? - python

I am new to python, coming from swift and I am wondering about the following. In swift if I would like to add a functionality to an an existing class, I can do something like this (as in the book example):
extension Double {
var km: Double { return self * 1_000.0 }
var m: Double { return self }
var cm: Double { return self / 100.0 }
var mm: Double { return self / 1_000.0 }
var ft: Double { return self / 3.28084 }
}
let oneInch = 25.4.mm
I really like this functionality and I was wondering if there is something similar in python, or there is some other way much better that I do not see and hence this does not make any sense in python.

For the sake of completeness, I think this is what you should do (In Swift as well!):
class Meter(float):
def to_km(self): return self * 1000.0
def to_m (self): return self
def to_cm(self): return self / 100.0
def to_mm(self): return self / 1000.0
def to_ft(self): return self / 3.28084
oneInch = Meter(25.4).to_mm()
print(oneInch)
Make it clear that your object represents a meter, and that you are converting it to something.
If you want some syntactic sugar, that I am not sure is helpful, you can override the item getter so you do not have to use ():
class Meter(float):
conversions = {
'km':1000,
'cm':.01,
'mm':.001,
'ft':1/3.28084
}
def __getattr__(self,x):
try:
return self*Meter.conversions[x]
except KeyError:
raise KeyError("No such conversion!")
oneInch = Meter(25.4).mm
print(oneInch)
Adding conversions is as simple as:
Meter.conversions['whatever'] = 123.123

No, you can't extend builtin classes like float or int.
>>> int.double = lambda self: self * 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'int'
(You can modify non-builtin classes though, but you really shouldn't, as code doing that is hard to reason about.)
Your particular example is better served by an unit library like pint -- using libraries like that prevents you from making $125 million mistakes doing math between metric and imperial units, too.

You can build child classes of parent classes. If you're writing your own code, sometimes this is a good idea, but extending something like a pandas dataframe is probably a bad idea
I didn't spend much time googling for you, but this is the best I found: https://docs.python.org/3/tutorial/classes.html
This is also some really bad code I wrote trying to explain class inheritance to my girlfriend. Note that the Car class will still have car.roll_up_windows() and car.roll_down_windows() and the __repr__:
class Plant(object):
def __init__(self, color, seeds):
self.color = color
self.seeds = seeds
class Fruit(Plant):
def __init__(self, color, defense):
self.color = color
self.seeds = True
self.defense = defense
class Vehicle(object):
def __init__(self, num_tires, engine_size, fuel_type):
self.num_tires = num_tires
self.engine_size = engine_size
self.fuel_type = fuel_type
self.windows = 'up'
def roll_down_windows(self):
self.windows = 'down'
def roll_up_windows(self):
self.windows = 'up'
def __repr__(self):
print("This is a vehicle. \nIt has {} tires. \nIts engine has {} liters. \nIt runs on {}. \nIt's windows are {}.".format(self.num_tires, self.engine_size, self.fuel_type, self.windows))
class Car(Vehicle):
def __init__(self, num_tires=4, engine_size, fuel_type):
self.num_tires = num_tires
self.engine_size = engine_size
self.fuel_type = fuel_type
self.windows = 'up'

Related

Confused by accessing class methods

I'm not new to OOP but I'm quite a noobie when it comes to python, I'm working on a framework that should help in the development of my work but I'm getting confused by how to access methods.
I'm on Python 3.10
class Dialogue(EngineComponent):
def __init__(self, name: str, directory=""):
self.__scenes = []
self.__directory = directory
super().__init__(name, "dialogue", ".dialogue.json")
def scene(self, tag: str):
scene = Dialogue.__Scene(tag)
self.__scenes.append(scene)
return scene
def export(self):
dialogues = {
"format_version": "1.17.0",
"minecraft:npc_dialogue": {
"scenes": []
}
}
for scene in self.__scenes:
dialogues["minecraft:npc_dialogue"]["scenes"].append(
scene.__export())
self.content(dialogues)
return super().export(self.__directory)
class __Scene():
def __init__(self, tag: str):
self.__buttons = []
self.__tag = tag
def properties(self, npc_name: str, text: str):
self.__npc_name, self.__npc_display_name = RawText(npc_name)
self.__text, self.__display_text = RawText(text)
return self
def button(self, button_name: str, commands: str or object):
button = self.__Button(button_name, commands)
self.__buttons.append(button)
return button
def __export(self):
buttons = []
for button in self.__buttons:
buttons.append(button.__export())
Language(f"npc_name.{self.__npc_name}={self.__npc_display_name}\n")
Language(f"npc_text.{self.__tag}={self.__display_text}\n")
return {
"scene_tag": self.__tag,
"npc_name": {"rawtext": [{"translate": f"npc_name.{self.__npc_name}"}]},
"text": {"rawtext": [{"translate": f"npc_text.{self.__tag}", "with": ["\n"]}]},
"buttons": buttons
}
class __Button():
def __init__(self, button_name: str, commands: str or Function):
self.__button_name = button_name
if isinstance(commands, Function):
self.__commands = f"function {commands.get_path()}"
else:
self.__commands = commands
def __export(self):
Language(f"{self.__button_name}={self.__button_name}\n")
return {
"name": self.__button_name,
"commands": [
self.__commands
]
}
I have written the class above to help with the generation of files, and actual usage of that class should be
d0 = Dialogue("my_dialogue")
scene0 = d0.scene("hello").properties("Jake","Hello traveler!\\nLooking for some tips?").button("Get Tips","/say tips")
d0.export()
Everything worked fine when I write all the export methods as export()
But I want to prevent people from using the inner classes export() methods for the __Scene and __Button so I'm following the PEP8 or so I read on other posts that I should just switch export() to __export(). It can still be used but someone familiar with python knows better not to
But when I do that I get this error AttributeError: '__Scene' object has no attribute '_Dialogue__export'
for scene in self.__scenes:
dialogues["minecraft:npc_dialogue"]["scenes"].append(
scene.__export())
And I can't figure out why?? I've just changed the name of the method and the way how I call it, why is it not working as intended?
I'm also using chained method calls as you can see in properties() and button() but also for a lot of other classes where I have so many methods. I'm wondering if there's a way to limit the reusability of each method but still allow chained calls for unused ones?

Is it possible to create an object that actualize its attributes when modified?

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?

Chainable dynamic methods with __getattr__?

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"

Python Class Objects/Attributes

I am learning python with an online course and have been doing fine, I have stumbled across and issue. Normally when having an issue i just google to find the answer or look up guides but the problem here is I'm not even sure what I'm looking for!
I currently have the below code. I have a task to make each workout intensity have a specific value, such as Low = 3, Medium = 6 and High = 12. Then I can find the calories burned via duration * the values dependant on the intensity and duration passed into the class.
What I don't know is how do I assign a value to a class method? I tried lists and Dictionarys and both are throwing errors. I tried writing and If statement to try if self.intensity(getattr) = "Low": Then x = 3, etc.
I really am not sure where to even start to find an answer hence asking you guys and girls.
The code currently is (I am aware pieces are missing also I'm currently only focusing on assigning values to the Intensity
class ExerciseSession:
def __init__(self, exercise, intensity, duration):
self.exercise = exercise
self.intensity = intensity
self.duration = duration
def get_exercise(self):
return self.exercise
def get_intensity(self):
return self.intensity
def get_duration(self):
return self.duration + " minutes"
def set_exercise(self, excersice):
self.set_exercise = exercise
def set_intensity(self, intensity):
self.set_intensity = intensity
def set_duration(self, duration):
self.set_duration = duration
new_exercise = ExerciseSession("Running", "Low", 60)
print(new_exercise.get_exercise())
print(new_exercise.get_exercise())
print(new_exercise.get_intensity())
print(new_exercise.get_duration())
new_exercise.set_exercise("Swimming")
new_exercise.set_intensity("High")
new_exercise.set_duration(30)
print(new_exercise.get_exercise())
print(new_exercise.get_intensity())
print(new_exercise.get_duration())
print(new_exercise.get_intensity())
print(new_exercise.get_duration())
new_exercise.set_exercise("Swimming")
new_exercise.set_intensity("High")
new_exercise.set_duration(30)
print(new_exercise.get_exercise())
print(new_exercise.get_intensity())
print(new_exercise.get_duration())
Am i just doing lists/dictionaries wrong within a class or am I missing something incredibly easy here. I understand classes and methods but it seems some things work slightly different when inside a class etc.
Firstly - are your setters implemented as they are in your question? If so, you appear to be trying to override your methods with a value. There is also a small typo in set_exercise(). Compare the below with your question:
def set_exercise(self, exercise):
self.exercise = exercise
def set_intensity(self, intensity):
self.intensity = intensity
def set_duration(self, duration):
self.duration = duration
You can then write your test statements outside of the class to figure out what value to use when calculating the number of calories consumed based on the intensity.
Alternatively, Python provides a neat way of encapsulating data without resorting to getter/setter methods through the #property decorator. The property can then be set and retrieved in a simple fashion. This is particularly useful in more complex situations where an attribute is derived from other attributes, meaning you will always access the most up-to-date attributes. This is covered in more detail here: "public" or "private" attribute in Python ? What is the best way?
class ExerciseSession:
def __init___(self, exercise, intensity, duration):
self._exercise = exercise
self._intensity = intensity
self._duration = duration
#property
def exercise(self):
return self._exercise
#property
def intensity(self):
if self._intensity == "Low":
out = 3
elif self._intensity == "Medium":
out = 6
elif self._intensity == "High":
out = 12
else:
out = None
return out
#property
def duration(self):
return self._duration
Note: the "_" before each instance attribute is used to indicate the internal attribute is conventionally private.
These properties can then be accessed as follows (note we do not have to call any methods, e.g. new_exercise.exercise()):
new_exercise = ExerciseSession("Running", "Low", 60)
print(new_exercise.exercise)
print(new_exercise.intensity)
print("{} minutes.".format(new_exercise.duration))
If you need to be able to update the type of exercise/duration/intensity on the object, rather than just creating a new one, you can add setter methods using the #.setter decorator, e.g.:
#exercise.setter
def exercise(self, value):
self._exercise = value
and these properties can be updated as:
new_exercise.exercise = "Swimming"
doing the same :-)
def calories_burned(self):
if self.intensity == "Low":
return 4 * self.duration
elif self.intensity == "Medium":
return 8 * self.duration
else:
return 12 * self.duration

Referring Enum members to each other

I defined the following Enum in Python:
class Unit(Enum):
GRAM = ("g")
KILOGRAM = ("kg", GRAM, 1000.0)
def __init__(self, symbol, base_unit = None, multiplier = 1.0):
self.symbol = symbol
self.multiplier = multiplier
self.base_unit = self if base_unit is None else base_unit
I would expect that
print(Unit.GRAM.base_unit)
print(Unit.KILOGRAM.base_unit)
will return
Unit.GRAM
Unit.GRAM
However, what I get is quite confusing
Unit.GRAM
g
Why is it so?
The way Python defines a class involves creating a new scope, processing a bunch of statements (variable assignments, function definitions, etc.), and then actually creating a class object based on the local variables which exist after all those statements have run. Nothing gets converted into Enum instances until that last step.
You could understand it somewhat like this:
def make_class_Unit():
GRAM = ("g")
KILOGRAM = ("kg", GRAM, 1000.0)
def __init__(self, symbol, base_unit = None, multiplier = 1.0):
self.symbol = symbol
self.multiplier = multiplier
self.base_unit = self if base_unit is None else base_unit
return make_class(name='Unit', base=Enum, contents=locals())
Unit = make_class_Unit()
Looking at it this way, hopefully you can tell that at the time when KILOGRAM is defined, GRAM is really just a string. It doesn't become a Unit instance until the last stage, where I call the (imaginary) make_class() function.1
1Even though the make_class function I used above doesn't actually exist under that name, it's not too different from what Python really does, which is calling the constructor of type or a metaclass (which in this case is the metaclass for Enums).
DavidZ explained the problem well.
The last bit that you need to solve this problem is this: when the __init__ of each member is being run, the Enum has been created -- so you can call it:
self.base_unit = self if base_unit is None else self.__class__(base_unit)

Categories

Resources