- Thanks to the Python data model, your user-defined types can behave as naturally as the built-in types. And this can be accomplished without inheritance, in the spirit of duck typing: you just implement the methods needed for your objects to behave as expected.
1. Classmethod Versus Staticmethod
class Test: @staticmethod def f1(*args): print args # define a method that operates on the class and not on instances. @classmethod def f2(*args): # cls = args[0] print args t = Test() t.f1('1') # ('1',) t.f2('1') # (<class __main__.Test at 0x10afeca78>, '1') # 1. Classmethod's most common use is for alternative constructors. # 2. In essence, a static method is just like a plain function that # happens to live in a class body, instead of being defined at the # module level.
2. Formatted Displays
brl = 1/2.43 print(brl) # 0.4115226337448559 print(format(brl, '0.4f')) # 0.4115 print('BRL = {rate:0.2f}'.format(rate=brl)) # BRL = 0.41 print(format(42, 'b')) # 101010 print(format(2/3, '.1%')) # 66.7% from datetime import datetime now = datetime.now() print("It's {:%I:%M %p}".format(now)) # It's 10:59 AM class A: def __init__(self, a1, a2): self.a1 = a1 self.a2 = a2 def __iter__(self): return (i for i in (self.a1, self.a2)) def __format__(self, fmt_spec=''): if fmt_spec is '': fmt_spec = '({}, {})' return fmt_spec.format(*self) a = A(3, 4) print(format(a)) # (3, 4) print(format(a, '-->{}, {}<--')) # -->3, 4<-- # 1. A format string such as '{0.mass: 5.3e}' actually uses two # separate notations. The '0.mass' to the left of the colon is the # field_name part of the replacement field syntax; the '5.3e' after # the colon is the formatting specifier. # 2. A few built-in types have their own presentation codes. # 3. If a class has no __format__, format(obj) returns str(obj)
3. Private and “Protected” Attributes in Python
- Python stores the attributes with two leading underscores in the instance __dict__, prefixed with a leading underscore and the class name. (Dog + __mood --> _Dog__mood)
- Name mangling is about safety, not security: it’s designed to prevent accidental access and not intentional wrongdoing.
- But if you are doing 'v1._Vector__x = 7' in production code, you can’t complain if something blows up.
- Attributes with a single _ prefix are called “protected” in some corners of the Python documentation.9 The practice of “protecting” attributes by convention with the form self._x is widespread, but calling that a “protected” attribute is not so common. Some even call that a “private” attribute.
4. Object Representations
from array import array import math class Vector2d: typecode = 'd' def __init__(self, x, y): self.__x = float(x) self.__y = float(y) @property def x(self): return self.__x @property def y(self): return self.__y def __iter__(self): return (i for i in (self.x, self.y)) # iterator # yield self.x; yield.self.y def __repr__(self): # for developer class_name = type(self).__name__ # safer to inherit return '{}({!r}, {!r})'.format(class_name, *self) # iterable def __str__(self): # for user return str(tuple(self)) def __bytes__(self): return (bytes([ord(self.typecode)]) + bytes(array(self.typecode, self))) def __eq__(self, other): # It works for Vector2d operands but also returns True when comparing # Vector2d instances to other iterables holding the same numeric values # (e.g., Vector(3, 4) == [3, 4]). return tuple(self) == tuple(other) def __abs__(self): return math.hypot(self.x, self.y) def __bool__(self): return bool(abs(self)) @classmethod def frombytes(cls, octets): # Read the typecode from the first byte. typecode = chr(octets[0]) # Create a memoryview from the octets and use the typecode to cast it. memv = memoryview(octets[1:]).cast(typecode) # print(memv) # <memory at 0x1012fb108> # print(*memv) # 3.0 4.0 return cls(*memv) def __format__(self, fmt_spec=''): if fmt_spec.endswith('p'): fmt_spec = fmt_spec[:-1] outer_fmt = '<{}, {}>' else: outer_fmt = '({}, {})' components = (format(c, fmt_spec) for c in self) return outer_fmt.format(*components) def __hash__(self): return hash(self.x) ^ hash(self.y) v1 = Vector2d(3, 4) print(v1.x) # 3.0 # v1.x = 5 # AttributeError: can't set attribute a, b = v1 # __iter__ print(a, b) # 3.0 4.0 print(repr(v1)) # Vector2d(3.0, 4.0) print(v1) # (3.0, 4.0) __str__ print(bytes(v1)) # b'dx00x00x00x00x00x00x08@x00x00x00x00x00x00x10@' print(v1 == eval(repr(v1))) # True __eq__ print(abs(v1)) # 5.0 print(bool(v1)) # True print(bool(Vector2d(0, 0))) # False v2 = Vector2d.frombytes(bytes(v1)) print(v1 == v2) # True print(format(v1)) # (3.0, 4.0) print(format(v1, '.3f')) # (3.000, 4.000) print(format(v1, '.2ep')) # <3.00e+00, 4.00e+00> print(hash(v1)) # 7 # 1. In Python 3, __repr__, __str__, and __format__ must always return # Unicode strings (type str). Only __bytes__ is supposed to return a # byte sequence (type bytes). # 2. We can implement the __hash__ method. It should return an int and # ideally take into account the hashes of the object attributes that # are also used in the __eq__ method, because objects that compare equal # should have the same hash. The __hash__ special method documentation # suggests using the bitwise XOR operator (^) to mix the hashes of the # components. # 3. It’s not strictly necessary to implement properties or otherwise # protect the instance attributes to create a hashable type. # Implementing __hash__ and __eq__ correctly is all it takes. But the # hash value of an instance is never supposed to change.
5. Saving Space with the __slots__ Class Attribute
P264
6. Overriding Class Attributes
- Class attributes can be used as default values for instance attributes.
- Class attributes are public, they are inherited by subclasses, so it’s common practice to subclass just to customize a class data attribute.