Allows you to customize or extend the behavior of built-in types with user-defined class
class MyList(list):
def __str__(self):
print("Content: ")
return list.__str__(self)
ml = MyList([1, 2, 3])
print(ml)
Content: [1, 2, 3]
class C(object):
pass
class GetAttrTest(object):
data = "spam"
def __getattr__(self, attrname):
print(attrname)
return getattr(self.data, attrname)
g = GetAttrTest()
g.__add__ = lambda y: 88 + y
g + 1
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-3-4fc49c6ca986> in <module>() 8 9 g.__add__ = lambda y: 88+y ---> 10 g + 1 TypeError: unsupported operand type(s) for +: 'GetAttrTest' and 'int'
Direct calls to built-in method names still work, but equivalent expression is not
# Direct calls still work
g.__add__(1)
89
To code a proxy of an object whose interface may in part be invoked by built-in operations, new style classes require both --getattr__
for normal names, as well as method redefinitions for all names accessed by built-in operations
type(I) return the class an instance is made from, instead of generic instance type
type(5)
int
isinstance(5, (int, str))
True
type and class hasve merged - type is a kind of object while object is a kind of type
isinstance(type, object)
True
isinstance(object, type)
True
A small set of default operator overloading method (e.g. __repr__
)
%%python2
# To make this cell magic runs ok please ensure python2 can be run when you type python2 in terminal
# Reference: http://stackoverflow.com/questions/30201431/ipython-cell-magics
# DFLR: D -> B -> A -> C -> A
class A: attr =1
class B(A): pass
class C(A): attr = 2
class D(B, C): pass
x = D()
print(x.attr) # x -> D -> B -> A
1
# MRO: D -> B -> C -> A
class A:
attr = 1
class B(A):
pass
class C(A):
attr = 2
class D(B, C):
pass
x = D()
print(x.attr) # x -> D -> B -> C
2
You can simply resolve this by using attr = C.attr
in in class D
%%python2
class A: attr =1
class B(A): pass
class C(A): attr = 2
class D(B, C): attr = C.attr
x = D()
print(x.attr) # x -> D -> B -> A
2
__mro__
¶class A:
attr = 1
class B(A):
pass
class C(A):
attr = 2
class D(B, C):
pass
D.__mro__
(__main__.D, __main__.B, __main__.C, __main__.A, object)
class limiter(object):
__slots__ = ["age", "name", "job"]
x = limiter()
x.age
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-13-a33d945640ff> in <module>() 3 4 x = limiter() ----> 5 x.age AttributeError: age
x.age = 40 # must be assign first
print(x.age)
40
x.ape = 1000
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-15-483ef4f9f9f3> in <module>() ----> 1 x.ape = 1000 AttributeError: 'limiter' object has no attribute 'ape'
__dict__
used for objects' attributes may share part of their internal storage, including that of their keys. This may lessen some of the value of __slots__
as an optimization tool__slots__
do not have __dict__
by defualtclass C(object):
__slots__ = ["a"]
c = C()
c.__dict__
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-16-4b9d1d768956> in <module>() 3 4 c = C() ----> 5 c.__dict__ AttributeError: 'C' object has no attribute '__dict__'
dir(c)
['__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__', 'a']
__dict__
can still be included in __slots__
class C(object):
__slots__ = ["a", "b", "__dict__"]
d = 3
def __init__(self):
self.d = 5
self.a = 1
c = C()
c.e = 5
c.__dict__
{'d': 5, 'e': 5}
Without an attribute namespace dict, it's not possible to assign new names to instances that are not in slots list
class C(object):
__slots__ = ["a", "b"]
def __init__(self):
self.d = 4
c = C()
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-19-f57b79b6a9a7> in <module>() 3 def __init__(self): 4 self.d = 4 ----> 5 c = C() <ipython-input-19-f57b79b6a9a7> in __init__(self) 2 __slots__ = ['a', 'b'] 3 def __init__(self): ----> 4 self.d = 4 5 c = C() AttributeError: 'C' object has no attribute 'd'
__dict__
created for the superclass will always be accessible__dict__
__dict__
__dict__
, unless it's listed explicitlyslots essentially require both universal and careful deployment to be effective
slots are not generally recommended, except in pathologicall cases where their space reduction is significant
class Proper(object):
def getage(self):
return 40
age = property(getage, None, None, None) # (get, set, del, docs)
p = Proper()
p.age
40
__getattribute__
and Descriptors¶__getattribute__
is available for new-style classes only and used to intercept all attribute__setattr__
__get__
and __set__
class AgeDesc(object):
def __get__(self, instance, owner):
return 30
def __set__(self, instance, value):
instance._age = value
class D(object):
age = AgeDesc()
d = D()
d.age
30
d.age = 42
d._age
42
# Works both Python2 and Python3
class C(object):
@staticmethod
def print_one():
print(1)
c = C()
c.print_one()
1
# Python3 Only
class C(object):
def print_one():
print(1)
C.print_one()
1
Look at the code above.
Since print_one does not operate any class or instance data, it may be a good idea to make it static
Methods of a class that are passed a class object in their first argument instead of an instance, regardless of whether they are called through an instance or a class
Receive the lowest class of the call's subject
class C(object):
counter = 0
@classmethod
def cmethod(cls):
cls.counter += 1
print(cls.counter)
c1 = C()
c1.cmethod()
c2 = C()
c2.cmethod()
1 2
# Decorator version
class C:
@staticmethod
def meth():
pass
class C:
def meth():
pass
meth = staticmethod(meth)
Use __call__
class tracer:
def __init__(self, func):
self.calls = 0
self.func = func
def __call__(self, *args):
self.calls += 1
print("Call {} to {}".format(self.calls, self.func.__name__))
return self.func(*args)
@tracer
def spam(a, b, c): # Same as spam=tracer(spam)
return a + b + c
print(spam(1, 2, 3))
print(spam(3, 4, 5))
Call 1 to spam 6 Call 2 to spam 12
The spam function is run through the tracer decorator, when the original spam is called it actually triggers the __call__
in the class
*name
argument is used to pack and unpack the passed-in arguments, because of this, this decorator can be used to wrap any function with any number of positional arguments
__new__
or __init__
of the type class that normally intercepts this calldef decorator(a_class):
pass
@decorator
class C(object):
pass
def decoraor(a_class):
pass
class C(object):
pass
C = decoraor(C)
class X(object):
a = 1
X.a = 2
x2 = X()
print(x2.a)
2