typedef struct _object {
PyObject_HEAD
} PyObject;
struct _object *_ob_next;
struct _object *_ob_prev;
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
# id (built-in function)
# returns object's address in memory
In [1]: id(1)
Out[1]: 4452383728
In [2]: id('')
Out[2]: 4453206704
In [3]: id(None)
Out[3]: 4452107176
In [4]: id([])
Out[4]: 4477498568
In [5]: id(...)
Out[5]: 4452118880
# is (statement) compares objects
# by their identity i.e. by their
# address in memory
In [6]: 1 is 1
Out[6]: True
In [7]: -6 is -6
Out[7]: False
In [9]: 'asd' is 'asd'
Out[9]: True
In [10]: a, b = 'asd!', 'asd!'
In [11]: a is b
Out[11]: True
In [12]: a = 'asd!'
In [13]: b = 'asd!'
In [14]: a is b
Out[14]: False
In [28]: -6 is -6
Out[28]: False
In [29]: 257 is 257
Out[29]: True
In [36]: a = 257
In [37]: b = 257
In [38]: a is b
Out[38]: False
In [1]: 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
Out[1]: True
In [2]: 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
Out[2]: False
In [11]: 'a' + 'sd' is 'asd'
Out[11]: True
In [12]: ''.join(['a', 's', 'd']) is 'asd'
Out[12]: False
In [3]: 'asd!' is 'asd!'
Out[3]: True
In [6]: a, b = 'asd!', 'asd!'
In [7]: a is b
Out[7]: True
In [8]: a = 'asd!'
In [9]: b = 'asd!'
In [10]: a is b
Out[10]: False
In [1]: True + True == 2 - False
Out[1]: True
In [2]: a = True
In [3]: a is True
Out[3]: True
In [4]: a = ...
In [5]: a is ...
Out[5]: True
In [6]: a = None
In [7]: a is None
Out[7]: True
# ellipsis is useful when You need default
# value but None is also an option
default = ...
def update_user(name, email, address=default):
user = db.get_user(email)
if address is not default:
user.address = address
user.name = name
user.email = email
db.update_user(user)
If You would ever consider track object's uniqueness by their id without storing links on them
ids = set() # just a set for some object's identifiers
def f():
l2 = [1, 2, 3] # create a list
ids.add(id(l2)) # store its address in global registry
f() # call function
l3 = ['1', '2', '3', '4'] # create new list with other data
print(id(l3) in ids)
class Person: # always uppercased camel-case (pep-8)
name = 'Person'
def __init__(self, name):
self._name = name
self.__secret_name = 'secret-{}'.format(name)
def get_name(self, *args, **kwargs):
print('I am {}'.format(self))
@classmethod
def get_class_name(cls, *args, **kwargs):
print('I am {}'.format(cls))
@staticmethod
def get_static_name():
print('I have no name actually =(')
person = Person('Bob')
person.get_name()
Person.get_class_name()
Person.get_static_name()
print(person.name, person._name)
print(person.__secret_name)
I am <__main__.Person object at 0x10bbb8cf8>
I am <class '__main__.Person'>
I have no name actually =(
Person Bob
Traceback (most recent call last):
File "t.py", line 29, in <module>
print(person.__secret_name)
AttributeError: 'Person' object has no attribute '__secret_name'
Java and C++ people where you at?
class Object:
def __new__(cls, *args, **kwargs):
print(cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print(self)
def __del__(self):
print('{} is down!'.format(self))
b = Object()
a = Object()
del a
Object()
<class '__main__.Object'>
<__main__.Object object at 0x10c6c9c18>
<class '__main__.Object'>
<__main__.Object object at 0x10c6c9c50>
<__main__.Object object at 0x10c6c9c50> is down!
<class '__main__.Object'>
<__main__.Object object at 0x10c6c9c50>
<__main__.Object object at 0x10c6c9c50> is down!
<__main__.Object object at 0x10c6c9c18> is down!
from weakref import WeakSet
class A:
pass
references = set()
weak_references = WeakSet()
a1, a2 = A(), A()
references.add(a1)
weak_references.add(a2)
print(references)
print(weak_references.data)
del a1
del a2
print(references)
print(weak_references.data)
{<__main__.A object at 0x10b532b38>}
{<weakref at 0x10b537b88; to 'A' at 0x10b532ba8>}
{<__main__.A object at 0x10b532b38>}
set()
class A:
def foo(self):
print('from A')
class B(A):
def foo(self):
print('from B')
super().foo()
class C(A):
def foo(self):
print('from C')
super().foo()
class D(B, C):
def foo(self):
print('from D')
super().foo()
D().foo()
from D
from B
from C
from A
D.mro()
[
<class '__main__.D'>,
<class '__main__.B'>,
<class '__main__.C'>,
<class '__main__.A'>,
<class 'object'>
]
class A:
def foo(self):
print('from A')
class B(A):
def foo(self):
print('from B')
super().foo()
# eq to super(B, self).foo()
class C(A):
def foo(self):
print('from C')
super().foo()
# eq to super(C, self).foo()
class D(B, C):
def foo(self):
print('from D')
super().foo()
# eq to super(D, self).foo()
D().foo()
(1) So its D class and super will give B
(2) So its B class and super will give C
(3) So its C class and super will give A
(4) So its A class
class A:
def foo(self):
print('from A')
class B(A):
def foo(self):
print('from B')
super().foo()
class C(A):
def foo(self):
print('from C')
super().foo()
class D(B, C):
@classmethod
def mro(cls):
return [D, B, A]
def foo(self):
print('from D')
super().foo()
print(D.mro())
D().foo()
Yes, but not the way You'd expect it to be
And I dare You don't
[
<class '__main__.D'>,
<class '__main__.B'>,
<class '__main__.A'>
]
from D
from B
from C
from A
class meta(type):
@classmethod
def mro(cls):
return [B, A, object]
class A:
def foo(self):
print('from A')
class B(A):
def foo(self):
print('from B')
super().foo()
class C(A):
def foo(self):
print('from C')
super().foo()
class D(B, C, metaclass=meta):
def foo(self):
print('from D')
super().foo()
D().foo()
from B
from A
Since mro for D class doesn't contain it, its namespace doesn't even looked up
So it just goes for B and A classes
If You won't include object You won't be able to create objects at all
(1) True
(2)
{
'foo': <function A.foo at 0x10dd82158>,
'a': None,
'__dict__': <attribute '__dict__' of 'A' objects>,
'__weakref__': <attribute '__weakref__' of 'A' objects>,
'__module__': '__main__',
'__init__': <function A.__init__ at 0x10dd820d0>,
'__doc__': 'This class literally does nothing'
}
(3) {'name': 'Bill'}
(4) {'surname': 'Billinsons', 'name': 'Bill'}
(5) J
class A:
'''This class literally does nothing'''
a = None
def __init__(self, name, surname=None):
self.name = name
if surname is not None:
self.surname = surname
def foo(self):
print('My name is {}'.format(self.name))
print('__dict__' in dir(A)) # 1
print(A.__dict__) # 2
print(A('Bill').__dict__) # 3
print(A('Bill', 'Billinsons').__dict__) # 4
john = A('John')
john.__dict__['middlename'] = 'J'
print(john.middlename) # 5
class A:
__slots__ = ('a', 'name')
def __init__(self, name):
self.a = 'A!'
self.name = name
def foo(self):
print('My name is {}'.format(self.name))
print('__dict__' in dir(A)) # 1
print(A.__dict__) # 2
bill = A('Bill')
print(bill.a, bill.name) # 3
bill.foo() # 4
print(bill.__dict__) # 5
(1) False
(2)
{
'name': <member 'name' of 'A' objects>,
'foo': <function A.foo at 0x1065fd158>,
'a': <member 'a' of 'A' objects>,
'__module__': '__main__',
'__doc__': None,
'__slots__': ('a', 'name'),
'__init__': <function A.__init__ at 0x1065fd0d0>
}
(3) A! Bill
(4) My name is Bill
(5)
Traceback (most recent call last):
File "t.py", line 21, in <module>
print(bill.__dict__)
AttributeError: 'A' object has no attribute '__dict__'
In short - there are no such things.
Protocols are, but its not more than a convention
class ContextManager:
def __enter__(self):
pass
def __exit__(self, *exc_info):
pass
class Iterable:
def __iter__(self):
return iterator
class Iterator:
def __next__(self):
return next_value
from abc import ABCMeta, abstractmethod
class BaseMerger(metaclass=ABCMeta):
@abstractmethod
def get(self, _id):
pass
@abstractmethod
def create(self, data):
pass
@abstractmethod
def update(self, _id, data):
pass
def merge(self, _id, new_data):
old_obj = self.get(_id)
if old_obj is None:
return self.create(new_data)
return self.update(_id, new_data)
Wrong
class BaseMerger:
def get(self, _id):
raise NotImplementedError()
def create(self, data):
raise NotImplementedError()
def update(self, _id, data):
raise NotImplementedError()
def merge(self, _id, new_data):
old_obj = self.get(_id)
if old_obj is None:
return self.create(new_data)
return self.update(_id, new_data)
Correct
class Storage:
__storage = []
def store(self, val):
self.__storage.append(val)
def __repr__(self):
return str(self.__storage)
class FutureStorage(Storage):
pass
s = Storage()
s.store(1)
s.store(2)
print(s) # 1
ss = Storage()
print(ss) # 2
fs = FutureStorage()
print(fs) # 3
(1) [1, 2]
(2) [1, 2]
(3) [1, 2]
Wrong
Correct
class Storage:
__storage = None
def __init__(self):
self.__storage = []
def store(self, val):
self.__storage.append(val)
def __repr__(self):
return str(self.__storage)
class FutureStorage(Storage):
pass
s = Storage()
s.store(1)
s.store(2)
print(s)
ss = Storage()
print(ss)
fs = FutureStorage()
print(fs)
(1) [1, 2]
(2) []
(3) []
Person = type('Person', (), {'name': '', 'surname': ''})
print(type(Person)) # 1
print(Person) # 2
# p = Person('Patrick', 'Oneil')
# p = Person(name='Patrick', surname='Oneil')
p = Person()
p.name, p.surname = 'Patrick', 'Oneil'
print(type(p)) # 3
print(p) # 4
print(p.name, p.surname) # 5
(1) <class 'type'>
(2) <class '__main__.Person'>
(3) <class '__main__.Person'>
(4) <__main__.Person object at 0x10a373be0>
(5) Patrick Oneil
class Versioned(type):
def __init__(cls, name, bases, params, **kwds):
super().__init__(name, bases, params)
def __prepare__(name, bases, version=None, **kwds):
namespace = {}
if version is not None:
namespace['version'] = version
return namespace
def __new__(cls, cls_name, bases, params, **kwds):
print('Creating class object for {}'.format(cls_name))
return super().__new__(cls, cls_name, bases, params)
class Person(metaclass=Versioned, version=1):
pass
print(Person().version)
Creating class object for Person
1
class Singletoned(type):
@classmethod
def __prepare__(cls, name, bases, **kwds):
return {'_obj': None}
def __call__(cls, *args, **kwargs):
if cls._obj is None:
print('Creating new object for {}'.format(cls))
cls._obj = super().__call__(*args, **kwargs)
return cls._obj
class A(metaclass=Singletoned):
pass
class B(metaclass=Singletoned):
pass
a = A()
b = A()
print(a) # 1
print(b) # 2
a = B()
b = B()
print(a) # 3
print(b) # 4
Creating new object for <class '__main__.A'>
(1) <__main__.A object at 0x10c9b0c88>
(2) <__main__.A object at 0x10c9b0c88>
Creating new object for <class '__main__.B'>
(3) <__main__.B object at 0x10c9b0cc0>
(4) <__main__.B object at 0x10c9b0cc0>
Instance attribute lookup
Class attribute lookup
You know, python's, not the file ones
Class implementing a protocol
class Person:
name = ''
age = 0
def __check_name(self, name):
if not isinstance(name, str):
raise TypeError
return name
def __check_age(self, age):
if not isinstance(age, int):
raise TypeError
return age
def __init__(self, name, age):
self.age = age
self.name = name
def __setattr__(self, name, value):
if name == 'name':
value = self.__check_name(value)
elif name == 'age':
value = self.__check_age(value)
super().__setattr__(name, value)
def __repr__(self):
return 'Person: {} {}'.format(self.name, self.age)
p = Person('Bill', 29)
print(p)
p.name = 123
Person: Bill 29
Traceback (most recent call last):
File "t.py", line 38, in <module>
p.name = 123
File "t.py", line 25, in __setattr__
value = self.__check_name(value)
File "t.py", line 8, in __check_name
raise TypeError
TypeError
class Typed:
def __init__(self, _type, default=...):
self.val = default if default is not ... else _type
self.type = _type
def __set__(self, obj, value):
if not isinstance(value, self.type):
raise TypeError
self.val = value
def __get__(self, obj, type=None):
return self.val
def __delete__(self, obj):
raise AttributeError
class Person:
name = Typed(str)
age = Typed(int)
def __init__(self, name, age):
self.age = age
self.name = name
def __repr__(self):
return 'Person: {} {}'.format(self.name, self.age)
p = Person('Bill', 29)
print(p)
p.name = 123
Person: Bill 29
Traceback (most recent call last):
File "t.py", line 36, in <module>
p.name = 123
File "t.py", line 9, in __set__
raise TypeError
TypeError
class Descriptor:
def __set__(self, obj, value):
pass
def __get__(self, obj, type=None):
return 'Look at me!'
class A:
a = Descriptor()
def __init__(self):
self.a = 'Hey ya!'
a = A()
print(a.a)
Look at me!
class Property(object):
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
class Square:
def __init__(self, width, height):
self.width = width
self.height = height
def _area(self):
return self.width * self.height
area = Property(_area)
del _area
box = Square(7, 15)
print(box.area)
box.area = 150
105
Traceback (most recent call last):
File "t.py", line 56, in <module>
box.area = 150
File "t.py", line 21, in __set__
raise AttributeError("can't set attribute")
AttributeError: can't set attribute