refering to instance of a class from its metaclass python - python-3.x

Is there any way to refer to instance of a class from its metaclass every time an instance is created? I suppose I should use dunder _call_ method inside metaclass for that purpose.
I have the following code:
class meta(type):
def __call__(cls):
super().__call__()
#<--- want to get an object of A class here every time when instance of A class is created
class A(metaclass = meta):
def __init__(self, c):
self.c = 2
def test(self):
print('test called')
a1=A()
a2=A()
a3=A()
Also why when I implement __call__ method inside metaclass all created instances of my class became NoneType however when overring __call__ I used super().__call__()?
For example a4.test() returns AttributeError: 'NoneType' object has no attribute 'test'

The newly created instance is returned by super().__call__() - you hav to keep this value in a variable, use t for whatever you want and return it.
Otherwise, if the metaclass __call__ has no return statement, all instances are imediatelly de-referenced and destroyed, and the code trying to create instances just get None:
class meta(type):
def __call__(cls):
obj = super().__call__()
# use obj as you see fit
...
return obj

Related

How to define methods and member variables in class defined with custom metaclass in python

I am defining a singleton class, and using that class as a metaclass to create new classes.
class Singleton(type):
_lock: Lock = Lock()
_instance = {}
def __call__(cls, *args, **kwargs):
with cls._lock:
if cls not in cls._instance:
_instance = super().__call__(*args, **kwargs)
cls._instance[cls] = cls
return cls._instance.get(cls)
and the new class is defined like below
class SomeClass(metaclass=Singleton):
def __init__(self, some_list = []):
self.some_list = some_list
def add_to_list(self, a):
self.some_list.append(a)
some_class = SomeClass()
I am not able to access some_list variable of some_class object. It throws invalid attribute error.
some_class.some_list
a_list = [1,2,4,5]
for l in a_list:
some_class.add_to_list(l)
Also, I am not able to call add_to_list fn. It throws missing paramter "a" in the arguments.
Can some one help what I am missing in understanding of metaclass concept.
Your error is here:
cls._instance[cls] = cls
It should be:
cls._instance[cls] = _instance
You are storing the class itself on your class registry, not its single instance.
Before we proceed, I will point another problem your code:
def __init__(self, some_list = []):
Don't ever put a mutable object (an empty list) as a default parameter for a function or method: every time that function is called, the same object is re-used. In this case, this would be mitigated due to the method being in a singleton class, so this __init__ should run only once, but this is wrong enough. The correct pattern is:
def __init__(self, some_list = None):
if some_list is None:
some_list = []
This ensures a new, different, list is created each time the method is executed.
And, another thing, I don't know why this recipe of metaclass to create a singleton got so popular, but it is definitely overkill - I talk about it in some other answers, including Create singleton class in python by taking advantage of meta class , Dill doesn't seem to respect metaclass and Accessing the parameters of a constructor from a metaclass .

two issues on the lazy instantiation in the Singleton pattern

My python version:
python3 --version
Python 3.9.2
Issue 1:
What does isinstance function mean?
class Singleton1(object):
__instance = None
def __init__(self):
if not hasattr(Singleton1, '__instance'):
print("__init__ method called, but no instance created")
else:
print("instance already created:", self.__instance)
#classmethod
def get_instance(cls):
if not cls.__instance:
cls.__instance = Singleton1()
return cls.__instance
Initialize it :
x = Singleton1()
__init__ method called, but no instance created
Have a check with isinstance function:
isinstance(x,Singleton1)
True
If x is not an instance,why does isinstance(x,Singleton1) say it is an instance of Singleton1?
Issue2:
Why __init__ method can't be called anyway?
Now repalce all __instance (double underscores) with _instance(single underscore) in the class Singleton1 and replace all Singleton1 with Singleton2:
class Singleton2(object):
_instance = None
def __init__(self):
if not hasattr(Singleton2, '_instance'):
print("__init__ method called, but no instance created")
else:
print("instance already created:", self._instance)
#classmethod
def get_instance(cls):
if not cls._instance:
cls._instance = Singleton2()
return cls._instance
Initialize it:
y = Singleton2()
instance already created: None
Why __init__ method can't be called anyway in this status?
#snakecharmerb on issue1,Why someone say it is lazy instantiation ,if isinstance(x,Singleton1) is true,it is no need to call with Singleton1.get_instance() ,because the instance is already created during instantiation.
The hasattr check does not do what you think it does. Using Singleton2*, hasattr(Singleton2, '_instance') is always True, because the class has an attribute named _instance. You want to check the value of the instance, so use getattr instead; then the expected output will be printed.
The isinstance checks succeed because Singleton2() will return a new instance each time - there is nothing to prevent this. You can add a __new__ method to create _instance and return it every time Singleton2() is called. Note that this will mean that _instance will always exist by the time __init__ is called.
class Singleton2:
_instance = None
def __new__(cls):
if cls._instance is not None:
return cls._instance
instance = super().__new__(cls)
cls._instance = instance
return instance
* The hasattr check in Singleton1 is complicated by the name-mangling performed on __instance. In general, avoid using double-underscored variable names, except for avoiding name clashes in class hierarchies.

Initializing superclasses Python3

I am trying to understand when to initialize a superclass when using inheritance in python. Initially I thought that just by declaring a class inheriting from a super class, ex. class my_class(superclass):, would make available all the superclass's attributes and methods to the subclass. Which makes sense for somebody coming from Java. Then I read that Python forces us to initialize superclasses before we can implement them in our subclass, either by using the superclass.init() or super().init(). Then I came across this piece of code where I am not initializing the parent's class, however Python gave me access to the self.queue attribute from superclass without having initialized the parent class. I read the Python documentation and sometimes I think I know what they mean and some other I dont. Can anyone please explain to me when do we have to initialize superclasses in our subclasses?
class QueueError(IndexError):
pass
class Queue:
def __init__(self):
self.queue = []
def put(self,elem):
self.queue.insert(0,elem)
def get(self):
if len(self.queue) > 0:
elem = self.queue[-1]
del self.queue[-1]
return elem
else:
raise QueueError
class SuperQueue(Queue):
def isempty(self):
if not self.queue:
return True
else:
return False
que = SuperQueue()
que.put(1)
que.put("dog")
que.put(False)
for i in range(4):
if not que.isempty():
print(que.get())
else:
print("Queue empty")
In general, if you override __init__ in your subclass, you should call the super __init__ method only. This is necessary if you extend the superclass. But if you want to overwrite the whole __init__, you can also omit the super call.
Example:
You have class A that has one attribute value1.
And now you need a second attribute, so you subclass A with B and overwrite the __init__ where you call the super class (A), so A can set value1 and in B you can not set value2.
But now you need some other attributes in C, but need the same methods as in A. So you can entirely overwrite __init__ and omit the super call to A.
class A:
def __init__(self, value1):
print("Initialize A")
self.value1 = value1
class B(A):
def __init__(self, value1, value2):
super().__init__(value1)
print("Initialize B")
self.value2 = value2
class C(A):
def __init__(self, value3):
print("Initialize C")
self.value3 = value3
a = A("foo")
b = B("foo", "bar")
c = C("baz")
print(a.value1)
print(b.value1, b.value2)
print(c.value3)
print(c.value1)
Output
$ python main.py
Initialize A
Initialize A
Initialize B
Initialize C
foo
foo bar
baz
Traceback (most recent call last):
File "main.py", line 27, in <module>
print(c.value1)
AttributeError: 'C' object has no attribute 'value1'
You can see C wasn't initialized with value1, because C didn't call A's __init__.

Set variable as type of class

I am trying to figure out how I can pass a variable as the declaration type (object) for a class in Python 3.
Example:
#class defintion
class TestClass(Document):
test = IntField()
me = MongoEngine(app)
testInstance = TestClass(me.Document) # How do i pass the Document variable
I tried passing an instance of the MongoEngine variable as a variable to the TestClass but this isn't working properly?
I think you need to structure your class slightly different. Don't put Document in the class definition as if the TestClass is a subclass of Document. In stead, declare the class as standard (object), and define an __init__ where you can pass a variable which can be used by the instance of the class after initiation:
class TestClass(object):
def __init__(self, my_document):
self.document = my_document
# at this point the self.document variable
# is the same as the variable passed
# when initiating the instance of the class
def show_document(self):
# do something with your document
print(self.document)
me = MongoEngine(app)
# this will call __init__() passing the variable
test_instance = TestClass(me.Document)
# now do something with the class intance
test_instance.show_document()
[EDIT based on comment]
OP's comment:
Looking at the type(test_instance), Its not the same as a
MongoEngine.Document. I am hoping to create a class of type 'Document'
and pass in an instance of that type?
You can create classes which would take a parent class as object in the class definition. As I do not know MongoEngine I will make an example with list
A class defined as follows, will behave perfectly like a list, but if you do a type() it will come back as MyList:
class MyList(list):
def __init__(self, *args, **kwargs):
super(MyList, self).__init__(*args, **kwargs)
def my_extra_function(self):
print('hello world')
You can easily see this when using this class, first look at it as a list:
my_instance = MyList([1, 2, 3])
print(my_instance)
print(my_instance[::-1])
this will behave as if it was a list.
But when you do a type(), it will not return the same as list:
print(type(list))
print(type(list()))
print(type(MyList()))
print(type(my_instance))
output:
<class 'type'>
<class 'list'>
<class '__main__.MyList'>
<class '__main__.MyList'>
So even when you try to create a class with the MongoEngine.Document as parent object, the type() will still show you your own defined class.
class MyClass(MongoEngine.Document):
def __init__(self, *args, **kwargs):
super(MyClass, self).__init__(*args, **kwargs)
my_instance = MyClass('something')
If you do a type(my_instance) it will return your custom class, and not the parent object type.
Not sure how MongoEngine works, and if you can actually do something like this, so YMMV.
You can change the name type() is returning, by doing the following in my example class. Setting the self.__class__ in the __init__(). Like this:
class MyList(list):
def __init__(self, *args, **kwargs):
super(MyList, self).__init__(*args, **kwargs)
self.__class__ = type('list', (list,),{})
def my_extra_function(self):
print('hello world', self)
my_instance = MyList([1, 2, 3])
print(type(list))
print(type(list()))
print(type(MyList()))
print(type(my_instance))
output:
<class 'type'>
<class 'list'>
<class '__main__.list'>
<class '__main__.list'>
If this trick works for MongoEngine.Document I do not know.

class instance from nowhere [duplicate]

If I have a class ...
class MyClass:
def method(arg):
print(arg)
... which I use to create an object ...
my_object = MyClass()
... on which I call method("foo") like so ...
>>> my_object.method("foo")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: method() takes exactly 1 positional argument (2 given)
... why does Python tell me I gave it two arguments, when I only gave one?
In Python, this:
my_object.method("foo")
... is syntactic sugar, which the interpreter translates behind the scenes into:
MyClass.method(my_object, "foo")
... which, as you can see, does indeed have two arguments - it's just that the first one is implicit, from the point of view of the caller.
This is because most methods do some work with the object they're called on, so there needs to be some way for that object to be referred to inside the method. By convention, this first argument is called self inside the method definition:
class MyNewClass:
def method(self, arg):
print(self)
print(arg)
If you call method("foo") on an instance of MyNewClass, it works as expected:
>>> my_new_object = MyNewClass()
>>> my_new_object.method("foo")
<__main__.MyNewClass object at 0x29045d0>
foo
Occasionally (but not often), you really don't care about the object that your method is bound to, and in that circumstance, you can decorate the method with the builtin staticmethod() function to say so:
class MyOtherClass:
#staticmethod
def method(arg):
print(arg)
... in which case you don't need to add a self argument to the method definition, and it still works:
>>> my_other_object = MyOtherClass()
>>> my_other_object.method("foo")
foo
In simple words
In Python you should add self as the first parameter to all defined methods in classes:
class MyClass:
def method(self, arg):
print(arg)
Then you can use your method according to your intuition:
>>> my_object = MyClass()
>>> my_object.method("foo")
foo
For a better understanding, you can also read the answers to this question: What is the purpose of self?
Something else to consider when this type of error is encountered:
I was running into this error message and found this post helpful. Turns out in my case I had overridden an __init__() where there was object inheritance.
The inherited example is rather long, so I'll skip to a more simple example that doesn't use inheritance:
class MyBadInitClass:
def ___init__(self, name):
self.name = name
def name_foo(self, arg):
print(self)
print(arg)
print("My name is", self.name)
class MyNewClass:
def new_foo(self, arg):
print(self)
print(arg)
my_new_object = MyNewClass()
my_new_object.new_foo("NewFoo")
my_bad_init_object = MyBadInitClass(name="Test Name")
my_bad_init_object.name_foo("name foo")
Result is:
<__main__.MyNewClass object at 0x033C48D0>
NewFoo
Traceback (most recent call last):
File "C:/Users/Orange/PycharmProjects/Chapter9/bad_init_example.py", line 41, in <module>
my_bad_init_object = MyBadInitClass(name="Test Name")
TypeError: object() takes no parameters
PyCharm didn't catch this typo. Nor did Notepad++ (other editors/IDE's might).
Granted, this is a "takes no parameters" TypeError, it isn't much different than "got two" when expecting one, in terms of object initialization in Python.
Addressing the topic: An overloading initializer will be used if syntactically correct, but if not it will be ignored and the built-in used instead. The object won't expect/handle this and the error is thrown.
In the case of the sytax error: The fix is simple, just edit the custom init statement:
def __init__(self, name):
self.name = name
Newcomer to Python, I had this issue when I was using the Python's ** feature in a wrong way. Trying to call this definition from somewhere:
def create_properties_frame(self, parent, **kwargs):
using a call without a double star was causing the problem:
self.create_properties_frame(frame, kw_gsp)
TypeError: create_properties_frame() takes 2 positional arguments but 3 were given
The solution is to add ** to the argument:
self.create_properties_frame(frame, **kw_gsp)
As mentioned in other answers - when you use an instance method you need to pass self as the first argument - this is the source of the error.
With addition to that,it is important to understand that only instance methods take self as the first argument in order to refer to the instance.
In case the method is Static you don't pass self, but a cls argument instead (or class_).
Please see an example below.
class City:
country = "USA" # This is a class level attribute which will be shared across all instances (and not created PER instance)
def __init__(self, name, location, population):
self.name = name
self.location = location
self.population = population
# This is an instance method which takes self as the first argument to refer to the instance
def print_population(self, some_nice_sentence_prefix):
print(some_nice_sentence_prefix +" In " +self.name + " lives " +self.population + " people!")
# This is a static (class) method which is marked with the #classmethod attribute
# All class methods must take a class argument as first param. The convention is to name is "cls" but class_ is also ok
#classmethod
def change_country(cls, new_country):
cls.country = new_country
Some tests just to make things more clear:
# Populate objects
city1 = City("New York", "East", "18,804,000")
city2 = City("Los Angeles", "West", "10,118,800")
#1) Use the instance method: No need to pass "self" - it is passed as the city1 instance
city1.print_population("Did You Know?") # Prints: Did You Know? In New York lives 18,804,000 people!
#2.A) Use the static method in the object
city2.change_country("Canada")
#2.B) Will be reflected in all objects
print("city1.country=",city1.country) # Prints Canada
print("city2.country=",city2.country) # Prints Canada
It occurs when you don't specify the no of parameters the __init__() or any other method looking for.
For example:
class Dog:
def __init__(self):
print("IN INIT METHOD")
def __unicode__(self,):
print("IN UNICODE METHOD")
def __str__(self):
print("IN STR METHOD")
obj = Dog("JIMMY", 1, 2, 3, "WOOF")
When you run the above programme, it gives you an error like that:
TypeError: __init__() takes 1 positional argument but 6 were given
How we can get rid of this thing?
Just pass the parameters, what __init__() method looking for
class Dog:
def __init__(self, dogname, dob_d, dob_m, dob_y, dogSpeakText):
self.name_of_dog = dogname
self.date_of_birth = dob_d
self.month_of_birth = dob_m
self.year_of_birth = dob_y
self.sound_it_make = dogSpeakText
def __unicode__(self, ):
print("IN UNICODE METHOD")
def __str__(self):
print("IN STR METHOD")
obj = Dog("JIMMY", 1, 2, 3, "WOOF")
print(id(obj))
If you want to call method without creating object, you can change method to static method.
class MyClass:
#staticmethod
def method(arg):
print(arg)
MyClass.method("i am a static method")
I get this error when I'm sleep-deprived, and create a class using def instead of class:
def MyClass():
def __init__(self, x):
self.x = x
a = MyClass(3)
-> TypeError: MyClass() takes 0 positional arguments but 1 was given
You should actually create a class:
class accum:
def __init__(self):
self.acc = 0
def accumulator(self, var2add, end):
if not end:
self.acc+=var2add
return self.acc
In my case, I forgot to add the ()
I was calling the method like this
obj = className.myMethod
But it should be is like this
obj = className.myMethod()

Resources