用户工具

站点工具


python:第六章高级oop特性

第六章 高级OOP特性

本章概要

本章将深入探讨Python面向对象编程的高级特性,包括魔术方法、属性描述符、元类等,帮助你编写更Pythonic、更强大的代码。

6.1 魔术方法(Magic Methods)

魔术方法(也称为特殊方法或dunder方法)以双下划线开头和结尾,在特定情况下自动调用。

6.1.1 对象表示

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
    def __str__(self):
        """给用户的友好表示"""
        return f"Person(name='{self.name}', age={self.age})"
 
    def __repr__(self):
        """给开发者的详细表示,eval(repr(obj))应能重建对象"""
        return f"Person('{self.name}', {self.age})"
 
    def __bytes__(self):
        """返回bytes表示"""
        return str(self).encode('utf-8')
 
p = Person("Alice", 25)
print(str(p))    # Person(name='Alice', age=25)
print(repr(p))   # Person('Alice', 25)
print(bytes(p))  # b"Person(name='Alice', age=25)"

6.1.2 比较操作

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height
 
    def area(self):
        return self.width * self.height
 
    def __eq__(self, other):
        """等于 =="""
        if not isinstance(other, Rectangle):
            return NotImplemented
        return self.area() == other.area()
 
    def __lt__(self, other):
        """小于 <"""
        if not isinstance(other, Rectangle):
            return NotImplemented
        return self.area() < other.area()
 
    def __le__(self, other):
        """小于等于 <="""
        return self < other or self == other
 
    # 使用functools.total_ordering可以自动生成其他比较方法
    def __hash__(self):
        """支持作为字典键或集合元素"""
        return hash((self.width, self.height))
 
r1 = Rectangle(3, 4)   # area = 12
r2 = Rectangle(2, 6)   # area = 12
r3 = Rectangle(4, 4)   # area = 16
 
print(r1 == r2)   # True(面积相等)
print(r1 < r3)    # True

6.1.3 算术运算

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
 
    def __add__(self, other):
        """+ 运算符"""
        return Vector(self.x + other.x, self.y + other.y)
 
    def __sub__(self, other):
        """- 运算符"""
        return Vector(self.x - other.x, self.y - other.y)
 
    def __mul__(self, scalar):
        """* 运算符(向量数乘)"""
        return Vector(self.x * scalar, self.y * scalar)
 
    def __rmul__(self, scalar):
        """右乘(scalar * vector)"""
        return self * scalar
 
    def __truediv__(self, scalar):
        """/ 运算符"""
        return Vector(self.x / scalar, self.y / scalar)
 
    def __neg__(self):
        """一元负号 -vector"""
        return Vector(-self.x, -self.y)
 
    def __abs__(self):
        """abs()函数"""
        return (self.x ** 2 + self.y ** 2) ** 0.5
 
    def __str__(self):
        return f"Vector({self.x}, {self.y})"
 
    def __repr__(self):
        return f"Vector({self.x}, {self.y})"
 
v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1 + v2)     # Vector(4, 6)
print(v1 * 2)      # Vector(6, 8)
print(3 * v1)      # Vector(9, 12)
print(abs(v1))     # 5.0

6.1.4 容器类型方法

class Deck:
    def __init__(self):
        self._cards = []
 
    def __len__(self):
        """len()函数"""
        return len(self._cards)
 
    def __getitem__(self, index):
        """索引访问 deck[index]"""
        return self._cards[index]
 
    def __setitem__(self, index, value):
        """索引赋值 deck[index] = value"""
        self._cards[index] = value
 
    def __delitem__(self, index):
        """del deck[index]"""
        del self._cards[index]
 
    def __iter__(self):
        """迭代"""
        return iter(self._cards)
 
    def __contains__(self, item):
        """in 运算符"""
        return item in self._cards
 
    def add_card(self, card):
        self._cards.append(card)
 
# 使用
deck = Deck()
for card in ["A♠", "K♥", "Q♦"]:
    deck.add_card(card)
 
print(len(deck))       # 3
print(deck[0])         # A♠
print("K♥" in deck)    # True
for card in deck:
    print(card)

6.1.5 上下文管理器

class DatabaseConnection:
    def __init__(self, db_name):
        self.db_name = db_name
        self.connection = None
 
    def __enter__(self):
        """进入with语句时调用"""
        print(f"Connecting to {self.db_name}...")
        self.connection = f"Connection({self.db_name})"
        return self.connection
 
    def __exit__(self, exc_type, exc_val, exc_tb):
        """退出with语句时调用"""
        print(f"Closing connection to {self.db_name}...")
        self.connection = None
        # 返回True表示异常已处理,不再传播
        return False
 
# 使用
with DatabaseConnection("mydb") as conn:
    print(f"Using {conn}")
# 输出:
# Connecting to mydb...
# Using Connection(mydb)
# Closing connection to mydb...

6.1.6 可调用对象

class Counter:
    def __init__(self, start=0):
        self.value = start
 
    def __call__(self, step=1):
        """使实例可以像函数一样调用"""
        self.value += step
        return self.value
 
    def __int__(self):
        """int()转换"""
        return self.value
 
    def __index__(self):
        """用于索引"""
        return self.value
 
counter = Counter(10)
print(counter())      # 11
print(counter(5))     # 16
print(int(counter))   # 16

6.2 属性(Property)深入

6.2.1 属性装饰器详解

class Temperature:
    def __init__(self, celsius=0):
        self._celsius = celsius
 
    @property
    def celsius(self):
        """Getter"""
        print("Getting celsius...")
        return self._celsius
 
    @celsius.setter
    def celsius(self, value):
        """Setter"""
        print("Setting celsius...")
        if value < -273.15:
            raise ValueError("Temperature below absolute zero!")
        self._celsius = value
 
    @celsius.deleter
    def celsius(self):
        """Deleter"""
        print("Deleting celsius...")
        del self._celsius
 
    @property
    def fahrenheit(self):
        """只读属性"""
        return self._celsius * 9/5 + 32
 
# 使用
temp = Temperature(25)
print(temp.celsius)    # 调用getter
temp.celsius = 30      # 调用setter
print(temp.fahrenheit) # 只读属性

6.2.2 property() 函数方式

class Circle:
    def __init__(self, radius):
        self._radius = radius
 
    def get_radius(self):
        return self._radius
 
    def set_radius(self, value):
        if value < 0:
            raise ValueError("Radius cannot be negative")
        self._radius = value
 
    def del_radius(self):
        del self._radius
 
    # 使用property()函数
    radius = property(
        fget=get_radius,
        fset=set_radius,
        fdel=del_radius,
        doc="The radius property"
    )
 
    @property
    def area(self):
        import math
        return math.pi * self._radius ** 2

6.3 描述符(Descriptor)

描述符是实现了特定协议的类,用于管理属性访问。

6.3.1 描述符协议

class Validator:
    """基础验证描述符"""
    def __init__(self, min_value=None, max_value=None):
        self.min_value = min_value
        self.max_value = max_value
        self.name = None
 
    def __set_name__(self, owner, name):
        """Python 3.6+ 自动获取属性名"""
        self.name = name
        self.private_name = f"_{name}"
 
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return getattr(obj, self.private_name, None)
 
    def __set__(self, obj, value):
        if self.min_value is not None and value < self.min_value:
            raise ValueError(f"{self.name} must be >= {self.min_value}")
        if self.max_value is not None and value > self.max_value:
            raise ValueError(f"{self.name} must be <= {self.max_value}")
        setattr(obj, self.private_name, value)
 
    def __delete__(self, obj):
        raise AttributeError(f"Cannot delete {self.name}")
 
class Person:
    age = Validator(min_value=0, max_value=150)
    salary = Validator(min_value=0)
 
    def __init__(self, name, age, salary):
        self.name = name
        self.age = age
        self.salary = salary
 
# 使用
p = Person("Alice", 25, 5000)
print(p.age)    # 25
# p.age = 200   # ValueError: age must be <= 150

6.3.2 类型检查描述符

class Typed:
    """类型检查描述符"""
    def __init__(self, expected_type):
        self.expected_type = expected_type
        self.name = None
 
    def __set_name__(self, owner, name):
        self.name = name
        self.private_name = f"_{name}"
 
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return getattr(obj, self.private_name)
 
    def __set__(self, obj, value):
        if not isinstance(value, self.expected_type):
            raise TypeError(f"{self.name} must be {self.expected_type.__name__}, "
                          f"got {type(value).__name__}")
        setattr(obj, self.private_name, value)
 
class Stock:
    name = Typed(str)
    shares = Typed(int)
    price = Typed(float)
 
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price
 
    @property
    def cost(self):
        return self.shares * self.price
 
# 使用
s = Stock("GOOG", 100, 123.45)
print(s.cost)  # 12345.0
# s.shares = "100"  # TypeError

6.4 元类(Metaclass)

元类是创建类的“类”,类是元类的实例。

6.4.1 type 元类

# 使用type动态创建类
def init(self, name):
    self.name = name
 
def greet(self):
    return f"Hello, I'm {self.name}"
 
# 创建类
Person = type('Person', (), {
    '__init__': init,
    'greet': greet
})
 
p = Person("Alice")
print(p.greet())  # Hello, I'm Alice

6.4.2 自定义元类

class SingletonMeta(type):
    """单例元类"""
    _instances = {}
 
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]
 
class Database(metaclass=SingletonMeta):
    def __init__(self, db_name):
        self.db_name = db_name
 
    def query(self, sql):
        return f"Querying {self.db_name}: {sql}"
 
# 使用
db1 = Database("mydb")
db2 = Database("otherdb")  # 实际上还是mydb实例
print(db1 is db2)  # True

6.4.3 元类控制类创建

class AutoReprMeta(type):
    """自动为类生成__repr__方法的元类"""
 
    def __new__(mcs, name, bases, namespace, **kwargs):
        # 创建类之前修改namespace
        if '__repr__' not in namespace:
            namespace['__repr__'] = mcs._make_repr(name, namespace.get('__init__'))
 
        cls = super().__new__(mcs, name, bases, namespace)
        return cls
 
    @staticmethod
    def _make_repr(class_name, init_method):
        def __repr__(self):
            attrs = ', '.join(f"{k}={v!r}" for k, v in self.__dict__.items())
            return f"{class_name}({attrs})"
        return __repr__
 
class Point(metaclass=AutoReprMeta):
    def __init__(self, x, y):
        self.x = x
        self.y = y
 
p = Point(3, 4)
print(p)  # Point(x=3, y=4)

6.5 抽象基类(ABC)

6.5.1 定义抽象基类

from abc import ABC, abstractmethod
 
class Animal(ABC):
    @abstractmethod
    def speak(self):
        """子类必须实现"""
        pass
 
    @abstractmethod
    def move(self):
        pass
 
    @property
    @abstractmethod
    def species(self):
        """抽象属性"""
        pass
 
    def introduce(self):
        """具体方法"""
        return f"I am a {self.species}"
 
class Dog(Animal):
    @property
    def species(self):
        return "Canis familiaris"
 
    def speak(self):
        return "Woof!"
 
    def move(self):
        return "Running on 4 legs"
 
# animal = Animal()  # TypeError: Can't instantiate abstract class
dog = Dog()
print(dog.introduce())  # I am a Canis familiaris

6.5.2 注册虚拟子类

from abc import ABC, abstractmethod
 
class Shape(ABC):
    @abstractmethod
    def area(self):
        pass
 
class Rectangle:
    """普通类,没有继承Shape"""
    def __init__(self, width, height):
        self.width = width
        self.height = height
 
    def area(self):
        return self.width * self.height
 
# 注册为虚拟子类
Shape.register(Rectangle)
 
print(issubclass(Rectangle, Shape))  # True
print(isinstance(Rectangle(3, 4), Shape))  # True

6.6 混入(Mixin)

混入是一种设计模式,通过多重继承为类添加功能。

class JSONSerializableMixin:
    """提供JSON序列化功能的混入"""
    import json
 
    def to_json(self):
        return self.json.dumps(self.__dict__, indent=2)
 
    @classmethod
    def from_json(cls, json_str):
        data = cls.json.loads(json_str)
        return cls(**data)
 
class ComparableMixin:
    """提供比较功能的混入"""
    def __eq__(self, other):
        if not isinstance(other, self.__class__):
            return NotImplemented
        return self.__dict__ == other.__dict__
 
    def __ne__(self, other):
        return not self == other
 
class Person(JSONSerializableMixin, ComparableMixin):
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
# 使用
p1 = Person("Alice", 25)
p2 = Person("Alice", 25)
print(p1.to_json())
print(p1 == p2)  # True

6.7 代码示例

示例1:实现一个完整的数值类型

from functools import total_ordering
 
@total_ordering
class Rational:
    """有理数类"""
 
    def __init__(self, numerator, denominator=1):
        if denominator == 0:
            raise ValueError("Denominator cannot be zero")
 
        # 约分
        g = self._gcd(abs(numerator), abs(denominator))
        self.numerator = numerator // g
        self.denominator = denominator // g
 
        # 确保分母为正
        if self.denominator < 0:
            self.numerator = -self.numerator
            self.denominator = -self.denominator
 
    @staticmethod
    def _gcd(a, b):
        while b:
            a, b = b, a % b
        return a
 
    def __str__(self):
        if self.denominator == 1:
            return str(self.numerator)
        return f"{self.numerator}/{self.denominator}"
 
    def __repr__(self):
        return f"Rational({self.numerator}, {self.denominator})"
 
    def __eq__(self, other):
        if isinstance(other, Rational):
            return (self.numerator == other.numerator and 
                    self.denominator == other.denominator)
        return NotImplemented
 
    def __lt__(self, other):
        if isinstance(other, Rational):
            return (self.numerator * other.denominator < 
                    other.numerator * self.denominator)
        return NotImplemented
 
    def __add__(self, other):
        if isinstance(other, Rational):
            num = (self.numerator * other.denominator + 
                   other.numerator * self.denominator)
            den = self.denominator * other.denominator
            return Rational(num, den)
        return NotImplemented
 
    def __sub__(self, other):
        return self + Rational(-other.numerator, other.denominator)
 
    def __mul__(self, other):
        if isinstance(other, Rational):
            return Rational(self.numerator * other.numerator,
                          self.denominator * other.denominator)
        return NotImplemented
 
    def __truediv__(self, other):
        return self * Rational(other.denominator, other.numerator)
 
    def __float__(self):
        return self.numerator / self.denominator
 
    def __int__(self):
        return self.numerator // self.denominator
 
# 使用
r1 = Rational(1, 2)
r2 = Rational(1, 3)
print(r1 + r2)   # 5/6
print(r1 * r2)   # 1/6
print(r1 > r2)   # True
print(float(r1)) # 0.5

示例2:使用描述符实现ORM字段

class Field:
    """数据库字段描述符"""
    def __init__(self, name=None, dtype=str, primary_key=False):
        self.name = name
        self.dtype = dtype
        self.primary_key = primary_key
 
    def __set_name__(self, owner, name):
        if self.name is None:
            self.name = name
        self.private_name = f"_{name}"
 
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return getattr(obj, self.private_name, None)
 
    def __set__(self, obj, value):
        if not isinstance(value, self.dtype):
            try:
                value = self.dtype(value)
            except (ValueError, TypeError):
                raise TypeError(f"Expected {self.dtype.__name__}, got {type(value).__name__}")
        setattr(obj, self.private_name, value)
 
class ModelMeta(type):
    """模型元类"""
    def __new__(mcs, name, bases, namespace):
        fields = {}
        for key, value in namespace.items():
            if isinstance(value, Field):
                fields[key] = value
        namespace['_fields'] = fields
        return super().__new__(mcs, name, bases, namespace)
 
class Model(metaclass=ModelMeta):
    """ORM基础模型"""
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)
 
    def __repr__(self):
        attrs = ', '.join(f"{k}={getattr(self, k)!r}" for k in self._fields)
        return f"{self.__class__.__name__}({attrs})"
 
    def to_dict(self):
        return {k: getattr(self, k) for k in self._fields}
 
class User(Model):
    id = Field(dtype=int, primary_key=True)
    name = Field(dtype=str)
    age = Field(dtype=int)
    email = Field(dtype=str)
 
# 使用
user = User(id=1, name="Alice", age=25, email="alice@example.com")
print(user)
print(user.to_dict())

6.8 练习题

练习1:实现一个可切片、可迭代的序列类

class MySequence:
    def __init__(self, data):
        self._data = list(data)
 
    def __len__(self):
        return len(self._data)
 
    def __getitem__(self, index):
        if isinstance(index, slice):
            return MySequence(self._data[index])
        return self._data[index]
 
    def __setitem__(self, index, value):
        self._data[index] = value
 
    def __iter__(self):
        return iter(self._data)
 
    def __contains__(self, item):
        return item in self._data
 
    def __repr__(self):
        return f"MySequence({self._data})"
 
# 测试
seq = MySequence([1, 2, 3, 4, 5])
print(len(seq))        # 5
print(seq[1:4])        # MySequence([2, 3, 4])
print(3 in seq)        # True

练习2:实现属性缓存装饰器

class cached_property:
    """缓存属性装饰器,只计算一次"""
    def __init__(self, func):
        self.func = func
        self.name = func.__name__
        self.__doc__ = func.__doc__
 
    def __set_name__(self, owner, name):
        self.name = name
 
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if self.name not in obj.__dict__:
            obj.__dict__[self.name] = self.func(obj)
        return obj.__dict__[self.name]
 
class Circle:
    def __init__(self, radius):
        self.radius = radius
 
    @cached_property
    def area(self):
        print("Calculating area...")
        import math
        return math.pi * self.radius ** 2
 
# 测试
c = Circle(5)
print(c.area)  # 计算并缓存
print(c.area)  # 直接使用缓存

本章小结

本章深入学习了Python的高级OOP特性:

  • 魔术方法 - 对象表示、比较、算术运算、容器操作、上下文管理
  • 属性 - @property装饰器、getter/setter/deleter
  • 描述符 - getsetdelete协议
  • 元类 - type、自定义元类、控制类创建
  • 抽象基类 - ABC、@abstractmethod、虚拟子类
  • 混入 - 通过多重继承复用代码

这些高级特性让你能够编写更灵活、更强大的面向对象代码。下一章将学习迭代器和生成器。

进一步阅读

python/第六章高级oop特性.txt · 最后更改: 127.0.0.1