Python—面向对象编程(4)

127 阅读11分钟

类和实例

面向对象编程(OOPObject Oriented Programming,对象包含了属性行为

  • Class 类:用于定义同一类的对象的属性行为
  • Instance 实例:通过Class创建出来的一个个具体的对象
# 定义一个继承于`object`的类
class User(object):
    pass
# 通过类创建一个实例    
user = User()

类中定义属性和方法

class User(object):
    # `__init__` 直接在类中定义属性,创建实例的时候就会自动绑定到对象上
    # self 就是实例本身,将 name、age 绑定到 self
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # 定义方法,第一个参数必须是 self
    def sign(self):
        print("sign:",self.name, self.age)

user = User("dawn",18)
# 调用时无需传入 self
print(user.sign())

实例绑定属性

  • 定义类时通过 __init__ 给实例绑定属性
  • 初始化实例后,给实例绑定属性
# 定义类时通过 `__init__` 给实例绑定属性
class User(object):
    def __init__(self, name):
        self.name = name
# 初始化实例后,给实例绑定属性
user = User()
user.name = "dawn"
print(user.name)

绑定属性

如果类本身需要绑定一个属性(属性属于类),类属性可以被所有的实例访问,也可以被类本身访问

# 给类绑定属性
class User(object):
    name = "dawning"

user = User()
print(user.name) # dawning
print(User.name) # dawning
user.name = "zhangsan"
print(user.name) # zhangsan

实例绑定一个方法

# 给实例绑定一个方法,给实例绑定的方法不能应用于其他的实例
user.set_age = MethodType(set_age, user)
print(user.set_age(30))
print(user.age)  # 30

绑定一个方法

# 给类绑定一个方法,给类绑定的方法所有实例都可用
def set_hobby(self, hobby):
    self.hobby = hobby
User.set_hobby = set_hobby

user0 = User()
user1 = User()
user0.set_hobby("reading")
user1.set_hobby("writing")
print(user0.hobby)
print(user1.hobby)

__slots__限制 class 实例所能添加的属性

__slots__ 限制实例所能添加的属性

class User(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

user = User()
user.name = "dawn"
user.age = 44
user.phone = '18888888888' # AttributeError

__slots__定义的属性仅对当前类实例起作用,对继承的子类无用;

子类中定义__slots__后,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

class Worker(User):
    pass

worker = Worker()
worker.name = "dawn"
worker.age = 44
worker.phone = '18888888888'

@property 将方法变成属性

Python内置的@property装饰器就是负责把一个方法变成一个属性来调用

# birth 和 age 都是 只读 属性
class Worker(object):
    @property
    def birth(self):
        return self._birth

    @birth.setter
    def birth(self, value):
        self._birth = value

    @property
    def age(self):
        return 2025 - self._birth

worker = Worker()
worker.birth = 1998
print(worker.age)

获取对象信息

type() 判断对象类型

person = Person("John")
print(type(person))

# 比较两个变量的type类型是否相同
print(type(123) == type(456)) # True
print(type(123) == int) # True
print(type('abc')==str) # True
print(type('abc')==type(123)) # False

types 判断函数类型

判断一个对象是否为函数,可导入 types 模块,使用其定义的常量

import types
def fn():
    pass

print(type(fn)==types.FunctionType) # True
print(type(abs)==types.BuiltinFunctionType) #True
print(type(lambda x: x)==types.LambdaType) #True
print(type((x for x in range(10)))==types.GeneratorType) #True

isinstance 判断实例类型

判断对象类型

stu = Student("John", "c001")
print(isinstance(stu, Person))  # True
print(isinstance(stu, Student))  # True
print(isinstance(stu, Teacher))  # False

判断一个变量是否为某个类型中的一种

print(isinstance([1, 2, 3], (list, tuple)))
print(isinstance((1, 2, 3), (list, tuple)))

dir() 获取一个对象的所有属性和方法

print(dir('123'))

运行结果:
['__add__', '__class__', ... ...  'zfill']

getattr()setattr()以及hasattr(),直接操作对象属性

person = Person("John")
# getattr 获取属性值
print(getattr(person, "userName")) # John

# hasattr 是否具有指定的属性
print(hasattr(person, "password")) # False

# setattr 设置某个属性
setattr(person, "userName", "John1")
print(person.userName) # John1

访问限制

类属性名以__开头,就变成了一个私有属性(private

class User(object):
    def __init__(self, __user_no, name, age):
        # 变量名以`__`开头,就变成了一个私有变量(`private`)
        self.__user_no = __user_no
        self.name = name
        self.age = age


user = User("u001", "dawn", 18)
# 私有变量外部调用报错 AttributeError: 'User' object has no attribute
print(user.__user_no)

增加get方法访问私有属性

class User(object):
    ... ...    
    def get_user_no(self):
        return self.__user_no


user = User("u001", "dawn", 18)
# 调用get方法访问私有属性
print(user.get_user_no())
  • 类属性定义为 __xxx__ 是特殊变量,特殊变量是可以直接访问的,不是 private 变量,所以,不能用__name____age__这样的变量名。

  • 一个下划线开头的属性,_xxx,这样的实例变量外部是可以访问的,但是尽量不可随意访问。

class User(object):
    def __init__(self, __id__, __user_no, _phone, name, age):
        # 属性命名格式为 __xx__ 不建议,这种命名为关键字
        self.__id__ = __id__
        # 外部是可以访问的,但是尽量不可随意访问
        self._phone = _phone
        self.name = name
        self.age = age

    def get_id(self):
        return self.__id__


user = User(1, "u001", "17777777777", "dawn", 18)
print(user.get_id())
print(user._phone)

实际上,我们也可以访问 __xxx 这样的变量因为python解释器会将 __xxx 这样的变量,改成 _类名__xxx

属性中定义的 __xxx 属性就是 __xxx 解释器不会修改

class User(object):
    def __init__(self, __user_no):
        self.__user_no = __user_no

user = User("u001")
user.__user_no = "c001"
# 外部通过_类名+属性名访问
print(user._User__user_no)

# 并非和 class 中定义的 __user_no 是同一个变量
# class 中定义的 __user_no 已经被修改成了 _User__user_no
print(user.__user_no)

继承和多态

extends

StudentTeacher 继承于 People

class Person:
    def __init__(self, userName):
        self.userName = userName

    def working(self):
        print(self.userName, " working")


class Student(Person):
    def __init__(self, userName, classNo):
        super().__init__(userName)
        self.classNo = classNo


class Teacher(Person):
    def __init__(self, userName, teacherNo):
        super().__init__(userName)
        self.teacherNo = teacherNo


student = Student("John", 1)
student.working()

teacher = Teacher("HuaPeo", 2)
teacher.working()

# isinstance 可以判断是否属于某个类型
print(isinstance(teacher, Teacher))

file-like object 鸭子类型

python 并不要求严格的继承体系,file-like object

class Worker(object):
    def __init__(self, userName):
        self.userName = userName
    def working(self):
        print(self.userName, " working")

# 传入的Worker对象并没有继承Person类,但具有running方法
worker0 = Person(Worker("Hew"))
worker0.running()

多重继承

一个子类就可以同时获得多个父类的所有功能,选择组合不同的类的功能来快速构造出一个新的子类

class UDPMixIn(object):
    pass
class ForkingMixIn(object):
    pass
class Server(UDPMixIn, ForkingMixIn):
    pass

定制类

Python中在运行期间可以创建属性、对象、函数等,这样就会很方便的定制特定的类。

如同__xxx__的变量或者函数名比如,__len__()__slots__,在Python中是有特定用途,可以帮我们定特定的类。

__str__ 定制打印对象

# 默认的对象打印
class User:
    def __init__(self, name):
        self.name = name
user = User("John")
print(user) # <__main__.User object at 0x105cc8ef0>
# `__str__ ` 定制对象打印
class User:
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return 'User[name=%s]' % self.name
user = User("John")
print(user) # User[name=John]

__iter__ 获取迭代对象

类似list或tuple,想让一个类对象使用for-in循环,就必须实现一个__iter__()并返回一个迭代对象,然后,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

class EvenNumberAcquirer(object):
    def __init__(self, maxNumber):
        self.a = 0
        self.maxNumber = maxNumber

    def __iter__(self):
        return self  # 实例本身就是迭代对象,直接返回自己

    def __next__(self):
        if self.a > self.maxNumber:
            raise StopIteration()
        if self.a % 2 == 0:
            return_val = self.a
            self.a += 1
            return return_val
        else:
            return_val = self.a + 1
            self.a += 2
            return return_val

for n in EvenNumberAcquirer(100):
    print(n)

__getitem__ 获取元素

根据下标获取元素

class EvenNumberAcquirer(object):
    ... ... 
    # __getitem__ 根据下标获取元素
    def __getitem__(self, item):
        return item * 2
even_number = EvenNumberAcquirer(100)
print(even_number[0]) # 0
print(even_number[7]) # 14

实现切片[x:y]操作

class EvenNumberAcquirer(object):
    ... ...
    # isinstance 判断item的类型,返回不同的元素
    def __getitem__(self, item):
        if isinstance(item, int):
            return item * 2
        if isinstance(item, slice):
            start = item.start
            if item.start is None:
                start = 0
            L = []
            for x in range(start,item.stop):
                L.append(x * 2)
            return L
even_number = EvenNumberAcquirer(100)
print(even_number[1:7])

如果要实现[1:7:2][-10:] 等这样的切片操作,都需要自己去实现。

如果把对象看成dict

  • __getitem__()获取某个key对应的元素
  • __setitem__()设置某个key对应的元素
  • __delitem__()删除某个元素。
class MyDict:
    def __init__(self):
        self.data = {}

    # __getitem__:用于获取元素
    def __getitem__(self, key):
        print(f"Getting the value for key: {key}")
        return self.data[key]

    # __setitem__:用于设置元素
    def __setitem__(self, key, value):
        print(f"Setting key {key} to value {value}")
        self.data[key] = value

    # __delitem__:用于删除元素
    def __delitem__(self, key):
        print(f"Deleting the key: {key}")
        del self.data[key]

my_dict = MyDict()

# 使用 __setitem__ 来设置键值对
my_dict["name"] = "Alice"
my_dict["age"] = 25

# 使用 __getitem__ 来获取值
print(my_dict["name"])  # 输出: Alice

# 使用 __delitem__ 来删除键值对
del my_dict["age"]

__getattr__ 动态获取元素属性

访问一个不存在的属性会报错

class User:
    pass

user = User()
print(user.name) # AttributeError

__getattr__ 动态返回对应值

class User(object):
    def __getattr__(self, attr):
        if attr=='name':
            return '王林'

user = User()
print(user.name)

__getattr__ 也可以动态返回一个方法

class User(object):
    def __getattr__(self, attr):
        if attr=='get_name':
            return lambda :"王林"
user = User()
print(user.get_name())

实现一个 URL 的动态拼接

class Url(object):
    def __init__(self, path = ""):
        self.path = path

    def __getattr__(self, path):
        return Url('%s/%s' % (self.path, path))

    # 支持调用括号的方式
    def __call__(self, *args):
        # 将数字(或其他参数)作为路径的一部分
        if args:
            return Url(f"{self.path}/{args[0]}")
        return self

    def __str__(self):
        return self.path

    __repr__ = __str__

print(Url().app.api(1).user.page) # /app/api/1/user/page
print(Url().app.api.user.page) # /app/api/user/page

__call__ 直接调用实例

调用实例方法时可通过 instance.method()来调用,而调用实例时可通过 instance() 来调用,但是调用实例需要定义 __call__方法

class User(object):
    def __call__(self, *args, **kwargs):
        print(f'call args:{args} kwargs:{kwargs}')

user = User()
user(1,2,3,name="婉儿",age=2000) # call args:(1, 2, 3) kwargs:{'name': '婉儿', 'age': 2000}

使用枚举类

from enum import Enum, unique

TimeUnit = Enum('Time', ('YEAR', 'MONTH', 'DAY', 'HOUR', 'MINUTE', 'SECOND'))
for name, member in TimeUnit.__members__.items():
    print(name, '=>', member, '=>', member.value)

从Enum 派生出自定义的类,可精确的控制枚举类型

@unique  # @unique装饰器可以帮助我们检查保证没有重复的值
class TimeUnit(Enum):
    YEAR = 1
    MONTH = 2
    DAY = 3
    HOUR = 4
    MINUTE = 5
    SECOND = 6

访问枚举对象

year = TimeUnit.YEAR
print(year) # TimeUnit.YEAR
print(year == TimeUnit.SECOND) # False
print(year.value) # 1
print(TimeUnit(2)) # TimeUnit.MONTH

使用元类

type() 动态创建类

class 的类型为 type,instance 的类型为class type

type() 既可以返回一个对象类型又可以创建一个新的类

# type() 返回一个对象类型
class User:
    pass

user = User()
print(type(User)) # <class 'type'>
print(type(user)) # <class '__main__.User'>

创建类的方式:

  • class:定义类(常用)
  • type()可动态创建出类(动态语言本身支持运行期动态创建类)
class Working:
    pass

class Running:
    pass

def reading(self, name):
    print(f'The reading book is {name}')

# type() 创建一个 User 类
"""
第一个参数:类名
第二个参数:继承的父类
第三个参数:函数绑定
"""
User = type('User', (Working, Running), dict(reading=reading))
print(type(User))

user = User()
user.reading('<仙逆>') # The reading book is <仙逆>

metaclass 创建或修改类

metaclass是Python的魔术对象,它可以改变类创建时的行为。

创建实例的方式

  • 定义类=>创建实例
  • 定义元类=>定义类=>创建实例
# ListMetaclass(命名一般XxxMetaclass)
class ListMetaclass(type):

    """
    第一个参数:要创建的类对象(MyListMetaclass 类对象)
    第二个参数:要创建的类名 (MyListMetaclass)
    第三个参数:类继承的父类集合 (list)
    第四个参数:一个字典,包含即将创建的类的所有属性和方法
    """
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, val: self.append(val)
        return type.__new__(cls, name, bases, attrs)


class MyListMetaclass(list, metaclass=ListMetaclass):
   pass

list0 = MyListMetaclass()
list0.add('王林')
list0.add('婉儿')
print(list0) # ['王林', '婉儿']

元类示例

利用元类,打印sql

# 定义列(列名,列类型)
class Column(object):
    def __init__(self, cname, ctype):
        self.cname = cname
        self.ctype = ctype

    def __str__(self):
        return '<%s:%s:%s>' % (self.__class__.__name__, self.cname, self.ctype)


# 定义String类型的列
class StringColumn(Column):
    def __init__(self, name):
        super(StringColumn, self).__init__(name, 'varchar(100)')


# 定义Integer类型的列
class IntegerColumn(Column):
    def __init__(self, name):
        super(IntegerColumn, self).__init__(name, 'bigint')

# 定义 Metaclass 从指定类型中获取所有属性并将其添加至 mappings 中
class ModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
        # 如果创建的类为 Model 类直接返回
        if name == 'Model':
            return type.__new__(cls, name, bases, attrs)
        print('Found model: %s' % name)
        mappings = dict()
        # 获取所有的属性,并添加到 mappings 中
        for k, v in attrs.items():
            if isinstance(v, Column):
                print('Found mapping: %s ==> %s' % (k, v))
                mappings[k] = v
        for k in mappings.keys():
            attrs.pop(k)
        attrs['__mappings__'] = mappings  # 保存属性和列的映射关系
        attrs['__table__'] = name  # 假设表名和类名一致
        return type.__new__(cls, name, bases, attrs)


class Model(dict, metaclass=ModelMetaclass):
    def __init__(self, **kw):
        super(Model, self).__init__(**kw)
    # self 本身是一个dict 动态获取属性实例
    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)
    # 动态给当前实例设置属性
    def __setattr__(self, key, value):
        self[key] = value

    def print_sql(self):
        columns = []
        args = []
        # ModelMetaclass 在创建类对象时,会获取当前对象的所有属性并设置到 mappings 中
        for k, v in self.__mappings__.items():
            columns.append(v.cname)
            args.append(getattr(self, k, None))
        sql = "insert into %s (%s) values (%s)" % (self.__table__, ','.join(columns), ','.join(['?'] * len(columns))),
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))

class User(Model):
    id = IntegerColumn('id')
    name = StringColumn('name')
    birthday = StringColumn('birthday')
    phone = StringColumn('phone')

u = User(id=111, name='王林', birthday='2025-02-15', phone='7777777')
u.print_sql()

# 执行结果
Found model: User
Found mapping: id ==> <IntegerColumn:id:bigint>
Found mapping: name ==> <StringColumn:name:varchar(100)>
Found mapping: birthday ==> <StringColumn:birthday:varchar(100)>
Found mapping: phone ==> <StringColumn:phone:varchar(100)>
SQL: insert into User (id,name,birthday,phone) values (?,?,?,?)
ARGS: [111, '王林', '2025-02-15', '7777777']