_foo
)from <module/package> import *
, none of the names that start with an _
will be imported unless the module's/package's __all__
list explicitly contains them.__foo
)__foo
(at least two leading underscores, at most one trailing underscore) is textually replaced with _classname__foo
, where classname is the current class name with leading underscore(s) stripped. This is called name mangling. (source: Python documentation).__foo__
)__name__
is the class name, __module__
is the module name in which the class was defined, __dict__
is the dictionary containing the class’s namespace, __bases__
is a tuple containing the base classes.# name mangling mechanism
class Mapping:
def __init__(self, iterable):
self.items_list = []
# self.__update inside the class is equivalent to self._Mapping__update
# the same function will be called even if __update is overridden in inheriting classes
self.__update(iterable)
def __update(self, iterable):
for item in iterable:
self.items_list.append(item)
class MappingSub(Mapping):
def __update(self, keys, values):
# provides new signature for __update() but does not break __init__()
for item in zip(keys, values):
self.items_list.append(item)
m = Mapping([1, 2])
ms = MappingSub([1,2])
print('__update' in dir(m), '__update' in dir(ms))
m._Mapping__update([3, 4])
print(m.items_list)
ms._Mapping__update([3, 4]) # call update function of Mapping class
ms._MappingSub__update([5, 6], ['five', 'six']) # call update of MappingSub class
print(ms.items_list)
False False [1, 2, 3, 4] [1, 2, 3, 4, (5, 'five'), (6, 'six')]
objectname.attributename
and voila! you now have the handle to another object.objectname.attributename = anotherobject
.Depending on the programming language:
__getattribute__
method, i.e. a.x -> a.__getattribute__(x)
.__getattribute__
has an order of priority that describes where to look for attributes and how to react to them.__dict__
where user provided attributes are stored and looked up.__dict__
(e.g. special methods).__dict__
is looked up first and this is how we override special methods.__slots__
. However we cannot add new attributes to __slots__
.class B:
x = 1
class A(B):
y = 2
def __getattr__(self, value):
return str(value)
a = A()
print("a.x: {}, a.y: {}".format(a.x, a.y)) # x from B, y from A
a.y = 3
print("a.x: {}, a.y: {}, A.y: {}".format(a.x, a.y, A.y)) # x from B, y from a (overrides y in A)
print("a.z: {}".format(a.z)) # call __getattr__
print(A.__dict__)
print(a.__dict__)
a.x: 1, a.y: 2 a.x: 1, a.y: 3, A.y: 2 a.z: z {'__module__': '__main__', 'y': 2, '__getattr__': <function A.__getattr__ at 0x0000022EE39A7EA0>, '__doc__': None} {'y': 3}
Raymond Hettinger (Python Documentation):
In general, a descriptor is an object attribute with "binding behavior", one whose attribute access has been overridden by methods in the descriptor protocol.
__get__
, __set__
and __delete__
. If any of those methods are defined for an object, it is said to be a descriptor.__set__
or __delete__
, but can include both. They also often include __get__
, since it's rare to want to set something without also being able to get it too.__get__
. If it adds __set__
or __delete__
to its method list, it becomes a data descriptor.__get__(self, instance, owner)
¶self
is the descriptor instance.owner
is the class the descriptor is accessed from.A.x
, where x
is a descriptor object with __get__
, it's called with A
as owner and instance
as None
.__get__
is being called from a class, not an instance.A.x
is translated to A.__dict__['x'].__get__(None, A)
.instance
is the instance that the descriptor is accessed from.instance
and the class of the instance as owner
.a.x
is translated to type(a).__dict__['x'].__get__(a, type(a))
type(a)
, not just a
, because descriptors are stored on classes not instances.Two important points:
instance
and owner
(the class of the instance).instance
parameter is the instance the descriptor is being called from. It is actually being called from the instance class.__set__(self, instance, value)
¶__set__
does not have an owner parameter that accepts a class and does not need it, since data descriptors are generally designed for storing per-instance data.A.x = value
does not get translated to anything; value
replaces the descriptor object stored in x
(however, see note below).a.x = value
is translated to type(a).__dict__['x'].__set__(a, value)
__delete__(self, instance)
¶del a.x
is called.del a.x
is translated to type(a).__dict__['x'].__delete__(a)
Note: If we want a descriptor's __set__
or __delete__
methods to work from the class level, the descriptor must be created on the class's metaclass. When doing so, everything that refers to owner
is referring to the metaclass, while a reference to instance
refers to the class. After all, classes are just instances of metaclasses.
__getattribute__
method.__getattribute__
prevents automatic descriptor calls.__getattribute__
, but it's the one defined on its metaclass.__getattr__
method.__getattr__
method.a.x
)¶__dict__
, working up the MRO.__get__
method, call it and return the result.__dict__
.__dict__
.__dict__
again, working up the MRO.__get__
method, call it and return the result.__get__
method, return the descriptor object itself.__dict__
.__getattr__
if it exists and return the result.AttributeError
.A.x
)¶__dict__
, working up the MRO.__get__
method, call it and return the result.__dict__
, working up the MRO.__get__
method, call it and return the result.__get__
method, return the descriptor object itself.__dict__
.__dict__
again, working up the MRO.__get__
method, call it and return the result.__get__
method, return the descriptor object itself.__dict__
.__getattr__
if it exists and return the result.AttributeError
.__set__
& __delete__
(a.x = value
& del a.x
)¶__dict__
, working up the MRO.__set__
or __delete__
method, call __set__
or __delete__
.AttributeError
.__dict__
.a.x = value
del a.x
AttributeError
.__set__
& __delete__
(A.x = value
& del A.x
)¶__dict__
, working up the MRO.__set__
or __delete__
method, call __set__
or __delete__
.AttributeError
.__dict__
.A.x = value
del A.x
AttributeError
.# implementation of classmethod and staticmethod, equivalent to the standard library
class MyClassmethod:
def __init__(self, func):
self.func = func
# ignore the instance, provide the class as first argument (usually named cls) so the
# returned function can be called with the arguments the user wants to explicitly provide
def __get__(self, instance, owner):
def cls_wrapper(*args, **kwargs):
return self.func(owner, *args, **kwargs) # what if I put cls=owner?
return cls_wrapper
class MyStaticmethod:
def __init__(self, func):
self.func = func
# essentially just accepts a function and then returns it when __get__ is called
def __get__(self, instance, owner):
return self.func
class A:
def foo(self):
print(self)
@MyClassmethod # same as: bar = MyClassmethod(bar)
def bar(cls):
print(cls)
@MyStaticmethod # same as: baz = MyStaticmethod(baz)
def baz():
print('static method')
# both methods are accessed through their respective descriptors
a = A()
# instance method, business as always
a.foo()
print()
# access the method object, descriptor is called and returns the cls_wrapper method object
print(A.bar)
# call the method, instance in __get__ is None (don't care), owner is A
A.bar()
# run it on the instance, instance in __get__ is a (don't care), owner is A
a.bar()
print()
# access the method object, descriptor returns a function with no arguments
print(A.baz)
# call the method without any instance (self) or class (cls) object
# of course same result if we call it in the instance
A.baz()
a.baz()
<__main__.A object at 0x0000022EE39CCF60> <function MyClassmethod.__get__.<locals>.cls_wrapper at 0x0000022EE39D2510> <class '__main__.A'> <class '__main__.A'> <function A.baz at 0x0000022EE39D2268> static method static method
# implementation of property, equivalent to the standard library
class MyProperty:
def __init__(self, fget=None, fset=None, fdel=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
def __get__(self, instance, owner):
if instance is None: # this was called from the class, not the instance
return self
elif self.fget is None:
raise AttributeError("unreadable attribute")
else:
return self.fget(instance)
def __set__(self, instance, value):
if self.fset is None:
raise AttributeError("can't set attribute")
else:
self.fset(instance, value)
def __delete__(self, instance):
if self.fdel is None:
raise AttributeError("can't delete attribute")
else:
self.fdel(instance)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel)
class A:
def __init__(self, x):
self._x = x
@MyProperty
def x(self):
print("returning _x: {}".format(self._x))
return self._x
@x.setter
def x(self, value):
print("setting _x to {}".format(value))
self._x = value
a = A(1)
print(A.x) # call the descriptor from the class, returns the descriptor object
print(a.x)
print()
a.x = 2
print(a.x)
print()
A.x = 'bye bye descriptor'
print(a.x) # descriptor is gone from class, instance gets attribute from class
<__main__.MyProperty object at 0x0000022EE39EBF28> returning _x: 1 1 setting _x to 2 returning _x: 2 2 bye bye descriptor
property
?property
is awesome! Use property
for greater good!class BasketballGame:
def __init__(self, points, rebounds, steals):
self.points = points
self.rebounds = rebounds
self.steals = steals
@property
def points(self):
return self._points
@points.setter
def points(self, value):
if value < 0:
raise ValueError('Positive values only!')
self._points = value
@property
def rebounds(self):
return self._rebounds
@rebounds.setter
def rebounds(self, value):
if value < 0:
raise ValueError('Positive values only!')
self._rebounds = value
@property
def steals(self):
return self._steals
@steals.setter
def steals(self, value):
if value < 0:
raise ValueError('Positive values only!')
self._steals = value
class NonNegativeField:
def __init__(self, name=''):
# need to store the field name on the descriptor object itself
# as descriptors are defined on the class level
self.name = name
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
if value < 0:
raise ValueError('Positive values only!')
instance.__dict__[self.name] = value
class BasketballGame:
# is there a better way, so that we don't have to repeat the field name?
points = NonNegativeField('points')
rebounds = NonNegativeField('rebounds')
steals = NonNegativeField('steals')
def __init__(self, points, rebounds, steals):
self.points = points
self.rebounds = rebounds
self.steals = steals
a = BasketballGame(points=100, rebounds=30, steals=10)
print("points: {}, rebounds: {}".format(a.points, a.rebounds))
try:
a.points = -5
except ValueError as e:
print("Error! {}".format(e))
points: 100, rebounds: 30 Error! Positive values only!
def named_descriptors(cls):
for name, attr in cls.__dict__.items():
if isinstance(attr, NonNegativeField):
attr.name = name
return cls
@named_descriptors
class BasketballGame:
points = NonNegativeField()
rebounds = NonNegativeField()
steals = NonNegativeField()
def __init__(self, points, rebounds, steals):
self.points = points
self.rebounds = rebounds
self.steals = steals
a = BasketballGame(points=100, rebounds=30, steals=10)
print("points: {}, rebounds: {}".format(a.points, a.rebounds))
try:
a.points = -5
except ValueError as e:
print("Error! {}".format(e.args))
points: 100, rebounds: 30 Error! ('Positive values only!',)
class NonNegativeField:
def __get__(self, instance, owner):
return instance.__dict__[self.name]
def __set__(self, instance, value):
if value < 0:
raise ValueError('Positive values only!')
instance.__dict__[self.name] = value
# new in Python 3.6
def __set_name__(self, owner, name):
self.name = name
class BasketballGame:
points = NonNegativeField()
rebounds = NonNegativeField()
steals = NonNegativeField()
def __init__(self, points, rebounds, steals):
self.points = points
self.rebounds = rebounds
self.steals = steals
a = BasketballGame(points=100, rebounds=30, steals=10)
print("points: {}, rebounds: {}".format(a.points, a.rebounds))
try:
a.points = -5
except ValueError as e:
print("Error! {}".format(e))
points: 100, rebounds: 30 Error! Positive values only!