Is there any way to know the context in which an object is instantiated? So far I've been searching and tried inspect module (currentcontext) with poor results.
For example
class Item:
pass
class BagOfItems:
def __init__(self):
item_1 = Item()
item_2 = Item()
item_3 = Item()
I'd want to raise an exception in the instantiation of item_3 (because its outside a BagOfItems), while not doing so in item_1 and item_2. I dont know if a metaclass could be a solution to this, since the problem occurs at instantiation not at declaration.
The holder class (BagOfItems) can't implement the check because when Item intantiation happens outside it there would be no check.
When you instantiate an object with something like Item(), you are basically doing type(Item).__call__(), which will call Item.__new__() and Item.__init__() at some point in the calling sequence. That means that if you browse up the sequence of calls that led to Item.__init__(), you will eventually find code that does not live in Item or in type(Item). Your requirement is that the first such "context" up the stack belong to BagOfItem somehow.
In the general case, you can not determine the class that contains the method responsible for a stack frame1. However, if you make your requirement that you can only instantiate in a class method, you are no longer working with the "general case". The first argument to a method is always an instance of the class. We can therefore move up the stack trace until we find a method call whose first argument is neither an instance of Item nor a subclass of type(Item). If the frame has arguments (i.e., it is not a module or class body), and the first argument is an instance of BagOfItems, proceed. Otherwise, raise an error.
Keep in mind that the non-obvious calls like type(Item).__call__() may not appear in the stack trace at all. I just want to be prepared for them.
The check can be written something like this:
import inspect
def check_context(base, restriction):
it = iter(inspect.stack())
next(it) # Skip this function, jump to caller
for f in it:
args = inspect.getargvalues(f.frame)
self = args.locals[args.args[0]] if args.args else None
# Skip the instantiating calling stack
if self is not None and isinstance(self, (base, type(base))):
continue
if self is None or not isinstance(self, restriction):
raise ValueError('Attempting to instantiate {} outside of {}'.format(base.__name__, restriction.__name__))
break
You can then embed it in Item.__init__:
class Item:
def __init__(self):
check_context(Item, BagOfItems)
print('Made an item')
class BagOfItems:
def __init__(self):
self.items = [Item(), Item()]
boi = BagOfItems()
i = Item()
The result will be:
Made an item
Made an item
Traceback (most recent call last):
...
ValueError: Attempting to instantiate Item outside of BagOfItems
Caveats
All this prevents you from calling methods of one class outside the methods of another class. It will not work properly in a staticmethod or classmethod, or in the module scope. You could probably work around that if you had the motivation. I have already learned more about introspection and stack tracing than I wanted to, so I will call it a day. This should be enough to get you started, or better yet, show you why you should not continue down this path.
The functions used here might be CPython-specific. I really don't know enough about inspection to be able to tell for sure. I did try to stay away from the CPython-specific features as much as I could based on the docs.
References
1. Python: How to retrieve class information from a 'frame' object?
2. How to get value of arguments passed to functions on the stack?
3. Check if a function is a method of some object
4. Get class that defined method
5. Python docs: inspect.getargvalues
6. Python docs: inspect.stack
Related
I've been programming an application with PyQt5, and when I try to open a new window after a button interaction, the program crashes, and spits out exit code -1073740791.
My code looks like this:
class Start(QWidget):
def __init__(self):
super().__init__()
self.run()
def switch1(self):
Initiative.initRoll.show()
def startScreen(self):
startButton = QPushButton('Start')
infoButton = QPushButton('How To Use')
creditButton = QPushButton('Credits')
settingButton = QPushButton('Settings')
grid = QGridLayout()
grid.addWidget(infoButton, 0, 0)
grid.addWidget(settingButton, 0, 1)
grid.addWidget(creditButton, 0, 2)
grid.addWidget(startButton, 1, 0, 1, 3)
self.setLayout(grid)
startButton.clicked.connect(self.switch1)
self.setWindowTitle('Battletech App')
def run(self):
Start.startScreen(self)
self.show()
class Initiative(QWidget):
def initRoll(self):
initButton = QPushButton('Roll for Initiative')
grid = QGridLayout()
grid.addWidget(initButton, 2, 0, 1, 3)
self.setLayout(grid)
self.setWindowTitle('Battletech App')
def main():
app = QApplication(sys.argv)
ex = Start()
sys.exit(app.exec_())
I've messed around with most of the code trying to fix it, but I'm not sure how to.
If you want to get a more descriptive error message then it is recommended that you run your code from the CMD or console. In your case it is:
Traceback (most recent call last):
File "/path/of/script.py", line 14, in switch1
Initiative.initRoll.show()
AttributeError: 'function' object has no attribute 'show'
Aborted (core dumped)
And the cause is clear, you are handling the creation of objects incorrectly. The logic is to use the class as an object provider, and use that object to call the functions (recommendation: Check your notes on OOP).
def switch1(self):
self.initiative = Initiative()
self.initiative.initRoll()
self.initiative.show()
tl;dr:
Your syntax to call instance methods is wrong, and you're trying to call functions that don't exist.
Change to:
def switch1(self):
self.initiative = Initiative()
self.initiative.initRoll()
self.initiative.show()
And also the following (while not technically wrong, your call is unorthodox):
def run(self):
self.startScreen()
self.show()
Explanation
The first problem is that you're trying to access an attribute (show) that doesn't exist. This is your command:
Initiative.initRoll.show()
The above roughly means: access the show attribute of initRoll, which is an attribute of Initiative.
The problem becomes clear if you run it from a terminal/prompt (something you should always do whenever you get an exception you cannot track in the IDE), as you can clearly see the following traceback:
Traceback (most recent call last):
File "file.py", line 11, in switch1
Initiative.initRoll.show()
AttributeError: 'function' object has no attribute 'show'
initRoll, your function, obviously doesn't have any show attribute. Initiative has, but trying to call Initiative.show() wouldn't work either, which leads us to another issue with your code.
The problem is the strange way you're trying to call instance methods.
You are doing that in two occasions.
In run() it works:
Start.startScreen(self)
That's because calling startScreen using self (which is the instance) as argument, can correctly execute it: the self argument exists, and it also matches the current instance.
While this approach works, is not the conventional way of calling instance methods, as long as they are method of the current instance.
This approach is normally found when subclassing, as it allows to call the base implementation on the class, and was much more common in Python 2. A typical case is the __init__:
class SomeClass(QWidget):
def __init__(self):
# direct call to the base implementation, using the reference to the
# current instance (self) as its argument
QWidget.__init__(self)
# using super(), as required by Python 2, which is almost the same as
# above: calls the base implementation by referring the *super* class
# for its instance
super(SomeClass, self).__init__()
# common Python 3 usage, which doesn't require the arguments
super().__init__()
In all the cases above, the (base) class __init__ is called with self as argument, which is exactly what you're doing with startScreen(self).
show() is an instance method, and as with all instance methods, the first argument refers to the instance and is mandatory.
If Qt was written in Python, the code for show would be the following:
def show(self):
# some code that actually tells the OS to map a region of the screen that
# will show some user interface
Being an instance method, it cannot be called just from a class using Initiative.show(). It has to be called from an instance:
# create the instance
initiative = Initiative()
# call the method
initiative.show()
In this case, show() will be called correctly (when functions are called from an instance, their self argument becomes implicitly the instance).
In the code given at the beginning I also made the initiative instance a member of the Start instance, using self.initiative = .... This is required, because failing to do so (using a local variable only: initiative = ...) will result in a garbage collection of that object as soon as the function returns, so the widget will be immediately closed (you might see it for a fraction of a second only, but that's not always the case).
Finally, two suggestions:
read more about classes and instances to better understand how they work and how they should be used, and do more studying on how object oriented programming works in general;
while "messing around" is sometimes fine, doing it completely randomly is never a good idea if you're planning on learning; in the long run, studying the documentation is always the right choice (even if it seems more boring than messing around), as it lets you better understand what you're doing, what happens and why, especially if the documentation is well written and/or there are multiple valid sources around (which is the case for both Python and Qt);
My attempt was to create the default instance from inside of a metaclass, but to no avail. At least the reported class is the singleton in the example bellow.
EDIT: Clarifying requirements here: a singleton comparable by using the is keyword, without having to instantiate/call it. Unfortunately, this well known question-answer here doesn't seem to address that.
class MyNormalClass:
def __init__(self, values):
self.values = values
class MySingleton(MyNormalClass, type):
def __new__(mcs, *args, **kwargs):
return MyNormalClass(["default"])
print(MySingleton)
# <class '__main__.MySingleton'>
print(MySingleton.values)
# AttributeError: type object 'MySingleton' has no attribute 'values'
Metaclasses for singletons are overkill. (Search my answers for that, and there should be about 10 occurrences of this phrase).
In your example code in the question, the code inside the class and the metaclass methods is not even being run, not once. There is no black-magic in Python - the program just runs, and there are a few special methods marked with __xx__ that will be called intrinsically by the language runtime. In this case, the metaclass __new__ will be called whenever you create a new class using it as the metaclass, which you code does not show. And also, it would create a new instance of your "singleton" class each time it were used.
In this case, if all you need is a single instance, just create that instance, and let that one be public, instead of its class. You can even us the instance itself to shadow the class from the module namespace, so no one can instantiate it again by accident. If you want values to be immutable, well, you can't
ensure that with pure Python code in any way, but you can make changing values do not work casually with = so that people will know they should not be changing it:
class MySingleton:
__slots__ = ("values",)
def __init__(self, values):
self.values = values
def lock(self, name, value):
raise TypeError("Singleton can't change value")
self.__class__.__setitem__ = lock
MySingleton = MySingleton(["values"])
So, I might just be getting the basics confused here. But, it is printing even if I don't call the class.
I have tried putting it in a function, but only when I run the function there are problems.
class something_im_doing_wrong():
print('this should not be printing unless I call the class')
def __init__(self):
self.info = 'weird'
Now, the output is
this should not be printing unless I call the class
Even though I haven't called the class it is printing
I expect the output to be nothing, yet is is still printing. I want it so it only prints when I call the class.
If you want it to be printed when the class instance is created, put it inside the function __init__.
This is the initializer method which is automatically called whenever a new instance of a class is created.
If you want your string to get printed when you create a class, put the print statement in this function. This function gives the programmer the opportunity to set up the attributes required within the new instance by giving them their initial state/values.
This is the property definition I use to lazy load the element attribute of my Element class, borrowed from This post on Python lazy loading.
#property
def element(self):
if not hasattr(self, "_element"):
self._element = self.wait.wait_for_element_to_be_visible(self.locator)
return self._element
It looks for the attribute _element if it doesn't find it then it goes and looks for it. If the attribute is there then it just returns the attribute without looking for it as its already been loaded in.
I've changed the if to be:
if self._element is None:
self._element = self.wait.wait_for_element_to_be_visible(self.locator)
return self._element
Is it more pythonic to see if the attribute exists, or to set _element to None in the __init__ method and then check for that instead?
Doing it the second way also seems better for debugging purposes in IntelliJ as there seems to be a strange loading issue that freezes the tests if I expand my Element object and it starts introspection to display attributes and their values.
My definition of a pythonic solution is the one that's most likely to help a future maintainer understand what's going on when they come across this code (most times it's the same person who wrote it but long after they have it in their memory). Here's some things I might ask myself if I was coming in cold.
1. hasattr
self._element is not in __init__, is this because it's set somewhere else?
Every access checks for the attribute, does that mean it's expecting it to be deleted at some point so it can refresh? Why else is it checked every time?
hasattr will search up the chain of MRO, are we actually looking for something?
2. is None
Found self._element in __init__ being set to None. Am I allowed to set it to None again to get fresh values? The _ in _element is hinting no.
2a. is _Missing
If None is a valid result, use a sentinel instead.
class _Missing:
pass
def __init__():
self._element = _Missing
3. One shot descriptor
Property produces what is known as a data descriptor. It will ignore attributes of the instance and go straight to the classes dictionary (otherwise __set__ won't be able to do it's thing). Here we make our own non-data descriptor (only defines __get__), which won't skip the instances dictionary. This means the instances attribute can be set. Deleting the instance attribute will result in a refreshed value on the next invocation.
class LazyLoadElement:
def __get__(self, instance, owner):
instance.element = instance.wait.wait_for_element_to_be_visible(instance.locator)
return instance.element
class MyClass:
element = LazyLoadElement()
What's the intention of the LazyLoadElement? Name says it all
Can it refresh to another value? Nope, it's set after the first call and removes itself.
Pythonic is a little arm wavy because it needs to make assumptions about who is reading the code at a future date. Out of the 2 original options, number 2 seems most likely to get maintainers up to speed. 3 is my fav though, mainly because once it's loaded there's no more function calls.
I'm having trouble getting my head around assigning a function to a variable when the function uses arguments. The arguments appear to be required but no matter what arguments I enter it doesn't work.
The scenario is that I'm creating my first GUI which has been designed in QT Designer. I need the checkbox to be ticked before the accept button allows the user to continue.
Currently this is coded to let me know if ticking the checkbox returns anything (which is does) however I don't know how to pass that result onto the next function 'accept_btn'. I thought the easiest way would be to create a variable however it requires positional arguments and that's where I'm stuck.
My code:
class MainWindow(QtWidgets.QMainWindow, Deleter_Main.Ui_MainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUi(self)
self.ConfirmBox.stateChanged.connect(self.confirm_box)
self.Acceptbtn.clicked.connect(self.accept_btn)
def confirm_box(self, state):
if self.ConfirmBox.isChecked():
print("checked")
else:
print("not checked")
checked2 = confirm_box(self, state)
def accept_btn(self):
if checked2 == True:
print("clicked")
else:
print("not clicked")
app = QApplication(sys.argv)
form = MainWindow()
form.show()
app.exec_()
The code gets stuck on 'checked2' with the error:
NameError: name 'self' is not defined
I thought there might be other solutions for running this all within one function but I can't seem to find a way whilst the below is required.
self.ConfirmBox.stateChanged.connect(self.confirm_box)
Would extra appreciate if anyone could help me understand exactly why I need the 'self' argument in the function and variable.
Thanks in advance,
If you just need to enable a button when the checkbox is checked, it can be easily done within the signal connection:
self.ConfirmBox.toggled.connect(self.Acceptbtn.setEnabled)
QWidget.setEnabled requires a bool argument, which is the argument type passed on by the toggled signal, so the connection is very simple in this case.
Apart from this, there are some mistakes in your understanding of classes in Python: it seems like you are thinking in a "procedural" way, which doesn't work well with general PyQt implementations and common python usage, unless you really need some processing to be done when the class is created, for example to define some class attributes or manipulate the way some methods behave. But, even in this case, they will be class attributes, which will be inherited by every new instance.
The line checked2 = confirm_box(self, state) will obviously give you an error, since you are defining checked2 as a class atribute. This means that its value will be processed and assigned when the class is being created: at this point, the instance of the class does not exist yet, Python just executes the code that is not part of the methods until it reaches the end of the class definition (its primary indentation). When it reaches the checked2 line, it will try to call the confirm_box method, but the arguments "self" and "state" do not exist yet, as they have not been defined in the class attributes, hence the NameError exception.
Conceptually, what you have done is something similar to this:
class SomeObject(object):
print(something)
This wouldn't make any sense, since there is no "something" defined anywhere.
self is a python convention used for class methods: it is a keyword commonly used to refer to the instance of a class, you could actually use any valid python keyword at all.
The first argument of any class method is always the reference to the class instance, the only exceptions are classmethod and staticmethod decorators, but that's another story. When you call a method of an instanciated class, the instance object is automatically bound to the first argument of the called method: the self is the instance itself.
For example, you could create a class like this:
class SomeObject(object):
def __init__(Me):
Me.someValue = 0
def setSomeValue(Myself, value):
Myself.someValue = value
def multiplySomeValue(I, multi):
I.setSomeValue(I.someValue * multi)
return I.someValue
But that would be a bit confusing...