I want to create a baseclass that has an attribute which should not change. Any derived class from this baseclass should have this attribute aswell. My idea was:
class baseclass(object):
flag = "some attr for all classes"
def __init__(self, baseattr):
self.baseattr = baseattr
class child(baseclass):
def __init__(self, baseattr, childattr):
super(child, self).__init__(baseattr)
self.childattr = childattr
My thinking was, if I know look at child.__dict__:
print(child.__dict__["flag"])
it should return "special attribute ... baseclass" but instead I get a KeyError:
Traceback (most recent call last):
File "./test.py", line 14, in <module>
print(child.__dict__["flag"])
KeyError: 'flag'
while when calling baseclass.__dict__['flag']
everything is fine. Is there a way to set flags for all derived class that inherit from baseclass?
First of all, I think it would be nice if you could review the concepts related to a Class definition so you can understand the difference between a class instance and a class object.
Overall, the __dict__ that you're trying to access implements the object namespace. It was not suppose to be accessed directly as you're trying to do. But for the sake of understanding I'll use it to illustrate the class instance vs class object difference.
Calling __dict__ as you were will get you the dict containing the attributes of your child class object (the default ones and the ones you defined in your Class definition):
>>> child.dict
dict_proxy({'module': 'main', 'doc': None, 'init': })
However, when you decided to put flag in your baseclass like you did, you were defining it as a part of your baseclass class object. It is not declared each time for instance, it was declared once you imported your class definition. Therefore, you can see flag if you do:
>>> baseclass.dict
dict_proxy({'module': 'main', 'flag': 'some attr for all classes', 'dict': , 'weakref': , 'doc': None, 'init': })
Finally, if you access an object instance __dict__ you'll see the attributes you declared with self (including baseattr that was declared when you called super):
>>> child('some base attr', 'some child attr').dict
{'childattr': 'some child attr', 'baseattr': 'some base attr'}
That being said, you already have to access to flag from any object instance. Being more specific, you have access to every attribute defined in the class definition, the inherit class definition and in your instance. So I really recommend you stop using __dict__ and access things the way they were intended to:
>>> your_obj_instance = child('some base attr', 'some child attr')
>>> your_obj_instance.childattr
'some child attr'
>>> your_obj_instance.baseattr
'some base attr'
>>> your_obj_instance.flag
'some attr for all classes'
Related
class Name():
def full_name(self):
self.firstname='[no name]'
self.lastname='[no name]'
class person:
def detail(self):
self.name=Name()
self.eye='[no eye]'
self.age=-1
myperson=person()
myperson.name.firstname='apple'
myperson.name.lastname='regmi'
myperson.name.firstname='cat'
print(myperson.name.firstname)
i could not find out why iam getting line 13, in
myperson.name.firstname='apple' AttributeError: 'person' object has no attribute 'name'
It seems like you're hoping to set the name, eye, and age attributes as defaults when you create any person object. If that's the case, detail should really be replaced with __init__, e.g.:
class person:
def __init__(self):
self.name=Name()
self.eye='[no eye]'
self.age=-1
A class's __init__ method defines how objects should be initialized when they are created. You don't need to call this method explicitly; instead, it is automatically run when you create an instance of a class:
# create an instance of persion, call
# `person.__init__()`, and assign the result
# to `myperson`:
myperson = person()
Now you should be able to reference and assign the attributes:
myperson.name.firstname='apple'
myperson.name.lastname='regmi'
myperson.name.firstname='cat'
Similarly, name.full_name should probably be name.__init__.
Note that by convention, classes in python usually use TitleCase, so this class would typically be named Person; likewise, name would be Name.
I'm trying to understand if there are differences between self and cls but I'm struggling, even though a lot of discussion on this topic exists. For instance:
class maclass():
A = "class method"
def __init__(self):
self.B = "instance method"
def getA_s(self):
print(self.A)
def getA_c(cls):
print(cls.A)
def getB_s(self):
print(self.B)
def getB_c(cls):
print(cls.B)
C = maclass()
C.getA_s()
C.getA_c()
C.getB_s()
C.getB_c()
which give me:
class method
class method
instance method
instance method
So whether I use self or cls, it always refers to the same variable. When I add a self.A in the Init__, the cls.A is just replaced
def __init__(self):
self.B = "instance method"
self.A = "new instance method"
and I get:
new instance method
new instance method
instance method
instance method
I don't understand the point of having two ways to call a class member if they are the same? I know this is a common question on this forum, yet I really don't understand why we would use different words to refer to the same thing (we even could use any variable name instead of self or cls).
update
In the following case:
class maclass():
A = "class method, "
def __init__(self):
self.A = "instance method, "
def getA_s(self):
print(self.A) #give me "instance method, "
#classmethod
def getA_c(cls):
print(cls.A) #give me "class method, "
C = maclass()
C.getA_s()
C.getA_c()
print(' ')
print(C.A) #give me "instance method, "
I get :
instance method,
class method,
instance method,
So in this case, in maclass: cls.A and self.A do not refer to the same variable.
All your methods are instance methods. None of them are class methods.
The first argument to a method is named self only by convention. You can name it anything you want, and naming it cls instead will not make it a reference to the class. That the first argument is bound to an instance is due to how method lookup works (accessing C.getA_s produces a bound method object, and calling that object causes C to be passed into the original function getA_s), the names of the parameters play no role.
In your methods, you are merely referencing instance attributes. That the A attribute is ultimately only defined on the class doesn't matter, you are still accessing that attribute through C.A (where C is the instance you created), not maclass.A. Looking up an attribute on the instance will also find attributes defined on the class if there is no instance attribute shadowing it.
To make a method a class method, decorate it with the #classmethod decorator:
#classmethod
def getA_c(cls):
print(cls.A)
Now cls will always be a reference to the class, never to the instance. I need to stress again that it doesn't actually matter to Python what name I picked for that first argument, but cls is the convention here as that makes it easier to remind the reader that this method is bound to the class object.
Note that if you do this for the getB_c() method, then trying to access cls.B in the method will fail because there is no B attribute on the maclass class object.
That's because classmethod wraps the function in a descriptor object that overrides the normal function binding behaviour. It is the descriptor protocol that causes methods to be bound to instances when accessed as attributes on the instance, a classmethod object redirects that binding process.
Here is a short demonstration with inline comments, I used the Python convertions for naming classes (using CamelCase), and for instances, attributes, functions and methods (using snake_case):
>>> class MyClass():
... class_attribute = "String attribute on the class"
... def __init__(self):
... self.instance_attribute = "String attribute on the instance"
... #classmethod
... def get_class_attribute(cls):
... return cls.class_attribute
... def get_instance_attribute(self):
... return self.instance_attribute
... #classmethod
... def get_instance_attribute_on_class(cls):
... return cls.instance_attribute
...
>>> instance = MyClass()
>>> instance.class_attribute # class attributes are visible on the instance
'String attribute on the class'
>>> MyClass.class_attribute # class attributes are also visible on the class
'String attribute on the class'
>>> instance.get_class_attribute() # bound to the class, but that doesn't matter here
'String attribute on the class'
>>> instance.class_attribute = "String attribute value overriding the class attribute"
>>> instance.get_class_attribute() # bound to the class, so the class attribute is found
'String attribute on the class'
>>> MyClass.get_instance_attribute_on_class() # fails, there is instance_attribute on the class
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 12, in get_instance_attribute_on_class
AttributeError: type object 'MyClass' has no attribute 'instance_attribute'
Note that the class method accesses the class attribute even though we set an attribute with the same name on the instance.
Next is binding behaviour:
>>> MyClass.get_instance_attribute # accessing the method on the class gives you the function
<function MyClass.get_instance_attribute at 0x10f94f268>
>>> instance.get_instance_attribute # accessing the method on the instance gives you the bound method
<bound method MyClass.get_instance_attribute of <__main__.MyClass object at 0x10f92b5f8>>
>>> MyClass.get_class_attribute # class methods are always bound, to the class
<bound method MyClass.get_class_attribute of <class '__main__.MyClass'>>
>>> instance.get_class_attribute # class methods are always bound, to the class
<bound method MyClass.get_class_attribute of <class '__main__.MyClass'>>
The bound methods tell you what they are bound to, calling the method passes in that bound object as the first argument. That object can also be introspected by looking at the __self__ attribute of a bound method:
>>> instance.get_instance_attribute.__self__ # the instance
<__main__.MyClass object at 0x10f92b5f8>
>>> instance.get_class_attribute.__self__ # the class
<class '__main__.MyClass'>
In Python, I am able to access the non-predefined class variables both from the class as well as instances. However, I am not able to access the predefined class variables (such as "name") from the object instances. What am I missing? Thanks.
Here is a test program that I wrote.
class Test:
'''
This is a test class to understand why we can't access predefined class variables
like __name__, __module__ etc from an instance of the class while still able
to access the non-predefined class variables from instances
'''
PI_VALUE = 3.14 #This is a non-predefined class variable
# the constructor of the class
def __init__(self, arg1):
self.value = arg1
def print_value(self):
print self.value
an_object = Test("Hello")
an_object.print_value()
print Test.PI_VALUE # print the class variable PI_VALUE from an instance of the class
print an_object.PI_VALUE # print the class variable PI_VALUE from the class
print Test.__name__ # print pre-defined class variable __name__ from the class
print an_object.__name__ #print the pre-defined class varible __name__ from an instance of the class
That's normal. Instances of a class look in that class's __dict__ for attribute resolution, as well as the __dict__s of all ancestors, but not all attributes of a class come from its __dict__.
In particular, Test's __name__ is held in a field in the C struct representing the class, rather than in the class's __dict__, and the attribute is found through a __name__ descriptor in type.__dict__. Instances of Test don't look at this for attribute lookup.
I don't have a great answer for "why". But here's how you can get to them, using __class__:
>>> class Foo(object): pass
...
>>> foo = Foo()
>>> foo.__name__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute '__name__'
>>> foo.__class__.__name__
'Foo'
>>>
I created the empty abstract class AbstractStorage and inherited the Storage class from it:
import abc
import pymongo as mongo
host = mongo.MongoClient()
print(host.alive()) # True
class AbstractStorage(metaclass=abc.ABCMeta):
pass
class Storage(AbstractStorage):
dbh = host
def __init__(self):
print('__init__')
Storage()
I expected the output to be
True
__init__
however, the one I'm getting is
True
Traceback (most recent call last):
File "/home/vaultah/run.py", line 16, in <module>
Storage()
TypeError: Can't instantiate abstract class Storage with abstract methods dbh
The problem (apparently) goes away if I remove metaclass=abc.ABCMeta (so that AbstractStorage becomes an ordinary class) and/or if I set dbh to some other value.
What's going on here?
This isn't really a problem with ABCs, it's a problem with PyMongo. There is an issue about it here. It seems that pymongo overrides __getattr__ to return some sort of database class. This means that host.__isabstractmethod__ returns a Database object, which is true in a boolean context. This cause ABCMeta to believe that host is an abstract method:
>>> bool(host.__isabstractmethod__)
True
The workaround described in the issue report is to manually set host.__isabstractmethod__ = False on your object. The last comment on the issue suggests a fix has been put in for pymongo 3.0.
Short Version
mongo.MongoClient returns an object that appears to be (is?) an abstract method, which you then assign to the dbh field in Storage. This makes Storage an abstract class, so instantiating it raises a TypeError.
Note that I don't have pymongo, so I can't tell you anything more about MongoClient than how it gets treated by ABCMeta.
Long Version
The ABCMeta.__new__ method looks inside each field of the new class it's creating. Any field that itself has a True (or "true-like") __isabstractmethod__ field is considered an abstract method. If a class has any non-overridden abstract methods, the whole class is considered abstract, so any attempt to instantiate it is an error.
From an earlier version of the standard library's abc.py:
def __new__(mcls, name, bases, namespace):
cls = super().__new__(mcls, name, bases, namespace)
# Compute set of abstract method names
abstracts = {name
for name, value in namespace.items()
if getattr(value, "__isabstractmethod__", False)}
# ...
cls.__abstractmethods__ = frozenset(abstracts)
# ...
This is not mentioned in the abc.ABCMeta class docs, but a bit lower, under the #abc.abstractmethod decorator:
In order to correctly interoperate with the abstract base class machinery, the descriptor must identify itself as abstract using __isabstractmethod__. In general, this attribute should be True if any of the methods used to compose the descriptor are abstract.
Example
I created a bogus "abstract-looking" class with an __isabstractmethod__ attribute, and two supposedly-concrete subclasses of AbstractStorage. You'll see that one produces the exact error you're getting:
#!/usr/bin/env python3
import abc
# I don't have pymongo, so I have to fake it. See CounterfeitAbstractMethod.
#import pymongo as mongo
class CounterfeitAbstractMethod():
"""
This class appears to be an abstract method to the abc.ABCMeta.__new__
method.
Normally, finding an abstract method in a class's namespace means
that class is also abstract, so instantiating that class is an
error.
If a class derived from abc.ABCMeta has an instance of
CounterfeitAbstractMethod as a value anywhere in its namespace
dictionary, any attempt to instantiate that class will raise a
TypeError: Can't instantiate abstract class <classname> with
abstract method <fieldname>.
"""
__isabstractmethod__ = True
class AbstractStorage(metaclass=abc.ABCMeta):
def __init__(self):
"""
Do-nothing initializer that prints the name of the (sub)class
being initialized.
"""
print(self.__class__.__name__ + ".__init__ executing.")
return
class ConcreteStorage(AbstractStorage):
"""
A concrete class that also _appears_ concrete to abc.ABCMeta. This
class can be instantiated normally.
"""
whatever = "Anything that doesn't appear to be an abstract method will do."
class BogusStorage(AbstractStorage):
"""
This is (supposedly) a concrete class, but its whatever field appears
to be an abstract method, making this whole class abstract ---
abc.ABCMeta will refuse to construct any this class.
"""
#whatever = mongo.MongoClient('localhost', 27017)
whatever = CounterfeitAbstractMethod()
def main():
"""
Print details of the ConcreteStorage and BogusStorage classes.
"""
for cls in ConcreteStorage, BogusStorage:
print(cls.__name__ + ":")
print(" whatever field: " + str(cls.whatever))
print(" abstract methods: " + str(cls.__abstractmethods__))
print(" Instantiating...")
print(" ", end="")
# KABOOM! Instantiating BogusStorage will raise a TypeError,
# because it appears to be an _abstract_ class.
instance = cls()
print(" instance: " + str(instance))
print()
return
if "__main__" == __name__:
main()
Running this produces:
$ ./storage.py
ConcreteStorage:
whatever field: Anything that doesn't appear to be an abstract method will do.
abstract methods: frozenset()
Instantiating...
ConcreteStorage.__init__ executing.
instance: <__main__.ConcreteStorage object at 0x253afd0>
BogusStorage:
whatever field: <__main__.CounterfeitAbstractMethod object at 0x253ad50>
abstract methods: frozenset({'whatever'})
Instantiating...
Traceback (most recent call last):
File "./storage.py", line 75, in <module>
main()
File "./storage.py", line 68, in main
instance = cls()
TypeError: Can't instantiate abstract class BogusStorage with abstract methods whatever
class Test():
myVersion="1.0"
t=Test()
t.myVersion
--> 1.0
Test.myVersion="2.0"
Test.x={'myVersion':'1.0'}
Test.x
--> {'myVersion': '1.0'}
t.x
--> {'myVersion': '1.0'}
del t.x
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: Test instance has no attribute 'x'
Why has the instance t no attribute x?
{'myVersion': '1.0'} is not the attribution?
In Python, both class and instance attributes are accessible from an instance.
Ref Class instances in http://docs.python.org/2/reference/datamodel.html clarifies how this works -
A class instance is created by calling a class object (see above). A
class instance has a namespace implemented as a dictionary which is
the first place in which attribute references are searched. When an
attribute is not found there, and the instance’s class has an
attribute by that name, the search continues with the class
attributes.
You can see your class and instance's namespace using Test.__dict__ and t.__dict__ respectively.
Interestingly, if you modify a class attribute by referencing it from the instance, the class attribute will remain unaffected. Instead, a copy of the attribute with the change will be added to the instance's namespace.
For example,
>>> t = Test()
>>> t.myVersion = "2.0"
>>> Test.__dict__
{'__module__': '__main__', 'myversion': '1.0', '__doc__': None}
>>> t.__dict__
{'myVersion': '2.0'}
myVersion attribute is now present in both Test (class) and t (instance)'s namespace, and can be safely deleted from either without raising an AttributeError.
The basic idea is that a variable you are trying to delete from an object should be present in its namespace.
The problem is that the variable isn't attached to the instance, it's attached to the whole class. To fix this, you have to physically attach it (ok, maybe not physically, but you get what I mean) to the instance.
class Test:
def __init__(self):
self.myVersion = '1.0'
Then you can delete it.
t = Test()
del t.myVersion # no exception raised