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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/shenziheng1/article/details/83692676

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的字符串格式化功能提供了一个钩子。 这里需要强调的是格式化代码的解析工作由类决定。

2. 在类中封装属性名

如果想封装类的实例上面的“私有”数据,但是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:提到单下划线和双下划线来命名私有属性,到底哪种方式好呢? 大多数而言,应该让非公共名称以单下划线开头。但是,如果代码涉及到子类, 并且有些内部属性应该在子类中隐藏起来,那么才考虑使用双下划线方案。

3. 创建可以管理的属性

如果想给某个实例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 属性的操作,这也是实际数据保存的地方。

4. 调用父类方法

为了调用父类(超类)的一个已经覆盖的方法,使用 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》

猜你喜欢

转载自blog.csdn.net/shenziheng1/article/details/83692676