Memoized objects still have their __init__() invoked? - python-3.x

So I am creating a memoized class, and have been observing a strange behavior.
Here's the snippet of the code:
class SomeClass(object):
_Memoized = {}
def __new__(cls, id: str, *args, **kwargs):
if id not in cls._Memoized:
print('New Instance')
cls._Memoized[id] = super(SomeClass, cls).__new__(cls, *args, **kwargs)
else:
print('Existing Instance')
return cls._Memoized[id]
def __init__(self, id: str):
print('Running init')
self.id = id
def test():
w1 = SomeClass(id='w1')
wx = SomeClass(id='w1')
print(id(w1), id(wx), id(w1) == id(wx))
test()
Running the above code results in:
New Instance
Running init
Existing Instance
Running init <===-------------------???
140008534476784 140008534476784 True
My questions: During the second invocation of SomeClass(), why does it execute the __init__ constructor? Wasn't the __init__ constructor only invoked on instantiating? Is there a way to prevent the __init__ from being invoked?

The purpose of __new__ is to create a new instance, which is why Python calls __init__ on it. You might instead override __call__ on a metaclass to avoid creating a new instance.
class MemoMeta(type):
def __init__(self, name, bases, namespace):
super().__init__(name, bases, namespace)
self.cache = {}
def __call__(self, id_, *args, **kwargs):
if id_ not in self.cache:
print('New Instance')
self.cache[id_] = super().__call__(id_, *args, **kwargs)
else:
print('Existing Instance')
return self.cache[id_]
class SomeClass(metaclass=MemoMeta):
def __init__(self, id_, *args, **kwargs):
print('Running init')
self.id = id_
def test():
w1 = SomeClass(id_='w1')
wx = SomeClass(id_='w1')
print(id(w1), id(wx), id(w1) == id(wx))
test()

Related

Passing non-default arguments to Tk class constructor

I want to pass two Queues objects to a class the inherits Tk.tk.
main.py code:
qin = Queue()
qout = Queue()
gui = GUI(qin,qout)
gui.mainloop()
gui.py code:
class GUI(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs) #if im calling tk.Tk.__init__(self) nothing is displayed to the screen so *args, **kwargs is requirement for some reason, not sure why
self.qin = args[0]
self.qout = args[1]
...
error:
gui = GUI(qin,qout)
File "/home/a/gui.py", line 20, in __init__
tk.Tk.__init__(self, *args, **kwargs)
File "/usr/lib/python3.9/tkinter/__init__.py", line 2270, in __init__
self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)
TypeError: create() argument 1 must be str or None, not Queue
How to fix this error?
Thanks.
Just pop the args off of kwargs before passing kwargs to the superclass init.
class GUI(tk.Tk):
def __init__(self, *args, **kwargs):
qin = kwargs.pop("qin", None)
qout = kwargs.pop("qout", None)
tk.Tk.__init__(self, *args, **kwargs)
Or, if you don't care about the caller being able to pass in other arguments through to the superlcass, just define them as normal arguments:
class GUI(tk.Tk):
def __init__(self, qin, qout):
tk.Tk.__init__(self)

With open inside of a class

Hello I am doing an assignment and I have a similar problem as to here but in this case I am unable to change the file = logsys(file_location) part due to the assignment rules. Is there any way I can do the "with" part inside of the class?
My code is shown below
doc = write("test_2.txt")
class HtmlTable:
def __init__(self, filename):
self.filename = filename
self.fp = None
def tag(self, text):
self.fp.write('<' + text+ '>' +'\n')
def __iadd__(self, text):
self.fp.write(text)
def __enter__(self):
self.fp = open(self.filename, "a+")
return self
def __exit__(self, exception_type, exception_value, traceback):
self.fp.close()
The idea is that i want to call the class and let it handle the with part for me

wrapper class with __enter__/__exit__ in python3

I have a code that perfectly works in Python 2, but it doesn't work in Python 3.
There is an aggregator class data and a few classes to work with specific data formats.
class data():
def __init__(self, file, format="x"):
if format == "x":
self.data = xdata(file)
elif format == "y":
self.data = ydata(file)
# Redirect functions to the specific class
self.__enter__ = self.data.__enter__
self.__exit__ = self.data.__exit__
class xdata():
def __init__(self, file):
#do something
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
#do something
class ydata():
def __init__(self, file):
#do something
def __enter__(self):
return self
def __exit__(self,exc_type, exc_value, traceback):
#do something
In python2 I was able to execute the following code without any errors,
with data("newfile.x") as sd:
#do something with data
but python3 returns an error AttributeError: __enter__
Any ideas on how to fix it?
__enter__ and __exit__ will be resolved as descriptors, which means that resolution bypasses the attributes of the class. You can provide your own descriptors for __enter__ and __exit__ using property:
class xdata():
def __init__(self, file):
self.attr = 'x'
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
pass
class ydata():
def __init__(self, file):
self.attr = 'y'
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
pass
class data():
def __init__(self, file, format="x"):
if format == "x":
self.data = xdata(file)
elif format == "y":
self.data = ydata(file)
# Redirect functions to the specific class
#property
def __enter__(self):
return self.data.__enter__
#property
def __exit__(self):
return self.data.__exit__
with data("", "x") as d:
print(d.attr) # x
with data("", "y") as d:
print(d.attr) # y

Spurious arguments to __new__ when using metaclass

I am trying to learn about metaclasses in python 3.7 and have the following code
class Foo(type):
def __new__(cls, *args, **kwargs):
return super().__new__(cls, *args, **kwargs)
def __init__(self, *args, **kwargs):
return super().__init__(*args, **kwargs)
def __call__(cls, *args, **kwargs):
return super().__call__(cls, *args, **kwargs)
class Bar(metaclass=Foo):
def __new__(cls, *args, **kwargs):
print(cls)
print(args)
print(kwargs)
return super().__new__(cls, *args, **kwargs)
def __init__(self, *args, **kwargs):
return super().__init__(*args, **kwargs)
b = Bar()
When I run it I get the output
<class '__main__.Bar'>
(<class '__main__.Bar'>,)
{}
and the error
File "meta/main.py", line 91, in __new__
return super().__new__(cls, *args, **kwargs)
TypeError: object.__new__() takes no arguments
where the line correpondse to the __new__ call in Bar
I can't work out why the second <class '__main__.Bar'> is being passed in. If I change Bar to not use the Foo metaclass (i.e. change class Bar(metaclass=Foo): to class Bar:) I get
<class '__main__.Bar'>
()
{}
and no errors (as I would expect). Thanks for any help
You are passing in an extra argument on calls:
def __call__(cls, *args, **kwargs):
return super().__call__(cls, *args, **kwargs)
__call__ is not an implicit static method, drop that cls argument:
def __call__(cls, *args, **kwargs):
return super().__call__(*args, **kwargs)

python lost data-descriptor as a instance attribute?

class data_attr_set_pass(object):
def __init__(self, inner=None, name=""):
self.inner = inner
self.name = name
def __get__(self, instance, owner):
return self.inner
def __set__(self, instance, value):
pass
def __repr__(self):
return str(self.name) + ":" + str(self.inner)
class data_attr(object):
def __init__(self, inner=None, name=""):
self.inner = inner
self.name = name
def __get__(self, instance, owner):
return self.inner
def __set__(self, instance, value):
self.inner = value
def __repr__(self):
return str(self.name) + ":" + str(self.inner)
class non_data_attr(object):
def __init__(self, inner=None, name=""):
self.inner = inner
self.name = name
def __get__(self, instance, owner):
return self.inner
def __repr__(self):
return str(self.name) + ":" + str(self.inner)
class Myclass(object):
x = data_attr_set_pass(11, "class attr")
def __init__(self):
self.x = data_attr(890, "instance attr")
print(Myclass.x)
m = Myclass()
print(Myclass.x)
print(m.x)
print(Myclass.__dict__)
print(m.__dict__)
output:
11
11
11
{'__module__': '__main__', 'x': class attr:11, '__init__': <function Myclass.__init__ at 0x7f0dcc2d1378>, '__dict__': <attribute '__dict__' of 'Myclass' objects>, '__weakref__': <attribute '__weakref__' of 'Myclass' objects>, '__doc__': None}
{}
I had some tests for usage of python data-descriptor. And one test had very strange behaviour. I know that descriptor should be class attr, it's just a test:).
I put pass in __set__ of class data_attr_set_pass. So as far as I know, self.x = data_attr(890, "instance attr") in __init__ will put data_attr(890, "instance attr") into self.__dict__. But it can be found nowhere.
why does it happend and where can I find the data-descriptor?
==================================================================
It's really my misunderstanding, I just run first test in code below in which I put 2 into self.__dict__["x"] directly, the second one can prove it.
class Myclass(object):
x = data_attr_set_pass(11, "class attr")
def __init__(self):
self.__dict__["x"] = 2
print(Myclass.x)
m = Myclass()
print(Myclass.x)
print(m.x)
print(Myclass.__dict__)
print(m.__dict__)
print('================')
class Myclass(object):
x = data_attr_set_pass(1324, "class attr")
def __init__(self):
self.x = 2232
print(Myclass.x)
m = Myclass()
print(Myclass.x)
print(m.x)
print(Myclass.__dict__)
print(m.__dict__)
You say
I put pass in __set__ of class data_attr_set_pass. So as far as I know, self.x = data_attr(890, "instance attr") in __init__ will put data_attr(890, "instance attr") into self.__dict__.
But why would it do that? You didn't write data_attr_set_pass.__set__ to insert the value into the instance __dict__. You wrote your __set__ to do nothing but pass.
Assigning something to self.x does exactly what you wrote your __set__ to do: nothing.

Resources