牛骨文教育服务平台(让学习变的简单)
博文笔记

Python类与对象技巧(1):字符串格式化、封装属性名、可管理的属性、调用父类方法

创建时间:2018-11-05 投稿人: 浏览次数:952
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/shenziheng1/article/details/83692676
_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》

声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。