Loading

9. A Pythonic Object

  • 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'd\x00\x00\x00\x00\x00\x00\x08@\x00\x00\x00\x00\x00\x00\x10@'
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.

 

posted @ 2019-05-29 19:34  LB477  阅读(182)  评论(0编辑  收藏  举报