I have a class as such:
class Line:
def __init__(self, text: str):
self.text = text
A line in my case can have many features. For example,
The line is considered long if it has more than 50 characters.
If the line has non-ascii characters, then a flag is set as non_ascii = True
and so on
Find all the urls in the line
I am able to implement them in terms of class methods:
class Line:
def __init__(self, text: str):
self.text = text
def is_ascii(self):
# check and return
def is_long(self):
# check and return
def urls(self):
# find and return urls
The problem I have is, I need to make the method calls on the Line object multiple times at different stages in the process (some of the method calls are pretty computationally heavy).
What I would like to have is, at initialization, I would like the Line object to have attributes such as is_ascii, is_long, urls to be pre populated so that they can be accessed multiple times without the need to compute them every time they are accessed.
class Line:
def __init__(self, text: str):
self.text = text
self.is_ascii = do some processing on self.text, and assign value
self.is_long = do some processing on self.text, and assign value
self.urls = do some processing on self.text, and assign value
I am not sure if having all that logic live inside the init block makes sense. How would I achieve this in a 'pythonic' manner?
You could just have the methods that you do, and call them from within __init__:
class Line:
def __init__(self, text: str):
self.text = text
self.is_ascii = self.calc_is_ascii(self.text)
self.is_long = self.calc_is_long(self.text)
self.urls = self.calc_urls(self.text)
def calc_is_ascii(self):
# check and return
def calc_is_long(self):
# check and return
def calc_urls(self):
# find and return urls
You could also have it so if a method is called, it checks to see if the value has already been calculated, and uses the cached value, otherwise it calculates and caches it:
class Line:
def __init__(self, text: str):
self.text = text
self.is_ascii = None
self.is_long = None
self.urls = None
def calc_is_ascii(self):
if self.is_ascii is None:
# Do expensive calculation of self.is_ascii
self.is_ascii = expensive_result
# Use the cached, previously calculated value
return self.is_ascii
# Then the same pattern for other methods
This has the benefit that if one of the attributes is never needed, the work to calculate it isn't done.
I'm initializing the attributes to None. If they're None later, I know they haven't been calculated yet. If they aren't None, I know they have already been calculated, so I can just return the calculated result. This of course assumes that None is not a valid value.
Related
In brief, I have a DataFormatter class that has two possible states: train or infer, that should act similarly to many of the sklearn libraries that have fit and transform functions: if mode is train I want to store in self.metadata a list of the function calls and args that were made, so that they can simply be reapplied verbatim and in order at infer time.
So minimally, I have:
import inspect
class DataFormatter:
def __init__(self, mode, data=None):
self.data = data
self.metadata = []
# The decorator function: broken, but something like--
def meta(self, f, *args):
def wrapper(*args):
return f(*args)
if self.mode == 'train':
print('caching metadata')
meta = {
f.__name__: {
param: arg for param, arg in zip(
inspect.getfillargspec(f).args, args)}}
self.metadata.append(meta)
return wrapper
#meta
def drop(self, cols):
self.data = self.data.drop(cols)
Then if I use:
formatter = DataFormatter('train', my_data)
formatter.drop(['col1', 'col5'])
print(formatter.metadata)
...I would like to get:
[{'drop': {'cols': ['col1', 'col5']}}]
I have tried various permutations and placements of self, and pulling the decorator func outside the class altogether, but no luck so far.
#kindall says "You can't get self at decoration time because the decorator is applied at function definition time. No self exists yet; in fact, the class doesn't exist yet." (Possible to create a #synchronized decorator that's aware of a method's object?), so not even sure if this is possible...
What will "see" a self is the decorated function - and it is represented by the function you call "wrapper":
import inspect
# The decorator function: should be out of the class body
def meta( f): ,
def wrapper(self, *args):
# call the decorated function:
# (but you could run other code, including inspecting
# and modifying arguments, here as well)
result = f(self, *args)
# now, your original method had run, and you have
# access to self:
if self.mode == 'train':
print('caching metadata')
meta = {
f.__name__: {
param: arg for param, arg in zip(
inspect.getfillargspec(f).args, args)}}
self.metadata.append(meta)
return result
return wrapper
class DataFormatter:
def __init__(self, mode, data=None):
self.data = data
self.metadata = []
#meta
def drop(self, cols):
self.data = self.data.drop(cols)
That will work.
I am pretty new to QT and I am using PySide2 (latest version) with Python 3.9.6.
I want to use a CustomModel via QAbstractItemModel on a QtreeView and at the same time with a QListView.
I have a CustomModel with a two-level hierarchy data.
I want to see the full data in the treeview (working).
At the beginning I show the same model in the QListView. It shows only the top level items.
So far so good.
Now I connected the setRootIndex fn from the QListView to the clicked signal of the QTreeView.
I want to be able to click on a root level item and see only the children in the QListView.
I thought the .setRootIndex should do the trick, but its weirdly offsetting the shown children.
And it's showing only ONE of the children and offsetted by the index count of the first level item.
Please see the gif:
First both views show the same model.
Then I click the first root element in the left treeView.
It updates the right ListView, but only the first children is shown.
And the second item shows its child but the second and with one gap in the listView
Here is a (almost) working example.
I really hope someone can spot the mistake or my misconception of things..
The .setRootIndex on the QListView is confusing me.
I tried approaching it differntly in the .index and .parent and .rowCount functions of the CustomModel. But like this it somehow works at least. I have the feeling I am doing something wrong somewhere or the QListView wants things differntly like the QTreeView.
Is it even possible and a good idea to use the same model in two views?
I really thought so and this is the hole point of a model/viewcontroller approach, isn't it?
# -*- coding: utf-8 -*-
from typing import *
from PySide2 import QtWidgets
from PySide2.QtCore import QAbstractItemModel, QModelIndex
from PySide2.QtGui import Qt
from PySide2.QtWidgets import QListView, QTreeView
class FirstLevelItem:
def __init__(self, name) -> None:
self.name = name
self.children = []
class SecondLevelItem:
def __init__(self, name, parent) -> None:
self.name = name
self.parent = parent
class CustomModel(QAbstractItemModel):
def __init__(self, root_items, parent=None):
super().__init__(parent)
self.root_items = root_items
def rowCount(self, itemIndex):
"""Has to return the number of children of the itemIndex.
If its not a valid index, its a root item, and we return the count of all root_items.
If its a valid one and can have children, return the number of children.
This makes the Model to ask for more indexes for each item.
Only works if parent is set properly"""
if itemIndex.isValid():
item = itemIndex.internalPointer()
if isinstance(item, FirstLevelItem):
return len(item.children)
else:
return 0
else:
return len(self.root_items)
def columnCount(self, parent=None):
return 1
def parent(self, child_index):
"""Has to return an index pointing to the parent of the current index."""
if child_index.isValid():
# get the item of this index
item = child_index.internalPointer()
# check if its one with a parent
if isinstance(item, SecondLevelItem):
# get the parent obj from the item
parent_item = item.parent
# now we have to find the parents row index to be able to create the index pointing to it
parent_row = parent_item.children.index(item)
# create an index with the parent row and column and the parent item itself
return self.createIndex(parent_row, 0, parent_item)
else:
return QModelIndex()
else:
return QModelIndex()
def data(self, index, role):
if not index.isValid():
return None
item = index.internalPointer()
if role == Qt.DisplayRole:
return item.name
return None
def index(self, row, column, parentIndex):
if parentIndex.isValid():
parent_item = parentIndex.internalPointer()
return self.createIndex(row, column, parent_item.children[row])
else:
return self.createIndex(row, column, self.root_items[row])
class ModelTestDialog(QtWidgets.QDialog):
window_instance = None
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowFlags(self.windowFlags() ^ Qt.WindowContextHelpButtonHint)
# self.setMinimumSize(1024, 1024)
self.setWindowTitle("ModelTestDialog")
rootItems = []
for i in range(0, 3):
name = ["FirstLevel_A", "FirstLevel_B", "FirstLevel_C"][i]
rootItem = FirstLevelItem(name)
rootItems.append(rootItem)
for j in range(0, 3):
name = ["SecondLevel_A", "SecondLevel_B", "SecondLevel_C"][j]
childItem = SecondLevelItem(name, rootItem)
rootItem.children.append(childItem)
self.model = CustomModel(rootItems)
self.treeView = QTreeView()
self.treeView.setModel(self.model)
self.listView = QListView()
self.listView.setModel(self.model)
self.main_layout = QtWidgets.QVBoxLayout(self)
self.listViews_layout = QtWidgets.QHBoxLayout()
self.main_layout.addLayout(self.listViews_layout)
self.listViews_layout.addWidget(self.treeView)
self.listViews_layout.addWidget(self.listView)
self.treeView.clicked[QModelIndex].connect(self.listView.setRootIndex)
if __name__ == "__main__":
app = QtWidgets.QApplication()
form = ModelTestDialog()
form.show()
app.exec_()
There is absolutely nothing wrong about using the same model in multiple views.
That is the whole concept behind the model/view paradigm (which relies on the principle of separation of concerns): the same model can be shared amongs multiple views, even if they show the content of that model in different ways.
That is completely respected by Qt (as long as the model is properly implemented, obviously); this also happens for similar concepts in Qt, like the QTextDocument interface used in QTextEdit (the same document can be shown on different QTextEdit instances), or the QGraphicsScene shown in a QGraphicsView (each view can show a different portion of the same scene).
The actual issue
You're using the wrong row for the parent:
parent_row = parent_item.children.index(item)
The above returns the index (row) of the child item, but you need to use createIndex() as a reference for the parent, because parent() has to return the row/column of the parent, not that of the child.
In this simple case, just return the index within the root_items:
parent_row = self.root_items.index(parent_item)
A better approach
I would suggest a more flexible structure, where a single base class is used for all items, and it always has a parent attribute. To do this, you need to also create a "root item" which contains all top level items.
You can still create subclasses for items if you need more flexibility or specialization, but the default behavior remains unchanged, making the implementation simpler especially in the case you need further levels within the structure.
The major benefit of this approach is that you never need to care about the item type to know its level: you know that you need to access the root item when the given index is invalid, and for any other case (like index creation, parent access, etc), the implementation is much more easy and readable. This will automatically make easier to add support for other features, like moving items and drag&drop.
class TreeItem:
parent = None
def __init__(self, name='', parent=None):
self.name = name
self.children = []
if parent:
parent.appendChild(self)
def appendChild(self, item):
self.insertChild(len(self.children), item)
def insertChild(self, index, item):
self.children.insert(index, item)
item.parent = self
def row(self):
if self.parent:
return self.parent.children.index(self)
return -1
class CustomModel(QAbstractItemModel):
def __init__(self, root_items=None, parent=None):
super().__init__(parent)
self.root_item = TreeItem()
if root_items:
for item in root_items:
self.root_item.appendChild(item)
def rowCount(self, itemIndex):
if itemIndex.isValid():
return len(itemIndex.internalPointer().children)
else:
return len(self.root_item.children)
def columnCount(self, parent=None):
return 1
def parent(self, child_index):
if child_index.isValid():
item = child_index.internalPointer()
if item.parent:
return self.createIndex(item.parent.row(), 0, item.parent)
return QModelIndex()
def data(self, index, role):
if not index.isValid():
return None
item = index.internalPointer()
if role == Qt.DisplayRole:
return item.name
def index(self, row, column, parentIndex=QModelIndex()):
if parentIndex.isValid():
parent_item = parentIndex.internalPointer()
return self.createIndex(row, column, parent_item.children[row])
else:
return self.createIndex(row, column, self.root_item.children[row])
class ModelTestDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowFlags(self.windowFlags() ^ Qt.WindowContextHelpButtonHint)
self.setWindowTitle('ModelTestDialog')
rootItems = []
for i in range(0, 3):
name = 'FirstLevel {}'.format('ABC'[i])
rootItem = TreeItem(name)
rootItems.append(rootItem)
for j in range(0, 3):
name = 'SecondLevel {} (child of {})'.format('ABC'[j], 'ABC'[i])
TreeItem(name, rootItem)
# or, alternatively:
# rootItem.appendChild(TreeItem(name))
self.model = CustomModel(rootItems)
self.treeView = QTreeView()
self.treeView.setModel(self.model)
self.listView = QListView()
self.listView.setModel(self.model)
self.main_layout = QVBoxLayout(self)
self.listViews_layout = QHBoxLayout()
self.main_layout.addLayout(self.listViews_layout)
self.listViews_layout.addWidget(self.treeView)
self.listViews_layout.addWidget(self.listView)
self.treeView.clicked.connect(self.listView.setRootIndex)
As you can see, the whole model code is much simpler and cleaner: there is no need to check for item level/type, as the concept of the structure makes that automatically immediate.
Further notes:
the Qt API suggests that the parent argument of index() should be optional; while it's common to use None for that, a default (and invalid) QModelIndex() is preferable, as I did above;
python implicitly returns None if no other return value is given;
in the last few years, Qt has been in the process of removing all overloaded signals, replacing them with more verbose and unique ones; in general, it's unnecessary to specify them, especially where no overload actually exists (self.treeView.clicked);
I encounter this issue, when I instantiate an Test object and call the main() method, it prints out
https://bunch/of/urls/with/None/
Although I am expecting to have
https://bunch/of/urls/with/1234/
It cannot update the self.id inside the dictionary f-string later inside the main function.
My expectation is when the dictionary value retrieves given the key, it will update the self.id as well while returning the value.
The reason I am expecting that because I call self.b_func(1234) before I call self.a_func() so it updates the self.id.
However, it is not updating the self.id inside the f-string. This behavior I don't understand why. What concept am I missing? How can I fix this?
class Test():
def __init__(self,id=None):
self.id = id
# Problem happens here
self.a_dict = {'a' : f'https://bunch/of/urls/with/{self.id}/'}
def a_func(self):
val = self.a_dict['a']
print(val)
# It prints
# https://bunch/of/urls/with/None/
# Not
# https://bunch/of/urls/with/1234/
def b_func(self, id):
self.id = id
return True
def main(self):
self.b_func(1234)
self.a_func()
if __name__ == '__main__':
a = Test()
a.main()
f-strings are evaluated right where they are defined. They do not magically update themselves when the values of the expressions inside change afterwards.
You can consider making a_dict a property instead so that its value would be dynamically generated:
class Test():
def __init__(self,id=None):
self.id = id
#property
def a_dict(self):
return {'a' : f'https://bunch/of/urls/with/{self.id}/'}
Demo: https://replit.com/#blhsing/UnnaturalElasticClasslibrary
This is my first attempt to make a Python decorator, and I humbly admit to drawing inspiration from a number of StackOverflow posts while doing so. When I scavenged the __instancecheck__ method, the last two asserts stopped triggering, but now PyCharm's built-in linter isn't happy with the very last assert.
Expected type 'Union[type, Tuple[Union[type, Tuple[Any, ...]], ...]]', got 'Multiton' instead.
What is the root of the typing problem, and how can it be fixed?
Addition, after making the __init__ change recommended by a commenter another warning surfaces.
Local variable 'instance' might be referenced before assignment.
class Multiton(object):
""" When init parameters match, reuse an old initialized object instead of making a new one. """
def __init__(self, cls):
# self.__dict__.update({'instances': list(), 'cls': cls})
self.instances = list()
self.cls = cls
def __call__(self, *args, **kwargs):
# make a key with the parameters
key = (args, kwargs)
# search for a matching old instance
found = False
for instance in self.instances:
if instance['key'] == key:
found = True
break
# if not found then create a new instance
if not found:
instance = {'key': key, 'object': self.cls(*args, **kwargs)}
self.instances.append(instance)
return instance['object']
def __instancecheck__(self, other):
return isinstance(other, self.cls)
#Multiton
class YourClass:
def __init__(self, number):
self.number = number
yc_1 = YourClass(1)
yc_2 = YourClass(2)
yc_two = YourClass(2)
assert yc_1 != yc_2
assert yc_2 == yc_two
assert not isinstance(yc_1, Multiton)
assert isinstance(yc_1, YourClass)
The false warnings are PyCharm bugs which hopefully the fine folks at JetBrains will remedy soon.
https://youtrack.jetbrains.com/issue/PY-38590
https://youtrack.jetbrains.com/issue/PY-49966
I want a callback to get called whenever a certain attribute of object A is changed.
I'm aware that this question is related to Observer Pattern and descriptors in Python. However, it seems descriptors could only detect explicit changes via dot access.
For instance:
class Observer(object):
def __init__(self, callback=None):
self.__callback = callback
def __set_name__(self, owner, name):
self.__name = name
def __set__(self, obj, value):
obj.__dict__[self.__name] = value
self.trigger()
def __get__(self, obj, type=None):
return obj.__dict__.get(self.__name)
def trigger(self):
self.__callback()
def hello():
print('hello')
class MyClass:
data = Observer(hello)
a = MyClass()
a.data = [[1],2,3]
a.data.append(4)
a.data[0][0] = -1
In the above code, the callback is only called once for the initialization of the data. However, I want it to be called 3 times. I'm not tied to using descriptors but I do want the method to work on any data types, such as list, dict and etc.