Python类与对象技巧(1):字符串格式化、封装属性名、可管理的属性、调用父类方法
_formats = {
"ymd" : "{d.year}-{d.month}-{d.day}",
"mdy" : "{d.month}/{d.day}/{d.year}",
"dmy" : "{d.day}/{d.month}/{d.year}"
}
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def __format__(self, code):
if code == "":
code = "ymd"
fmt = _formats[code]
return fmt.format(d=self)
d = Date(2018, 11, 4)
print(d.year, d.month, d.day) # >>> 2018 11 4
print(format(d))
# >>> 2018-11-4
print("This day is {:mdy}".format(d))
# >>> This day is 11/4/2018
__format__()方法给Python的字符串格式化功能提供了一个钩子。 这里需要强调的是格式化代码的解析工作由类决定。
如果想封装类的实例上面的“私有”数据,但是Python语言并没有访问控制。Python不依赖语言特性去封装数据,而是通过遵循一定的属性和方法命名约定来达到这个效果。
- 任何以单下划线_开头的名字都应该是内部实现
class A:
def __init__(self):
self._internal = 0 # 内部属性
self.public = 1 # 共有属性
def public_method(self):
pass
def _internal_method(self):
pass
Python并不会真的阻止访问内部名称。但是这么做肯定是不好的,可能会导致脆弱的代码(所以说对于初级程序员而言,python的严密性与可靠性都是远不如C++的)。 同时注意到,使用下划线开头的约定同样适用于模块名和模块级别函数。 例如,以单下划线开头(比如_socket)的模块,就是内部实现。 类似的,模块级别函数比如 sys._getframe() 在使用的时候就得加倍小心了(因为内部方法的调用,原则上外部不应该随意使用)。
- 任何以双下划线__开头的名字会导致访问名称变成其他形式
class B:
def __init__(self):
self.__private = 0 # 访问名称会发生变化
def __private_method(self):
pass
def public_method(self):
pass
self.__private_method()
使用双下划线开始会导致访问名称变成其他形式。 比如,在类B中,私有属性会被分别重命名为 _B__private 和 _B__private_method 。 这样重命名的目的就是继承——这种属性通过继承是无法被覆盖的。例如:
class C(B):
def __init__(self):
super().__init__()
self.__private = 1 # 没有覆盖B.__private
# 没有覆盖B.__private__method()
def __private_method(self):
pass
这里,私有名称 __private 和 __private_method 被重命名为 _C__private 和 _C__private_method ,这个跟父类B中的名称是完全不同的。
Note:提到单下划线和双下划线来命名私有属性,到底哪种方式好呢? 大多数而言,应该让非公共名称以单下划线开头。但是,如果代码涉及到子类, 并且有些内部属性应该在子类中隐藏起来,那么才考虑使用双下划线方案。
如果想给某个实例attribute增加除访问与修改之外的其他处理逻辑,比如类型检查或合法性验证。自定义某个属性的一种简单方法是将它定义为一个property。property的一个关键特征是它看上去跟普通的attribute没什么两样, 但是访问它的时候会自动触发 getter
、setter
和 deleter
方法。下面增加对一个属性简单的类型检查:
class Person:
def __init__(self, first_name):
self.first_name = first_name
# Getter function : 使得first_name成为一个属性
@property
def first_name(self):
return self._first_name
# setter function : 关联属性的装饰器
@first_name.setter
def first_name(self, value):
if not isinstance(value, str):
raise TypeError("Expected a string")
self._first_name = value
# deleter function (optional) : 关联属性的装饰器
@first_name.deleter
def first_name(self):
raise AttributeError("Can"t delete attribute")
p = Person("ziheng")
print(p.first_name) # calls the getter
# >>> ziheng
p.first_name = "xiaohe" # calls the setter 必须是字符串才会收集
print(p.first_name)
# >>> xiaohe
del p.first_name
# >>> AttributeError: Can"t delete attribute
在实现一个property的时候,底层数据需要存储在某个地方。 因此,在get和set方法中,会看到对 _first_name 属性的操作,这也是实际数据保存的地方。
为了调用父类(超类)的一个已经覆盖的方法,使用 super() 函数。super() 函数的一个常见用法是在 __init__() 方法中确保父类被正确的初始化。
class A:
def __init__(self, x):
self.x = x
class B(A):
def __init__(self, x, y):
super().__init__(x)
self.y = y
obj = B(2017, 2018)
print(obj.x) # 2017
print(obj.y) # 2018
实际上,如何正确使用 super() 函数还需要知道更多,比如说在复杂的多继承中直接调用父类和使用super()存在很大差别。
class Base:
def __init__(self):
print("Base.__init__")
class A(Base):
def __init__(self):
Base.__init__(self)
print("A._init__")
class B(Base):
def __init__(self):
Base.__init__(self)
print("B.__init__")
class C(A,B):
def __init__(self):
A.__init__(self)
B.__init__(self)
print("C.__init__")
obj = C()
执行结果:
Base.__init__
A._init__
Base.__init__
B.__init__
C.__init__
Comment:Base.__init__调用了两次!!!
class Base:
def __init__(self):
print("Base.__init__")
class A(Base):
def __init__(self):
super().__init__()
print("A._init__")
class B(Base):
def __init__(self):
super().__init__()
print("B.__init__")
class C(A,B):
def __init__(self):
super().__init__()
print("C.__init__")
obj = C()
执行结果:
Base.__init__
B.__init__
A._init__
C.__init__
为了弄清原理,需要解释Python是如何实现继承的。 对于定义的每一个类,Python会计算出一个方法解析顺序(MRO)列表。 这个MRO列表就是一个简单的所有基类的线性顺序表。例如:
>>> C.__mro__
(<class "__main__.C">, <class "__main__.A">, <class "__main__.B">, <class "__main__.Base">, <class "object">)
当使用 super() 函数时,Python会在MRO列表上搜索下一个类。 只要每个重定义的方法统一使用 super() 并只调用它一次, 那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次。
文章参考《python3-cookbook》
- 上一篇:Python示例代码之邮件发送
- 下一篇:投放电子版微积分教材,国人领先世界