• Python 工匠 第九章 面向对象


    基础知识

    类常用知识

    1 私有属性是“君子协定”

    >>> class Foo:
    ...     def __init__(self):
    ...             self.__bar = 'abc'
    ... 
    >>> foo = Foo()
    >>> foo.__bar
    Traceback (most recent call last):
      File "", line 1, in <module>
    AttributeError: 'Foo' object has no attribute '__bar'
    >>> foo._Foo__bar
    'abc'
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    可以看到,当我们使用__{var} 定义私有属性时,python解释器只是重命名为包含类名的别名_{class}__{var}

    私有属性主要用途:在父类中定义一个不容易被子类重写的受保护属性
    日常使用单下划线前缀即可表示私有属性

    2 实例内容都在字典里

    接上例

    >>> foo.__dict__
    {'_Foo__bar': 'abc'}
    >>> Foo.__dict__
    mappingproxy({'__module__': '__main__', '__init__': <function Foo.__init__ at 0x7f5699375670>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None})
    
    • 1
    • 2
    • 3
    • 4

    虽然setattr 可以和 __dict__.update 效果相同,但是明显后者更简单

    >>> d = {"a":"A", "b":"B"}
    >>> foo.__dict__
    {'_Foo__bar': 'abc'}
    >>> foo.__dict__.update(d)
    >>> foo.__dict__
    {'_Foo__bar': 'abc', 'a': 'A', 'b': 'B'}
    >>> setattr(foo, "c", "C")
    >>> foo.__dict__
    {'_Foo__bar': 'abc', 'a': 'A', 'b': 'B', 'c': 'C'}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    而且两者也有不同之处

    >>> class Person:
    ...     def __init__(self, name, age):
    ...             self.name = name
    ...             self.age = age
    ...     def __setattr__(self, name, value):
    ...             if name == "age" and value < 0:
    ...                     raise ValueError("age < 0")
    ...             super().__setattr__(name, value)
    ... 
    >>> p = Person("Tom", 1)
    >>> p.age = -1
    Traceback (most recent call last):
      File "", line 1, in <module>
      File "", line 7, in __setattr__
    ValueError: age < 0
    >>> p.__dict__["age"] = -1
    >>> p.age
    -1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    可以看到,__dict__ 可以绕过 __setattr__ 实现一些tricky的操作

    内置类方法装饰器

    类方法 @classmethod

    >>> class Duck:
    ...     def __init__(self, color):
    ...             self.color = color
    ...     def quack(self):
    ...             print(f"Hi, I'm a {self.color} duck!")
    ... 
    >>> Duck.quack()
    Traceback (most recent call last):
      File "", line 1, in <module>
    TypeError: quack() missing 1 required positional argument: 'self'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    可以看到,一般情况下类里面的方法,必须实例化之后才能调用

    @classmethod 就可以解决

    >>> class Duck:
    ...     def __init__(self, color):
    ...             self.color = color
    ...     def quack(self):
    ...             print(f"Hi, I'm a {self.color} duck!")
    ...     @classmethod
    ...     def create_random_duck(cls):
    ...             import random
    ...             color = random.choice(["yellow", "red", "white"])
    ...             return cls(color=color)
    ... 
    >>> d = Duck.create_random_duck()
    >>> d.quack()
    Hi, I'm a red duck!
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    静态方法 @staticmethod

    如果发现某个方法不需要使用当前实例里任何内容,则可以使用@staticmethod

    >>> class Cat:
    ...     def __init__(self, name):
    ...             self.name = name
    ...     def say(self):
    ...             sound = self.get_sound()
    ...             print(f"{self.name}: {sound}...")
    ...     @staticmethod
    ...     def get_sound():
    ...             return "Meow Meow"
    ... 
    >>> c = Cat("Tom")
    >>> c.say()
    Tom: Meow Meow...
    
    # 直接用类调用也可以
    >>> Cat.get_sound()
    'Meow Meow'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    可以发现静态方法可以拿到外面当做普通函数:

    1. 如果静态方法通用,与类关系不大,应该改写普通函数
    2. 反之,则静态方法
    3. 静态方法优势:可以被子类继承和重写

    属性装饰器 @property

    在类里,属性和方法职责不同:属性代表状态(instance.attr访问), 方法代表行为(instance.method()调用)。

    @property 模糊了两者界限,使用它,可以把方法通过属性的方式暴露出来

    >>> import os
    >>> class FilePath:
    ...     def __init__(self, path):
    ...             self.path = path
    ...     def get_basename(self):
    ...             return self.path.split(os.sep)[-1]
    ... 
    # 转化成属性
    >>> class FilePath:
    ...     def __init__(self, path):
    ...             self.path = path
    ...     @property
    ...     def basename(self):
    ...             return self.path.split(os.sep)[-1]
    ... 
    >>> p = FilePath("/tmp/foo.py")
    # 好比有个self.basename = self.path.split(os.sep)[-1]
    >>> p.basename
    'foo.py'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    除了定义属性的读取,@property还可以定义写入和删除逻辑

    >>> class FilePath:
    ...     def __init__(self, path):
    ...             self.path = path
    ...     @property
    ...     def basename(self):
    ...             return self.path.rsplit(os.sep, 1)[-1]
    ...     @basename.setter
    ...     def basename(self, name):
    ...             new_path = self.path.rsplit(os.sep, 1)[:-1] + [name]
    ...             self.path = os.sep.join(new_path)
    ...     @basename.deleter
    ...     def basename(self):
    ...             raise RuntimeError("cant delete basename")
    ... 
    >>> p = FilePath("/tmp/foo.py")
    >>> p.basename
    'foo.py'
    >>> p.basename = 'bar.py'
    >>> p.path
    '/tmp/bar.py'
    >>> del p.basename
    Traceback (most recent call last):
      File "", line 1, in <module>
      File "", line 13, in basename
    RuntimeError: cant delete basename
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    鸭子类型及其局限性

    假设想操作某个对象,不需要判断它是否属于某种类型,直接判断它是不是有你需要的某个属性或者方法。或者更激进点,直接尝试调用所需的属性或方法(失败了,就让它报错好了)。这就是鸭子类型

    抽象类

  • 相关阅读:
    【Python】文件操作
    线程的生命周期
    VsCode开发Vue(开源框架使用ruoyi)
    文心一言 VS 讯飞星火 VS chatgpt (137)-- 算法导论11.3 3题
    Go语言中结构体struct与字节数组[]byte的相互转换
    MySQL的结构化语言 DDL DML DQL DCL
    智芯传感MEMS压力传感器促进无人机跨入极其广阔的应用市场
    fastadmin的入门
    网络安全笔记-文件包含
    Nacos客户端启动出现9848端口错误分析(非版本升级问题)
  • 原文地址:https://blog.csdn.net/weixin_44596902/article/details/128027748