PART I
in new-style class, implicit attribute fetch starts at class instead of instance.
X[I]
is equivalent to X.__getitem__(I)
in old-style class.
X[I]
is equivalent to type(X).__getitem__(X, I)
in new-style class.
Rationale for this:
metaclasses are the type for classes and classes are instances of metaclasses.
metaclass A
class B is an instance of A, if B wants to call a method, this skip of instance attribute will directly call the method from metaclass B. Because the methods defined in B aim for the use from its own instance. So the introduction of metaclass needs this change.
this impacts proxy class
, which embeds other objects which implement operator overloading.
>>> class A:
... data = 'spam'
... def __getattr__(self, name):
... print(name)
... return getattr(self.data, name)
...
>>> x = A()
>>> x[0]
__getitem__
's'
>>> print(x)
__str__
spam
>>> class B(object):
... data = 'spam'
... def __getattr__(self, name):
... print(name)
... return getattr(self.data, name)
...
>>> y = B()
>>> y[0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'B' object does not support indexing
>>> print(y)
<__main__.B object at 0x7f061c41b950>
>>> y.__getitem__(0)
__getitem__
's'
__getattr__(self, item)
is used when fetching attribute item
while the instance does’t have this one, this method will be called. So for class level attribute fetch, this method will never be called.
thus y[0]
: the implicit call to __getitem__
will skip the instance level and start to find __getitem__
in class B
.
Notice that the explicit class succeeded.
In other words, only built-in operation in new style class starts at class level while explicit fetches run correctly.
for delegation class, needs to redefine all names accessed by built-in operations.
>>> class C(object):
... data = 'spam'
... def __getattr__(self, name):
... print('getattr: ' + name)
... return getattr(self.data, name)
... def __getitem__(self, i):
... print('getitem: ' + str(i))
... return self.data[i]
... def __add__(self, other):
... print('add: ' + other)
... return self.data + other
... def __str__(self):
... return self.data
PART II
classes
and types
please refer to http://www.cs.utexas.edu/~cannata/cs345/Class%20Notes/15%20Python%20Types%20and%20Objects.pdf
the type of an instance is its class, and the type of a user-defined class is the same as the type of a built-in object type.
classes are types
types are classes
how to determine if two objects are of the same type in both python2.x and python 3.x?
# python2
>>> class A(): pass
...
>>> class B(): pass
...
>>> a = A()
>>> b = B()
>>> type(a)
<type 'instance'>
>>> type(b)
<type 'instance'>
>>> type(a) == type(b)
True
>>> a.__class__ == b.__class__
False
# python3
>>> class A(): pass
...
>>> class B(): pass
...
>>> a = A()
>>> b = B()
>>> type(a)
<class '__main__.A'>
>>> type(b)
<class '__main__.B'>
>>> type(a) == type(b)
False
in python3.x, type(instance)
is the same as instance.__class__
.
>>> isinstance(object, type)
True
>>> isinstance(type, object)
True
PART III
DFLR
vs MRO
refer to http://python.jobbole.com/85685/ and http://hanjianwei.com/2013/07/25/python-mro/ for more details of the history and two methods.
PART IV*
__slots__
are used for saving space, which limits the the attributes that an object’s instance can create.
Let’s get several things straight.
1. __dict__
is a dictionary or mapping object used to store an object’s writable attributes.
2. difference between __dict__
in class and instance?
>>> class A: pass
...
>>> a = A()
>>>
>>> a.__dict__
{}
>>> A.__dict__
mappingproxy({'__doc__': None, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'A' objects>, '__dict__': <attribute '__dict__' of 'A' objects>})
mappingproxy
ensures keys for class-level attributes are only strings, which accelerates attribute lookup in method resolution order.
refer to:https://stackoverflow.com/questions/32720492/why-is-a-class-dict-a-mappingproxy
>>> class A: pass
...
>>> a = A()
>>> a.__dict__
{}
>>> A.__dict__
mappingproxy({'__doc__': None, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'A' objects>, '__dict__': <attribute '__dict__' of 'A' objects>})
>>> class B:
... __slots__ = 'x',
...
>>> b = B()
>>> b.__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'B' object has no attribute '__dict__'
>>> B.__dict__
mappingproxy({'__doc__': None, '__module__': '__main__', '__slots__': ('x',), 'x': <member 'x' of 'B' objects>})
>>> dir(A)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
>>> dir(B)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'x']
>>>
the declaration of __slots__
only deny the creation of __dict__
when create instance from this object(not on this object).
but why for this to happen?
B.__dict__
mappingproxy({'__doc__': None, '__module__': '__main__', '__slots__': ('x',), 'x': <member 'x' of 'B' objects>})
so how to list all instance-level attribute?
>>> class D:
... __slots__ = ['a', 'b', '__dict__']
... c = 3
... def __init__(self):
... self.d = 4
...
>>> x = D()
>>> x.a = 1
>>> x.b = 2
>>> for attr in list(getattr(x, '__dict__', [])) + getattr(x, '__slots__', []):
... print(attr, '=>', getattr(x, attr))
...
d => 4
a => 1
b => 2
__dict__ => {'d': 4}
slot names becomes class-level attributes, instances acquire the union of all slot names anywhere in the tree, by the inheritance order.
>>> class E:
... __slots__ = ['c', 'd']
...
>>>
>>> class D(E):
... __slots__ = ['a', '__dict__']
...
>>> x = D()
>>>
>>>
>>> x.a = 1
>>> x.b = 2
>>> x.c = 3
>>> x.d = 4
>>> x.__dict__
{'b': 2}
>>> x.__slots__
['a', '__dict__']
slot names, property and descriptors are the names that similarly reside in class level but provide attribute values for instance on request.
>>> class A: __slots__ = 'a'
...
>>> class B: __slots__ = 'b'
...
>>> class C(A, B): __slots__ = ()
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: multiple bases have instance lay-out conflict
this error means those two class could not be nested together.
>>> class A: __slots__ = 'a'
...
>>> class B: __slots__ = 'b',
...
>>> class C(A, B): __slots__ = 'a', 'b'
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: multiple bases have instance lay-out conflict
it seems not able to do multiple inheritance of classes with __code__
>>> class A:
... __slots__ = ('a')
...
>>> A.a = 11
>>> x = A()
>>> x.a = 11
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'A' object attribute 'a' is read-only
if class attribute shares same name as slot name, the attribute will become read-only, also this will overwrites the slot descriptor(a class attribute finally).
several rules:
>>> class A: pass
...
>>> class B(A): __slots__ = 'a'
...
>>> x = B()
>>> x.c = 100
slots in subs are pointless if absent in supers.
>>> class A: __slots__ = 'a'
...
>>> class B(A): pass
...
>>> x = B()
>>> x.c = 100
slots in supers are pointless if absent in subs.
redefinition in class renders slot name pointless and this class attribute is readonly.
no class-level defaults for slot names.
redefinition renders super slots pointless.
to get attribute source: from which super => mapping dir
results to objects in the MRO
to get attribute from this object => retrieve names from __dict__
if attr in getattr(obj, '__dict__', {}):
if hasattr(obj, '__dict__') and attr in obj.__dict__:
remember that slots and property are not physically stored in instance namespace dictionary __dict__
PART V
property are slots are both virtual attribute locates not in instance’s __dict__
.
refer to:
https://stackoverflow.com/questions/38682318/why-favor-object-setattr-self-name-value-only-in-new-style-classes
If __setattr__() wants to assign to an instance attribute, it should not simply execute self.name = value — this would cause a recursive call to itself. Instead, it should insert the value in the dictionary of instance attributes, e.g., self.__dict__[name] = value. For new-style classes, rather than accessing the instance dictionary, it should call the base class method with the same name, for example, object.__setattr__(self, name, value)
PART V
python attributes: instance attribute
, class attribute
and static attribute
.
class Methods:
def imeth(self, x):
print([self, x])
def smeth(x):
print([x])
def cmeth(cls, x):
print([cls, x])
smeth = staticmethod(smeth)
cmeth = classmethod(cmeth)
# python3
Python 3.4.5 (default, May 29 2017, 15:17:55)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from bothmethods import Methods
>>> m = Methods()
>>> m.imeth(100)
[<bothmethods.Methods object at 0x7f7748d782b0>, 100]
>>> m.cmeth(100)
[<class 'bothmethods.Methods'>, 100]
>>> m.smeth(100)
[100]
>>> Methods.cmeth(100)
[<class 'bothmethods.Methods'>, 100]
>>> Methods.smeth(100)
[100]
# python3
Python 3.4.5 (default, May 29 2017, 15:17:55)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-11)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> class Sample:
... def printf(x):
... return x
...
>>> s = Sample()
>>> s.printf(100)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: printf() takes 1 positional argument but 2 were given
>>> Sample.printf(100)
100
class attribute
could be accessed from both class or an instance.
python3.x allows simple functions in a class to serve as static methods without extra protocol, when called through a class object only.
difference between static method
and class method
: https://www.geeksforgeeks.org/class-method-vs-static-method-python/
class Spam:
num = 0
@classmethod
def count(cls):
cls.num += 1
def __init__(self):
self.count()
class Sub(Spam):
num = 0
def __init__(self):
super(Sub, self).__init__()
class Other(Spam):
num = 0
if __name__ == '__main__':
s1, s2 , s3 = Spam(), Spam(), Spam()
s4, s5 = Sub(), Sub()
s6 = Other()
print(Spam.num) --> 3
print(Sub.num) --> 2
print(Other.num) --> 1
class method
holds status for each layer of class, because using class methods will receive the lowest class of the call’s subject.
static method
may be a better solution for processing data local to a class.
class method
may be a better solution for processing data that may differ for each class in hierarchy.
Another example:
since python doesn’t support function overloading, class method
could be used to define different factory methods.
from datetime import date
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
@classmethod
def fromBirthYear(cls, name, year):
return cls(name, date.today().year-year)
@staticmethod
def isAdult(age):
return age > 18
class Employee(Person):
pass
if __name__ == '__main__':
p1 = Person('alice', 21)
p2 = Person('bob', 22)
p3 = Employee.fromBirthYear('cathy', 2001)
print(p1.age) --> 21
print(p2.age) --> 22
print(p3.age) --> 2018 - 2001 = 17
print(p3.__class__) --> <class '__main__.Employee'>