超越基础,拓展Python编程技能:深入探索Python高级特性

本文详细介绍了Python的高级特性,包括动态强类型、鸭子类型、Monkey Patch、自省机制以及内存管理。还讨论了Python如何进行内存管理,如引用计数、垃圾回收和内存池。此外,深入讲解了函数的参数传递、闭包、面向对象编程中的封装、继承、多态等概念,并提到了元类在创建类中的作用。
摘要由CSDN通过智能技术生成

什么是Python的高级特性?

这个问题对于许多正在学习Python的小伙伴来说可能会很疑惑?很多人知道这个名词,但不知道这玩意到底是什么。其实,Python作为一门高级语言它有许多的内置函数,运用这些函数可以很方便的完成一些功能,而这些特性,笼统的可称为Python的高级特性。简单地说Python的高级特性就是一些Python函数的高级用法。

1.语言特性

(1)python是动态强类型的语言

动态还是静态指的是编译器还是运行期确定类型。

强类型指的是不会发生隐式类型转换。比如js能够执行1+“1”,但是python不行,所以python是弱类型的语言。

(2)鸭子类型

当一只鸟走起来像鸭子、游泳起来像鸭子、叫气力啊也像鸭子,那么这只鸟就可以被称为鸭子。
鸭子类型关注的是对象的行为,而不是类型。比如file,StringIO,socket对象都支持read/write方法,再比如定义了__iter__魔术方法的对象可以用for迭代。

(3)monkey patch

所谓的monkey patch就是运行时替换。

(4) 自省

运行时判断一个对象类型的能力。

python一切皆对象,用type, id, isinstance获取对象类型信息。

自省,也可以说是反射,自省在计算机编程中通常指这种能力:检查某些事物以确定它是什么、它知道什么以及它能做什么。

与其相关的主要方法:

  • hasattr(object, name)检查对象是否具体 name 属性。返回 bool;
  • getattr(object, name, default)获取对象的name属性;
  • setattr(object, name, default)给对象设置name属性;
  • delattr(object, name)给对象删除name属性;
  • dir([object])获取对象大部分的属性;
  • isinstance(name, object)检查name是不是object对象;
  • type(object)查看对象的类型;
  • callable(object)判断对象是否是可调用对象;
(5)列表和字典推导

如 [i for i in range(10) if i % 2 == 0],如果[]改为(),则为生成器。

(6)变量查找顺序

函数作用域的LEGB顺序

L:local 函数内部作用域 E: enclosing 函数内部与内嵌函数之间 G: global 全局作用域 B:build-in 内置作用。

python在函数里面的查找分为4种,称之为LEGB,也正是按照这是顺序来查找的。

2.Python是如何进行内存管理的

从三个方面来说,一是对象的引用计数机制,二是垃圾回收机制,三是内存池机制。

(1) 对象的引用计数机制

Python内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用计数。

引用计数增加的情况:

一个对象分配一个新名称;
将其放入一个容器中(如列表、元组或字典);
引用计数减少的情况:

使用del语句对对象别名显示的销毁;
引用超出作用域或被重新赋值;
sys.getrefcount()函数可以获得对象的当前引用计数。

多数情况下,引用计数比你猜测得要大得多。对于不可变数据(如数字和字符串),解释器会在程序的不同部分共享内存,以便节约内存。

(2) 垃圾回收

当一个对象的引用计数归零时,它将被垃圾收集机制处理掉。
当两个对象a和b相互引用时,del 语句可以减少a和b的引用计数,并销毁用于引用底层对象的名称。然而由于每个对象都包含一个对其他对象的应用,因此引用计数不会归零,对象也不会销毁。(从而导致内存泄露)。为解决这一问题,解释器会定期执行一个循环检测器,搜索不可访问对象的循环并删除它们。

(3)内存池机制

Python 提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统。

Pymalloc机制。为了加速Python的执行效率,Python引入了一个内存池机制,用于管理对小块内存的申请和释放。
Python中所有小于256个字节的对象都使用pymalloc实现的分配器,而大的对象则使用系统的malloc。
对于Python对象,如整数,浮点数和List,都有其独立的私有内存池,对象间不共享他们的内存池。也就是说如果你分配又释放了大量的整数,用于缓存这些整数的内存就不能再分配给浮点数。

3.函数

(1)python如何传递参数

python既不是值传递也不是引用传递,唯一支持的参数传递是共享传参。

call by object(call by object reference or call by sharing)call by sharing(共享传参),函数形参获得实参中各个引用的副本。

变量一切都是对象。list是可变对象,string是不可变对象。

总结一下:根据对象的引用来传递,根据对象是可变对象还是不可变对象,得到两种不同的结果。如果是可变对象,则直接修改。如果是不可变对象,则生产新对象,让形参指向新对象

(2)python可变/不可变对象

不可变对象: bool/int/float/tuple/str/frozenset。

可变对象:list/set/dict。

案例:

# 1
def clear_list(l):
    l = []
ll = [1,2,3]
clear_list(ll)
print(ll)
# 2
def fl(l=[1]):
    l.append(1)
    print(l)
fl()
fl()

# 记住:默认参数只计算一次
(3)*args, **kwargs

用来处理可变参数,args被打包成tuple,kwargs被打包成dict。

传递方式有两种:

# 第一种
foo(1,2,3)
foo(a=1,b=2)
 
# 第二种
foo(*[1,2,3])
foo(**dict(a=1,b=2)})

其实并不是必须写成args和**kwargs。只有变量前面的(星号)才是必须的.你也可以写成var和**vars.而写成args和**kwargs只是一个通俗的命名约定,那就让我们先看一下*args吧。

*args和**kwargs主要用于函数定义。你可以将不定数量的参数传递给一个函数。

这里的不定的意思是:预先并不知道,函数使用者会传递多少个参数给你,所以在这个场景下使用这两个关键字。*args是用来发送一个非键值对的可变数量的参数列表给一个函数。

这里有个例子帮你理解这个概念:

def test_var_args(f_arg, *argv):
    print("first normal arg:", f_arg)
    for arg in argv:
        print("another arg through *argv:", arg)
 
test_var_args('yasoob', 'python', 'eggs', 'test')
这会产生如下输出:

first normal arg: yasoob
another arg through *argv: python
another arg through *argv: eggs
another arg through *argv: test

那接下来让我们谈谈 **kwargs,kwargs允许你将不定长度的键值对,作为参数传递给一个函数。如果你想要在一个函数里处理带名字的参数,你应该使用kwargs。

这里有个让你上手的例子:

def greet_me(**kwargs):
    for key, value in kwargs.items():
        print("{0} == {1}".format(key, value))
 
>>> greet_me(name="yasoob")
name == yasoob

现在你可以看出我们怎样在一个函数里,处理了一个键值对参数了。

这就是kwargs的基础,而且你可以看出它有多么管用。接下来让我们谈谈,你怎样使用*args和kwargs来调用一个参数为列表或者字典的函数。

那现在我们将看到怎样使用*args和**kwargs来调用一个函数。假设,你有这样一个小函数:

def test_args_kwargs(arg1, arg2, arg3):
    print("arg1:", arg1)
    print("arg2:", arg2)
    print("arg3:", arg3)

你可以使用*args或**kwargs来给这个小函数传递参数。

下面是怎样做:

## 首先使用 *args
>>> args = ("two", 3, 5)
>>> test_args_kwargs(*args)
arg1: two
arg2: 3
arg3: 5
 
## 现在使用 **kwargs:
>>> kwargs = {"arg3": 3, "arg2": "two", "arg1": 5}
>>> test_args_kwargs(**kwargs)
arg1: 5
arg2: two
arg3: 3
标准参数与*args、**kwargs在使用时的顺序?

4.闭包

闭包特性:

  • 绑定了外部作用域的变量的函数;

  • 即使程序离开外部作用域,如果闭包仍然可见,绑定变量不会销毁;

  • 每次运行外部函数都会重新创建闭包;

  • 闭包:引用了外部自由变量的函数;

  • 自由变量:不在当前函数定义的变量;

  • 特性:自由变量会和闭包函数同时存在;

from functools import wraps
 
def cache(func):
    store = {}
    @wraps(func)
    def _(n):
        if n in store:
            return store[n]
        else:
            res = func(n)
            store[n] = res
            return res
    return _
 
@cache
def f(n):
    if n <= 1:
        return 1
    return f(n-1) + f(n-2)

为什么用@wraps(func)?

因为当使用装饰器装饰一个函数时,函数本身就已经是一个新的函数;即函数名称或属性产生了变化。所以在python的functools模块中提供了wraps装饰函数来确保原函数在使用装饰器时不改变自身的函数名及应有属性。 所以在装饰器的编写中建议加入wraps确保被装饰的函数不会因装饰器带来异常情况。

在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包。

简单的说,如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。

来看一个简单的例子:

>>>def addx(x):
>>>    def adder(y): return x + y
>>>    return adder
>>> c =  addx(8)
>>> type(c)
<type 'function'>
>>> c.__name__
'adder'
>>> c(10)
18

5.面向对象编程

1. 封装、继承、多态
class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def print_name(self):
        print self.name
2. 组合与继承

优先使用组合(has a)而非继承(is a)。

3. 类变量和实例变量的区别

类变量由所有实例共享;
实例变量由实例单独享有,不同实例之间不影响;
当我们需要在一个类的不同实例之间共享变量的时候使用类变量;

4. classmethod/staticmethod区别

都可以通过Class.method()的方式使用;
classmethod第一个参数是cls,可以引用类变量;
staticmethod使用起来和普通函数一样,只不过放在类里去组织;

class Person(object):
    Country = 'china'
    def __init__(self, name, age):
        self.name = name
        self.age = age
 
    def print_name(self):
        print self.name
 
    @classmethod
    def print_country(cls):
        print(cls.Country)
 
    @staticmethod
    def join_name(first_name, last_name):
        return last_name + first_name
5. 什么是元类

元类是创建类的类:

元类允许我们控制类的生成,比如修改类的属性等;
使用type来定义元类;
元类最常见的一个使用场景就是ORM框架;
__new__用来生成实例,__init__用来初始化实例;
例子:

# 元类继承自type
class LowercaseMeta(type):
    """ 修改类的书写名称为小写的元类"""
    def __new__(mcs, name, bases, attrs):
        lower_attrs = {}
        for k, v in attrs.items():
            if not k.startswith('__'):
                lower_attrs[k.lower()] = v
            else:
                lower_attr[k] =v
        return type.__new__(mcs, name, bases, lower_attrs)
 
class LowercaseClass(metaclass=LowercaseMeta):
    BAR = True
 
    def Hello(self):
        print('hello')
 
print(dir(LowercaseClass))
 
LowercaseClass().hello()

python中类方法、类实例方法、静态方法有何区别?

类方法: 是类对象的方法,在定义时需要在上方使用 @classmethod 进行装饰,形参为cls,表示类对象,类对象和实例对象都可调用
类实例方法: 是类实例化对象的方法,只有实例对象可以调用,形参为self,指代对象本身;
静态方法: 是一个任意函数,在其上方使用 @staticmethod 进行装饰,可以用对象直接调用,静态方法实际上跟该类没有太大关系

6. python函数重载机制

函数重载主要是为了解决两个问题。 1。可变参数类型。 2。可变参数个数。

另外,一个基本的设计原则是,仅仅当两个函数除了参数类型和参数个数不同以外,其功能是完全相同的,此时才使用函数重载,如果两个函数的功能其实不同,那么不应当使用重载,而应当使用一个名字不同的函数。

好吧,那么对于情况 1 ,函数功能相同,但是参数类型不同,python 如何处理?答案是根本不需要处理,因为 python 可以接受任何类型的参数,如果函数的功能相同,那么不同的参数类型在 python 中很可能是相同的代码,没有必要做成两个不同函数。

那么对于情况 2 ,函数功能相同,但参数个数不同,python 如何处理?大家知道,答案就是缺省参数。对那些缺少的参数设定为缺省参数即可解决问题。因为你假设函数功能相同,那么那些缺少的参数终归是需要用的。 好了,鉴于情况 1 跟 情况 2 都有了解决方案,python 自然就不需要函数重载了。

好啦,今天先分享到这,我们下集继续!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值