网站可以做哪些广告,最新型建筑模板有哪些,制作主页的软件,长沙公司网站1. 写在前面
最近在重构之前的后端代码#xff0c;借着这个机会又重新补充了关于python的一些知识#xff0c; 学习到了一些高效编写代码的方法和心得#xff0c;比如构建大项目来讲#xff0c;要明确捕捉异常机制的重要性#xff0c; 学会使用try...except..finally借着这个机会又重新补充了关于python的一些知识 学习到了一些高效编写代码的方法和心得比如构建大项目来讲要明确捕捉异常机制的重要性 学会使用try...except..finally 要通过日志模块logging监控整个系统的性能学会把日志输出到文件并了解日志层级日志模块的一些工作原理能自定义日志这样能非常方便的debug服务学会使用装饰器对关键接口进行时间耗时统计日志打印等装饰减少代码的重复性 学会使用类的类方法静态方法对一些公用函数进行封装来增强代码的可维护性 学会使用文档对函数和参数做注解 学会函数的可变参数统一代码的风格等等 这样能使得代码从可读性可维护性 灵活性和执行效率上都有一定的提升写出来的代码也更加优美一些。 所以把这几天的学习分模块整理几篇笔记 这次是从实践的再去补充python的内容目的是要写出漂亮的python代码增强代码的可读可维护灵活和高效方便调试和监控。
这篇文章介绍类和对象 这块主要介绍类和对象里面一些比较好用但之前没接触到的东西比如类的静态方法可以非常灵活的组织一些公用的工具函数 关键字参数使得代码整洁统一 装饰器使得代码更加灵活等。
大纲如下
类的创建和使用类的实例和静态变量类方法、实例方法、静态方法装饰器封装继承多态特殊的一些方法
OK, let’s go!
面向对象编程是一种封装的思想不可以更好地模拟真实世界里的事物将其视为对象并把描述特征的数据和代码块函数封装到一起
2 类的创建与使用
__init()__ 方法该方法是一个特殊的类实例方法称为构造方法(或构造函数,创建对象时使用每当创建一个类的实例对象时Python 解释器都会自动调用它.
注意即便不手动为类添加任何构造方法Python 也会自动为类添加一个仅包含 self 参数的构造方法。
# 创建和使用
class Person:# 定义两个类变量(属性)name wuzhongqiangage 29address chinadef __init__(self,name,add):#下面定义 2 个实例变量(属性)self.name nameself.age ageprint(name, age)# 下面定义了一个say实例方法def say(self, content):print(content)# 类的实例化 -- 创建对象的过程
person Person(zhangsan,38)
print(person.name) # 对象名字
print(person.address) # china 优先找对象自身属性如果没有再去找类的属性
print(Person.name) # 大类的名字
person.say(hello world)每个实例方法的参数里面第一个参数是self, 这是干嘛用的 Python 只是规定无论是构造方法还是实例方法最少要包含一个参数并没有规定该参数的具体名称。之所以将其命名为 self只是程序员之间约定俗成的一种习惯遵守这个约定可以使我们编写的代码具有更好的可读性。 self 参数的具体作用是什么呢标识每个实例化的对象用的这样能保证每个对象只能使用自己的类变量和类方法。 打个比方如果把类比作造房子的图纸那么类实例化后的对象是真正可以住的房子。根据一张图纸类我们可以设计出成千上万的房子类对象每个房子长相都是类似的都有相同的类变量和类方法但它们都有各自的主人那么如何对它们进行区分呢 当然是通过 self 参数它就相当于每个房子的门钥匙可以保证每个房子的主人仅能进入自己的房子每个类对象只能调用自己的类变量和类方法。 # 同一个类可以产生多个对象当某个对象调用类方法时该对象会把自身的引用作为第一个参数自动传给该方法换句话说Python 会自动绑定类方法的第一个参数指向调用该方法的对象。
# 如此Python解释器就能知道到底要操作哪个对象的方法, 和c里面的this一样
class Person:def __init__(self):print(正在执行构造方法)# 定义一个study()实例方法def study(self):print(self,正在学Python)
zhangsan Person()
zhangsan.study() # __main__.Person object at 0x0000021ADD7D21D0 正在学Python
lisi Person()
lisi.study() # __main__.Person object at 0x0000021ADD7D2E48 正在学Python# 值得一提的是除了类对象可以直接调用类方法还有一种函数调用的方式
class Person:def who(self):print(self)
zhangsan Person()
#第一种方式
zhangsan.who()
#第二种方式
who zhangsan.who
who()#通过 who 变量调用zhangsan对象中的 who() 方法2 类变量和实例变量 类体中、所有函数之外此范围定义的变量称为类属性或类变量 # 类变量的特点是所有类的实例化对象都同时共享类变量也就是说类变量在所有实例化对象中是作为公用资源存在的。
# 类方法的调用方式有 2 种既可以使用类名直接调用也可以使用类的实例化对象调用。
class Person:# 定义两个类变量(属性)name wuzhongqiangage 29address china#使用类名直接调用
print(Person.name)
print(Person.age)
#修改类变量的值
Person.name zhangsan
Person.age 30# 也可以使用类对象调用不建议因为类对象是属于整个类的
print(Person().name)
print(Person().age)# 但不能通过类对象修改 修改的时候相当于给类对象本身加了属性类体中所有函数内部以“self.变量名”的方式定义的变量称为实例属性或实例变量 # 特点是只作用于调用方法的对象。另外实例变量只能通过对象名访问无法通过类名访问。
class Person: def __init__(self,name,add):#下面定义 2 个实例变量(属性)self.name nameself.age ageprint(name, age)person Person(zhongqiang, 28)
print(person.name, person.age)
# 修改
person.name lisi
print(person.name)# 类中实例变量和类变量可以同名但这种情况下使用类对象将无法调用类变量它会首选实例变量这也是不推荐“类变量使用对象名调用”的原因。类体中所有函数内部以“变量名变量值”的方式定义的变量称为局部变量 class Person: def __init__(self,name,add):#下面定义 2 个实例变量(属性)self.name nameself.age age# 下面定义了一个say实例方法# 定义局部变量是为了所在类方法功能的实现。需要注意的一点是局部变量只能用于所在函数中函数执行完成后局部变量也会被销毁。def say(self):content hello world # 局部变量print(content)person Person(zhongqiang, 28)
person.say()3 类方法、静态方法和实例方法
实例方法属于实例 默认情况类中定义的方法都是实例方法建立了实例之后才能用。 类方法属于类 和实例方法类似最少要包含一个参数 通常命名cls约定俗成 python自动将类本身绑定到cls参数。 访问的时候类名.方法名访问。静态方法就是我们说的函数只不过定义在类的命名空间中而不是全局命名空间(程序所在空间) 静态方法也无须self, cls这样的特殊参数。 访问也是类名.方法名访问。
class People(object):def __init__(self):pass# 实例方法def say(self):passclassmethoddef sleep(cls):passstaticmethoddef eat():pass# 使用
p People()
p.say() # 实例调用实例方法# 神奇的是 实例方法也能通过类名去访问但必须手动把对象传进去这叫做非绑定方法
People.say(p) # 不建议用容易混乱People.sleep() # 类名直接调用类方法
People.eat() # 类名直接调用静态方法到这里我疑惑一个东西就是类方法和静态方法到底有啥区别呢
# 类方法Class Methods# 通过装饰器classmethod标记。# 第一个参数是类本身通常命名为cls。# 类方法可以访问和修改类状态即它可以改变类属性的值。# 可以通过类本身调用也可以通过类的实例调用。
# 静态方法Static Methods# 通过装饰器staticmethod标记。# 不接收额外的隐式第一个参数既不传递类也不传递实例。# 静态方法通常是一些与类相关的工具函数它们在逻辑上属于类的范围内但不需要访问类属性或实例属性。# 也可以通过类本身调用或通过类的实例调用。class MyClass:class_var I am a class varclassmethoddef class_method(cls):return fThis is a class method. {cls.class_var}staticmethoddef static_method():return This is a static method.# 类方法可以使用类变量
print(MyClass.class_method()) # 输出: This is a class method. I am a class var# 静态方法不能使用类变量因为它们没有传递类或实例的引用
print(MyClass.static_method()) # 输出: This is a static method.# 使用场合
# 需要访问或者修改类的状态应该用类方法
# 如果只是需要执行与类相关的某个操作而这个操作并不需要类或者实例中的任何属性那么静态方法比较。
# 静态方法也有助于封装让类的功能更为完整同时提供工具函数给使用者而不需要创建类的实例实际使用经验我发现静态方法对于一些工具函数的类以及写服务比较好使 在这个场合下很常用。 类方法目前为止没怎么用到。 Python 类体中的代码位于独立的命名空间称为类命名空间中。换句话说所有用 class 关键字修饰的代码块都可以看做是位于独立的命名空间中。和类命名空间相对的是全局命名空间即整个 Python 程序默认都位于全局命名空间中类其实是由多个类属性和类方法构成而类属性其实就是定义在类这个独立空间中的变量而类方法其实就是定义在类空间中的函数和定义在全局命名空间中的变量和函数相比并没有明显的不同 但需要注意的一点是当使用类对象调用类方法时在传参方面是和外界的函数有区别的因为 Python 会自动会第一个参数绑定方法的调用者而位于全局空间中的函数则必须显式为第一个参数传递参数。
4 描述符
使用描述符可以让程序员在引用一个对象属性时自定义要完成的工作。本质上这哥们也是一个类只不过它定义了另一个类中属性的访问方式。 描述符是 Python 中复杂属性访问的基础它在内部被用于实现 property、方法、类方法、静态方法和 super 类型。 3个特殊方法
__set__(self, obj, typeNone)在设置属性时将调用这一方法__get__(self, obj, value)在读取属性时将调用这一方法__delete__(self, obj)对属性调用 del 时将调用这一方法
其中实现了 __get__ 和__set__方法的描述符类被称为数据描述符反之如果只实现了 __get__ 方法则称为非数据描述符。
在每次查找属性时描述符协议中的方法都由类对象的特殊方法 __getattribute__() 调用。就是说每次使用类对象.属性或者 getattr(类对象属性值)的调用方式时都会隐式地调用 __getattribute__()它会按照下列顺序查找该属性
验证该属性是否为类实例对象的数据描述符如果不是就查看该属性是否能在类实例对象的 __dict__ 中找到最后查看该属性是否为类实例对象的非数据描述符。
这东西干嘛用 看个例子
#描述符类
class revealAccess:def __init__(self, initval None, name var):self.val initvalself.name name# 要实现下面两个方法def __get__(self, obj, objtype):print(Retrieving,self.name)return self.valdef __set__(self, obj, val):print(updating,self.name)self.val valclass myClass:x revealAccess(28,var x)y 5m myClass()
print(m.x) # Retrieving var x 28 调了__get__方法
print(m.y) # 5
m.x 30 # updating var x 调了__set__方法
print(m.x) # Retrieving var x 30 调了__get__方法# 果一个类的某个属性有数据描述符那么每次查找这个属性时都会调用描述符的 __get__() 方法并返回它的值同样每次在对该属性赋值时也会调用 __set__() 方法除了使用描述符类自定义类属性被调用时做的操作外还可以使用 property() 函数或者 property 装饰器所以整理这个就是为了给后面的装饰器操作作铺垫。
5 property函数与property装饰器
直接用类对象.属性方式访问类中定义属性破坏了类的封装原则 正常情况类中属性应该是隐藏的只允许通过提供的方法间接实现对类属性的访问和操作。
所以不破坏封装的原则下 一个类里面可能需要多个set或者get方法单独对属性做操作然后对象调用这些方法间接操作属性。
class Person:def __init__(self, name: str):self._name name # 注意这里用_修饰属性表明是私有的外面没法通过对象.属性访问。def get_name(self):return self._namedef set_name(self,name):self._name namep Person(zhongqiang)
print(p.get_name())
p.set_name(zhangsan)
print(p.get_name())
print(p.name) # AttributeError: Person object has no attribute name那么如果感觉这个太麻烦还想用类对象.属性访问怎么办呢 property函数 不破坏类封装原则的前提下 可以通过类对象.属性操作类中的属性。 属性名property(fgetNone, fsetNone, fdelNone, docNone)
# fget 参数用于指定获取该属性值的类方法fset 参数用于指定设置该属性值的方法fdel 参数用于指定删除该属性值的方法最后的 doc 是一个文档字符串用于说明此函数的作用class Person:def __init__(self, name: str):self._name name # 注意这里用_修饰属性表明是保护的外面没法通过对象.属性访问。def get_name(self):return self._namedef set_name(self,name):self._name namedef del_name(self):self._name None# 后面这几个函数不一定全部指定 但是怎么指定决定了name的属性 这啥意思# 假设只指定get_name 那么这个属性只能读不能写和删除# 假设指定get_name, set_name那么只能读写不能删除即使上面有写删除函数也不行# name property(get_name, set_name, del_name, 对象访问类属性)name property(get_name, set_name) # 可读可写不可通过类对象.属性删除p Person(zhongqiang)
print(p.name) # zhongqiang 好使了
del p.name # AttributeError: cant delete attribute
print(p.name)property装饰器 这个也可以操作类中的私有属性 class Person:def __init__(self, name: str):self._name name # 注意这里用_修饰属性表明是保护的外面没法通过对象.属性访问。property # 这个就把name()这个方法变成了_name的__get__()方法def name(self):return self._namename.setterdef name(self, name):self._name namename.deleterdef name(self):self._name Nonep Person(zhongqiang)
print(p.name)
p.name zhangsan
print(p.name)
del p.name
print(p.name)这里不单纯是整理上面这点内容这么简单因为我发现实际编程里面装饰器这个写法还是经常用到的功能很强大所以想了解清楚些。 Python中的装饰器是一种非常强大且有用的工具它允许程序在不修改原始函数代码的情况下增加函数功能。简单来说装饰器可以在你调用一个函数之前或之后自动运行一些代码而无需改变函数的调用方式。 实现原理装饰器本质上是一个接受函数作为参数并返回一个新函数的函数。当你使用decorator语法时你实际上是在告诉Python执行如下操作“取得紧随其后的函数传递给装饰器函数然后将返回的新函数作为原始函数的替代”。 通过在方法上使用装饰器可以使得原本杂乱无章的代码变得更加清晰和易于管理同时也使得代码的重用性更高减少了重复性劳动**。** 常使用场景 # 1. 日志记录 自动记录方法的调用信息 时间参数执行结果耗时等 调试和监控应用
import loggingdef log_method_call(func):def wrapper(self, *args, **kwargs): # 如果是修饰普通函数就没有self这个参数 如果是类方法就加clslogging.info(f调用方法: {func.__name__}, 参数: {args}, {kwargs})return func(self, *args, **kwargs)return wrapperclass LoggerDemo:log_method_calldef example(self, x, y):return x y# 设定日志级别
logging.basicConfig(levellogging.INFO)
print(LoggerDemo().example(5, y3)) # INFO:root:调用方法: example, 参数: (5,), {y: 3}# 2. 性能检测 测量和记录实例方法的执行时间有助于识别和优化性能瓶颈
import timedef measure_time(func):def wrapper(self, *args, **kwargs):start_time time.time()result func(self, *args, **kwargs)end_time time.time()print(f方法 {func.__name__} 执行耗时: {end_time - start_time} 秒)return resultreturn wrapperclass PerformanceDemo:measure_timedef compute(self, n):return sum(i * i for i in range(n))print(PerformanceDemo().compute(5)) # 方法 compute 执行耗时: 6.9141387939453125e-06 秒# 3. 权限验证 在执行敏感操作前检查用户是否具有相应的权限确保应用的安全性
def check_permission(func):def wrapper(self, *args, **kwargs):if not self.user_has_permission:raise Exception(无权执行该操作)return func(self, *args, **kwargs)return wrapperclass PermissionDemo:user_has_permission Falsecheck_permissiondef sensitive_operation(self):print(敏感操作已执行)# 4. 参数验证自动检查传递给方法的参数是否满足特定条件如类型、范围等以保证方法体内代码的健壮性
def validate_args(func):def wrapper(self, x, y):if not isinstance(x, int) or not isinstance(y, int):raise ValueError(参数必须是整数)return func(self, x, y)return wrapperclass ValidateArgsDemo:validate_argsdef add(self, x, y):return x y# 5. 缓存管理 对实例方法的返回结果进行缓存如果之后的调用使用了相同的参数则直接返回缓存的结果以减少计算量和提高效率
from functools import lru_cache
class CacheDemo:lru_cache(maxsize32)def expensive_operation(self, x):# 费时的计算…print(执行费时操作)return x * x# 6. 异常处理 统一处理方法中可能抛出的异常可以用来记录错误信息或进行错误回复使得方法体更加简洁
def handle_exceptions(func):def wrapper(self, *args, **kwargs):try:return func(self, *args, **kwargs)except Exception as e:print(f捕获到异常: {e})return wrapperclass ExceptionDemo:handle_exceptionsdef risky_method(self):raise RuntimeError(出现问题了)# 7. 资源管理 自动管理资源的获取和释放例如数据库连接或文件句柄等确保资源的有效利用和防止泄漏
from contextlib import contextmanagercontextmanager
def manage_resource():print(资源获取)yieldprint(资源释放)class ResourceDemo:manage_resourcedef operation_with_resource(self):print(使用资源执行操作)# 资源获取
# 使用资源执行操作
# 资源释放# 8. 数据清洗和验证 在数据持久化前对数据进行必要的清洗和验证确保数据的准确性和完整性
def validate_data(func):def wrapper(self, data):if not isinstance(data, dict) or name not in data:raise ValueError(无效的数据)data[name] data[name].strip().title()return func(self, data)return wrapperclass DataProcessingDemo:validate_datadef process_data(self, data):print(f处理数据: {data})# 9. API调用限制 限制方法的调用频率防止滥用例如在网站后端限制某些费时的API调用
import time
def rate_limited(max_per_second):min_interval 1.0 / float(max_per_second)def decorate(func):last_called [0.0]def rate_limited_function(self, *args, **kwargs):elapsed time.clock() - last_called[0]left_to_wait min_interval - elapsedif left_to_wait 0:time.sleep(left_to_wait)last_called[0] time.clock()return func(self, *args, **kwargs)return rate_limited_functionreturn decorateclass APICallDemo:rate_limited(1) # 限制每秒调用不超过1次def call_api(self):print(API 被调用)# 10. 事务管理 在执行数据库操作时通过装饰器自动地开始事务和提交或回滚事务简化代码结构
def transactional(func):def wrapper(self, *args, **kwargs):print(开始事务)try:result func(self, *args, **kwargs)except Exception as e:print(回滚事务)raiseelse:print(提交事务)return resultreturn wrapperclass TransactionDemo:transactionaldef do_transaction(self):print(执行事务操作)# 11. 业务逻辑扩展 在不修改原有方法定义的情况下为方法添加额外的业务逻辑实现低耦合的功能拓展
def additional_logic(func):def wrapper(self, *args, **kwargs):print(执行额外的业务逻辑)return func(self, *args, **kwargs)return wrapperclass BusinessExtensionDemo:additional_logicdef original_business_method(self):print(执行原有业务方法)# 一个方法可以被多个装饰器修饰此时的执行顺序
# 当你有多个装饰器修饰同一个方法时实际的执行顺序确实是从内向外执行但是这个从内向外指的是装饰器的包装wrapping顺序而不是它们的执行顺序
decorator1
decorator2
def my_method():print(原始方法执行)
# 相当于my_method decorator1(decorator2(my_method))# 首先decorator2 被调用它接收my_method作为参数然后返回一个新的函数这个新函数加入了一些前置操作和后置操作。
# 然后decorator1 被调用它接收decorator2的输出也就是被decorator2装饰过的my_method函数作为参数然后返回另一个新的函数再次添加了一些前置操作和后置操作。# 当调用经过多个装饰器修饰的方法时执行的顺序是按照从最外层装饰器到最内层装饰器的顺序进入即代码中写装饰器的顺序然后执行原始方法最后再按照从最内层装饰器到最外层装饰器的顺序依次退出。实际编程中如果能巧妙运用会写出非常漂亮的代码并增强可维护性。 比如 处理异常的代码 封装日志的代码结合可以让每个方法都顺利捕捉异常且落盘到文件排查问题很方便 或者计算函数处理时间 附加逻辑的代码 可以对服务里面的重要接口实现打点操作记录接口响应的耗时请求等参数并存储到数据库的一张表再同步到看板就能非常方便的查看是否有响应异常pvuv等。
6 封装
封装是面向对象编程语言的典型特征之一 面向对象编程的四大特性 封装Encapsulation封装是把数据属性和处理数据的方法绑定一起形成一个整体即类。它隐藏了内部实现的细节只暴露出操作接口。外界通过接口与数据进行交互提高了数据的安全性和代码的可复用性。继承Inheritance继承让子类可以承继父类的属性和方法同时还可以扩展新的属性和方法。继承可以提高代码的复用性同时还可以建立类与类之间的关系。多态Polymorphism多态意味着可以通过父类的引用来实现对不同子类的方法进行调用。即同一个方法调用由于调用主体的不同可能有不同的行为。实现多态的方式有很多最常见的是通过虚函数和函数重载。抽象Abstraction抽象是将现实世界中的实体转化为计算机编程中的类和对象的过程以及在类中只抽取出对当前目标有用的部分的特性。在类中定义抽象方法来表示这个行为由具体子类提供实现细节。这样可以提高程序的清晰度和可维护性。 和其它面向对象的编程语言如 C、Java不同Python 类中的变量和函数不是公有的类似 public 属性就是私有的类似 private这 2 种属性的区别如下
public公有属性的类变量和类函数在类的外部、类内部以及子类中都可以正常访问private私有属性的类变量和类函数只能在本类内部使用类的外部以及子类都无法使用。
但是Python 并没有提供 public、private 这些修饰符。为了实现类的封装Python 采取了下面的方法
默认情况下Python 类中的变量和方法都是公有public的它们的名称前都没有下划线如果类中的变量和函数其名称以双下划线“__”开头则该变量函数为私有变量私有函数其属性等同于 private。
有时候还可以看到以单下划线“_”开头的类属性或者类方法例如 _name、_display(self)这种类属性和类方法通常被视为私有属性和私有方法是一种按照约定俗称的做法用来指示这个变量或方法不应该被直接访问即它们是“受保护的”或“私有的”。 注意Python 类中还有以双下划线开头和结尾的类方法例如类的构造函数__init__(self)这些都是 Python 内部定义的用于 Python 内部调用。我们自己定义类属性或者类方法时不要使用这种格式。 下面看一个封装
class Person:def __init__(self, name: str):# 私有了 外部不能直接访问self.__name name def setname(self, name):if len(name) 3:raise ValueError(名称长度必须大于3)self.__name namedef getname(self):return self.__name# 为 name 配置 setter 和 getter 方法name property(getname, setname)#定义个私有方法def __display(self):print(self.__name)p Person(zhongqiang)
p.name zhongsan
print(p.name)
print(p.display()) # AttributeError: Person object has no attribute display封装机制的底层原理Python 封装特性的实现纯属“投机取巧”之所以类对象无法直接调用以双下划线开头命名的类属性和类方法是因为其底层实现时Python 偷偷改变了它们的名称事实上对于以双下划线开头命名的类属性或类方法Python 在底层实现时将它们的名称都偷偷改成了 “_类名__属性方法名” 的格式
# 所以上面的display函数 由于是__display 在python底层实现时 被改成了_Person__display()
p._Person__display() # 这样就ok了
p._Person__name # 这样也能直接打印名字属性7 继承
继承机制经常用于创建和现有类功能类似的新类又或是新类只需要在现有类基础上添加一些成员属性和方法但又不想直接将现有类代码复制给新类。也就是说通过使用继承这种机制可以轻松实现类的重复使用。
# 子类继承父类时只需在定义子类时将父类可以是多个放在子类之后的圆括号里即可
class 类名(父类1, 父类2, ...)#类定义部分
# 注意如果该类没有显式指定继承自哪个类则默认继承 object 类object 类是 Python 中所有类的父类即要么是直接父类要么是间接父类。
# 另外Python 的继承是多继承机制和 C 一样即一个子类可以同时拥有多个直接父类。class People:def __init__(self):self.name Peopledef say(self):print(People类,self.name)def __display():print(self.name)class Animal:def __init__(self):self.name Animaldef say(self):print(Animal类,self.name)#People中的 name 属性和 say() 会遮蔽 Animal 类中的
class Person(People, Animal):pass
zhangsan Person()
zhangsan.name 张三
zhangsan.say() # People类 张三
zhangsan._Person.__display() # AttributeError: Person object has no attribute _Person__display
zhangsan._People.__display() # 张三 由此可见 子类会拥有父类所有的属性和方法即便该属性或方法是私有private的# Python支持多继承 但如果多个父类里面有同名函数会用排在前面父亲里面的
# 尽量少用多继承这玩意容易使得逻辑混乱方法的重写
# 子类继承了父类那么子类就拥有了父类所有的类属性和类方法。通常情况下子类会在此基础上扩展一些新的类属性和类方法。
# 但凡事都有例外我们可能会遇到这样一种情况即子类从父类继承得来的类方法中大部分是适合子类使用的但有个别的类方法并不能直接照搬父类的
# 如果不对这部分类方法进行修改子类对象无法使用。针对这种情况我们就需要在子类中重写父类的方法。
class Person(People):def say(self):print(Person类, self.name)
zhangsan Person()
zhangsan.name 张三
zhangsan.say() # Person类 张三 这时候子类就会调用自己的方法了# 如果还想让say调用父亲的方法怎么办呢 这个就需要用到之前学到的非绑定方法 手动通过类名.方法名传参
People.say(zhangsan) # People类 张三如何使用python的继承机制提升写代码的效率呢 内置类型子类化 实际编程过程中如果想实现与某个内置类型具有类似行为的类时最好的方法就是将这个内置类型子类化, 其实就是自定义一个新类使其继承有类似行为的内置类通过重定义这个新类实现指定的功能 # 比如我们可以自定义一个字典的子类 在能使用字典功能的同时加一些我们自己的方法去实现新功能
class newDictError(ValueError):如果向newDict 添加重复值则引发此异常class mydict(dict):不接受重复值的字典def __setitem__(self,key,value):if value in self.values():if ((key in self and self[key]!value) or (key not in self)):raise newDictError(这个值已经存在并对应不同的键)# super可以调用父类的方法 一般super用来初始化父类的属性super().__setitem__(key,value)
demoDict mydict()
demoDict[key]value
demoDict[other_key]value2
print(demoDict)
demoDict[other_key]value
print(demoDict) # __main__.newDictError: 这个值已经存在并对应不同的键# collections 模块中提供了很多有用的容器比如defaultdictordereddict等应该就是用了这种方式实现的# super的功效
class father(object):def __init__(self, name: str):self._name nameclass child(father):def __init__(self, name: str, age: int):self._age agesuper().__init__(name)def display(self):print(self._name, self._age)p child(zhongqiang, 30)
p.display()type函数的高级功效 可以动态的创建类
# type() 函数的语法格式有 2 种:# 1. 查看某个变量类对象的具体类型type(obj)# 2. 创建类type(name, bases, dict), 其中 name 表示类的名称bases 表示一个元组其中存储的是该类的父类dict 表示一个字典用于表示类内定义的属性或者方法。# 第一种非常常见和使用
# 看下第二种
def say(self):print(self.name, self.age)
Person type(Person, (object,), dict(namezhongqiang, age29, saysay))
p Person()
p.say() # zhongqiang 29# 如何判断 dict 字典中添加的是方法还是属性如果该键值对中值为普通变量则表示为类添加了一个类属性反之如果值为外部定义的函数如 say() 则表示为类添加了一个实例方法
# 使用 class 定义类时Python 解释器底层依然是用 type() 来创建这个类8 多态
类的多态特性要满足以下 2 个前提条件
继承多态一定是发生在子类和父类之间重写子类重写了父类的方法。
# 多态是声明一个父类类型的对象 定义的时候定义成子类 这时候这个对象调用子类方法的时候会找到具体子类里面的方法进行实现
# 多态是一种原则它允许不同的类的对象对同一个方法调用做出不同的响应。多态性表明不需要知道一个对象的确切类型就可以执行该对象的方法
# 但python是弱类型语言天然支持多态性。
# 这意味着在Python里你不需要显式地定义接口或者使用不同的继承机制来实现多态性。Python程序员可以期望任何对象都能执行被调用的方法只要该方法在对象上确实存在
# python里面也不一定非得继承
class Dog:def speak(self):return Woof!class Cat:def speak(self):return Meow!# 不知道确切的类别也能调用各自的speak方法次 who(animal):
class who:def say(self, who):print(who.speak())a who()
a.say(Dog())
a.say(Cat())# Python 这种由多态衍生出的更灵活的编程机制又称为“鸭子模型”或“鸭子类型”, 这感觉已经不是多态了9 类里面的特殊成员和属性
类的重要内容介绍完毕之后这里再把一些附属内容整理下即类的特殊成员和属性 知道这些写代码的时候有时候可以起到锦上添花的功效。 以双下划线 “__” 开头和结尾命名的成员属性和方法都被称为类的特殊成员特殊属性和特殊方法 # __new__() 负责创建类实例的静态方法
# 在构造类的新实例时被自动调用。
# 它负责在一个类的对象被创建之前为该对象分配空间。__new__是一个静态方法而且它是在__init__方法之前被调用的。
# 与__init__方法不同__new__方法返回的是类的一个实例而__init__则用于初始化这个实例的某些属性。
# __new__方法在一些特定的场合下非常有用例如在创建单例模式、控制对象创建过程或者当继承一些不可变的类型比如一个元组或字符串时。class Singleton:_instance None # 类属性用于存储类的单一实例def __new__(cls, *args, **kwargs):if not cls._instance: # 若实例不存在则创建一个cls._instance super(Singleton, cls).__new__(cls, *args, **kwargs)return cls._instance# 创建两个Singleton的实例
instance1 Singleton()
instance2 Singleton()# 检查两个实例是否相同
print(instance1 is instance2) # 输出: True# 需要注意的是直接使用__new__方法是一个高级特性通常情况下不需要重写它。在大多数情况下初始化实例的时候使用__init__方法就足够了。但在需要精确控制对象创建过程的高级用法中了解和使用__new__方法是很有用的。# __repr__()方法显示属性, 这个在代码调试中非常有用
# 通常情况下重写该方法可以帮助我们了解该对象的基本信息例如该对象有哪些属性它们的值各是多少等等
class Employee:def __init__(self, name, id):self.name nameself.id iddef __repr__(self):# 返回一个可以用来重建这个对象的字符串表示return fEmployee({self.name!r}, {self.id!r})# 创建一个Employee对象
emp Employee(zhongqiang, 11111)# 使用repr获取对象的“官方”字符串表示
repr_emp repr(emp)# 打印对象的repr表示
print(repr_emp) # Employee(zhongqiang, 11111)# __del__(): 用来销毁实例化对象
# 编写程序时如果之前创建的类实例化对象后续不再使用最好在适当位置手动将其销毁释放其占用的内存空间
# python有自动垃圾回收机制能自动将不需要使用的实例对象进行销毁
# Python 采用自动引用计数简称 ARC的方式实现垃圾回收机制。该方法的核心思想是每个 Python 对象都会配置一个计数器初始 Python 实例对象的计数器值都为 0如果有变量引用该实例对象其计数器的值会加 1依次类推反之每当一个变量取消对该实例对象的引用计数器会减 1。如果一个 Python 对象的的计数器值为 0则表明没有变量引用该 Python 对象即证明程序不再需要它此时 Python 就会自动调用 __del__() 方法将其回收。
# 但是读者千万不要误认为只要为该实例对象调用 __del__() 方法该对象所占用的内存空间就会被释放
class CLanguage:def __init__(self):print(调用 __init__() 方法构造对象)def __del__(self):print(调用__del__() 销毁对象释放其空间)
clangs CLanguage()
#添加一个引用clangs对象的实例对象
cl clangs
del clangs # 此时删除它 白搭 对象的空间不会释放因为还有个cl在这里引用这个python对象呢
print(***********)
del cl # 此时需要手动删除他# 需要额外说明的是如果我们重写子类的 __del__() 方法父类为非 object 的类
# 则必须显式调用父类的 __del__() 方法这样才能保证在回收子类对象时其占用的资源可能包含继承自父类的部分资源能被彻底释放
class CLanguage:def __del__(self):print(调用父类 __del__() 方法)
class cl(CLanguage):def __del__(self):print(调用子类 __del__() 方法)# 这里要显示的调用父类的__del__方法super().__del__()
c cl()
del c
# 调用子类 __del__() 方法
# 调用父类 __del__() 方法# __dir__(): 列出对象的所有属性和方法名这个也有用
# 用到的是dir()这个背后调的__dir__ 然后对输出做了排序
class Person:def __init__ (self,):self.name zhongqiangself.age 30def say():print(self.name, self.age)
p Person()
print(dir(p))
# 过滤掉特殊成员和方法
filtered_dir [attr for attr in dir(p) if not attr.startswith(__)]
print(filterd_dir) # [age, name, say]# __dict__: 查看对象内部所有属性名和属性值组成的字典, 用于调试
# 该属性可以用类名或者类的实例对象来调用用类名直接调用 __dict__会输出该由类中所有类属性组成的字典而使用类的实例对象调用 __dict__会输出由类中所有实例属性组成的字典
class MyClass:class_var I am a class variabledef __init__(self, val):self.instance_var valdef a_method(self):pass# 创建MyClass的实例
obj MyClass(10)# 打印实例的__dict__属性
print(obj.__dict__) # {instance_var: 10}
print(MyClass.__dict__) # {__module__: __main__, class_var: I am a class variable, __init__: , a_method: , __dict__: , __weakref__: , __doc__: None}# __call__(): 该方法的功能类似于在类中重载 () 运算符使得类实例对象可以像调用普通函数那样以“对象名()”的形式使用。
# Python 中凡是可以将 () 直接应用到自身并执行都称为可调用对象。可调用对象包括自定义的函数、Python 内置函数以及本节所讲的类实例对象。
# 实际上“名称()”可以理解为是“名称.__call__()”的简写
class Adder:def __init__(self, n):self.n ndef __call__(self, x):return self.n x# 创建一个Adder实例
adder Adder(10)# 由于定义了__call__方法我们可以调用这个实例
result adder(5) # 相当于调用adder.__call__(5)print(result) # 输出15所以最后总结写大型项目代码时候的一个逻辑规范
# 大型项目里面要考虑到代码的复用性和层次的清晰性
# 采用面向对象的编程方式从底层的基类开始写起# 先声明一个基类 这个基类里面具备基本的属性和为公共的一些方法
class Base(object):# 这里可以包含一些共用的属性def __init__(self, xxx):self._xxx xxx# 这里声明一些共用的方法接口 但不要具体实现 由后面继承的子类根据具体业务实现方法def common_method(self):raise Exception(common_method not implemented.)def common_method2(self):raise Exception(common_method2 not implemented.)# 也可以预留一些方法def common_method3(self):pass# 必要的时候也可以声明一些类方法或者静态方法staticmethoddef xxx():passclassmethoddef xxx(cls):pass# 接下来开始写子类 继承上面的base父类 然后在这个基础上实现具体的方法完成事情
class child(Base):# 可以有自己的一些属性def __init__(self, xxx, yyy):super().__init__(xxx)self._yyy yyy# 实现具体的方法def common_method(self):passdef common_method2(self):pass# 接下来 上层用这个子类去做相关的操作
c child(xxx, yyy)
c.common_method()
c.common_method2()# 整体思路 Base类搭建框架 子类做具体实现 层层继承 层层往上抽象 层层往下扩展让这个项目的逻辑层次非常清晰10 小总
这篇文章主要是剖析了下关于类和对象的一些细节部分 面向对象的三大特性继承封装多态 在python里面之前很少用到 但实际写代码中才发现能合理的对类做抽象并灵活使用继承会让代码变得更加的灵活和简洁 不过在耦合度上由于继承的引入会增高所以这一块也要适当做下衡量装饰器很强大可以有效的扩展函数功能且节省代码冗余静态方法也非常常用 可以帮助组织一些公用的函数。
参考
C语言中文网教程