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?
Related
I have the following class:
class MessageContext:
def __init__(self, raw_packet, packet_header, message_header, message_index):
self.raw_packet = raw_packet
self.pkthdr = packet_header
self.msghdr = message_header
self.msgidx = message_index
self.msg_seqno = packet_header.seqno + message_index
And a function that creates objects using the above class:
def parsers(data):
...
context = MessageContext(None, PacketAdapter(), msghdr, 0)
self.on_message(rawmsg, context)
I am trying to recreate context, and when i set a breakpoint just after it and print context, I get:
<exchanges.protocols.blahblah.MessageContext object at 0x7337211520>
I have left out quite a bit of code as it is very long, but if any more information is needed I am happy to provide of course.
Here is what I get when I print the arguments of MessageContext:
print(PacketAdapter()) -> <exchanges.blahblah.PacketAdapter object at 0x7f60929e1820>
Following the comments below, the PacketAdapter() class looks like this:
class PacketAdapter:
def __init__(self):
self.seqno = 0
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 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'
I want to access variable of other Class.
Static variable of other Class was very good accessed.
But dynimic changed variable value of ther Class was not good accessed.
Why I can't get the changed variable value?
bl_info = {
"name": "New Object",
"author": "Your Name Here",
"version": (1, 0),
"blender": (2, 75, 0),
"location": "View3D > Add > Mesh > New Object",
"description": "Adds a new Mesh Object",
"warning": "",
"wiki_url": "",
"category": "Add Mesh",
}
import bpy
class SelectFace(bpy.types.Operator):
bl_idname = "object.d0"
bl_label = "Select Face"
selected_faces = 2
def __init__(self):
self.selected_faces = 3
def execute(self, context):
print("self.selected_faces: ", self.selected_faces)
self.selected_faces += 1
bpy.ops.object.d1('INVOKE_DEFAULT')
return {'FINISHED'}
class OperationAboutSelectedFaces(bpy.types.Operator):
""" Test dialog. """
bl_idname = "object.d1"
bl_label = "Test dialog"
F_num = bpy.props.IntProperty(name="be Selected face", default=1)
#classmethod
def poll(self, context):
obj = context.object
return(obj and obj.type == 'MESH' and context.mode == 'OBJECT')
def invoke(self, context, event):
# This block code is Not Work! --- TypeError: bpy_struct.__new__(type): expected a single argument.
testInstance = SelectFace() # why not work?
print("testInstance.selected_faces: ", testInstance.selected_faces)
self.F_num = testInstance.selected_faces
# This block code is nice Work!
testInstance = SelectFace.selected_faces
print("testInstance: ", testInstance)
self.F_num = testInstance
return context.window_manager.invoke_props_dialog(self)
def execute(self, context):
context.active_object.data.polygons [self.F_num].select = True
return {'FINISHED'}
def register():
bpy.utils.register_class(SelectFace)
bpy.utils.register_class(OperationAboutSelectedFaces)
def unregister():
bpy.utils.unregister_class(SelectFace)
bpy.utils.unregister_class(OperationAboutSelectedFaces)
if __name__ == "__main__":
register()
bpy.ops.object.d0()
An operator in blender is used to perform an action. While we use a class to define that action and related properties, we shouldn't treat them as normal python classes. The properties of an operator should be used to adjust the action peformed, not to hold variable data.
As the operators properties control the result of the operator, they are used by blender to perform undo/redo steps. These properties are also adjustable by the user using the operator properties panel by pressing F6 and can also be found at the bottom of the toolbar region.
Add bl_options = {'REGISTER', 'UNDO'} to your operator to allow a user to adjust your operator. You can also customise the display within this panel by giving your operator a draw(self,context) method.
To control how an operator performs it's task when we call it directly, we can add the properties to the operator call -
bpy.ops.object.d1(F_num=4, val2=3.6)
If you are adding an operator button to a panel you can use -
row.operator('object.d1').F_num = 4
or if you need to set multiple values you can use -
op = row.operator('object.d1')
op.F_num = 4
op.val2 = 3.6
The example you provided uses a property that appears to only be valid for one object, if the user selects another object it will no longer be valid. This property would work better as an object property, you can add a property to the object class (or several others listed as subclasses of ID) by adding it in your addons register() and removing it in unregister()
def register():
bpy.types.Object.selected_faces = bpy.props.IntProperty()
def unregister():
del bpy.types.Object.selected_faces
For that example you could even count the selected faces when you needed the value -
selected_faces_count = len([f for f in obj.data.polygons if f.select])
I assume that
testInstance = SelectFace() # why not work?
is the real question.
see:
https://www.blender.org/api/blender_python_api_2_60a_release/info_overview.html
seems it is not expected that you write code that creates an instance of an bpy.types.Operator. Perhaps Blender handles bpy.types.Operator sub class creation in its own way.
"Notice these classes don’t define an init(self) function. While init() and del() will be called if defined, the class instances lifetime only spans the execution. So a panel for example will have a new instance for every redraw, for this reason there is rarely a cause to store variables in the panel instance. Instead, persistent variables should be stored in Blenders data so that the state can be restored when blender is restarted."
see also, Property Definitions: https://www.blender.org/api/blender_python_api_2_66a_release/bpy.props.html