python语法总结下载_Python常用语法总结

本文详细介绍了Python中的对象模型,包括类、类型对象和实例对象的概念,强调了函数作为对象的特性,如通过lambda定义函数和在函数中使用map()、filter()和reduce()。此外,还探讨了类方法、静态方法和实例方法的使用,以及属性可见度和描述符。最后,提到了描述符的使用以及继承、作用域、闭包和装饰器的概念,以及列表推导式、生成器和迭代器在Python编程中的应用。
摘要由CSDN通过智能技术生成

1. Python对象模型

Python中一切皆为对象。

对象拥有三个特性:id、类型和值。

类把数据与功能绑定在一起。创建新类就是创建新的对象类型,从而创建该类型的新实例。

类型对象与实例化对象

Python中的类型也是一种对象 ,称为类型对象。

>>> int

>>> type(int)

>>> type(type)

type对象是所有类型对象的元类。

通过类型对象实例化的对象称为实例化对象:

>>> int('1')

1

>>> type(1)

object对象是所有对象的基类:

>>> issubclass(int,object)

True

>>> type(object)

>>> issubclass(type, object)

True

函数对象

Python中函数也是对象,称为函数对象:

def func(x, y):

return x + y

print(type(func))

>>>

我们也可以使用lambda定义函数:

func = lambda x,y: x + y

print func(3,4)

函数可以作为一个对象,进行参数传递:

def print_func_output(f, x, y):

print(f(x, y))

func = lambda x,y: x + y

f = func

print_func_output(f, 1, 2)

>>>

3

Python中所提供的map(), filter(), reduce()等函数的第一个参数都是一个函数对象:

# map()将第一个参数中的函数应用于列表的每一个元素,返回一个迭代器

[2, 4, 6]

>>> list(map((lambda x: x * 2), [1, 2, 3]))

# filter()将第一个参数中的函数应用于列表的每一个元素,过滤掉函数结果为False的元素,返回一个迭代器

>>> list(filter((lambda x: x % 2 == 0), [1, 2, 3, 4, 5]))

[2, 4]

# reduce累进地将第一个参数中的函数作用于列表的每一个元素。

>>> from functools import reduce

>>> reduce((lambda x, y: x + y), [1, 2, 3])

6

2. 类型对象与实例对象

类型对象

每个自定义类型对象都有一个属性空间,信息存储于 dict 中:

class Person:

num = 0

def __init__(self, name, age):

self.name = name

self.age = age

def introduce(self):

print('My name is ' + self.name)

@classmethod

def get_num(cls):

return cls.num

@staticmethod

def say_hello():

print('Hello')

for k, v in Person.__dict__.items():

print(f'{k}:{v}')

>>>

__module__:__main__

num:0

__init__:

introduce:

get_num:

say_hello:

__dict__:

__weakref__:

__doc__:None

由此可见, 类的属性空间中存放了类变量,静态方法,类方法,实例方法和一些内置属性。这些方法都是以一个普通的函数对象方式保存在类的属性空间中。

静态方法其实就是普通的函数,只是在类的定义体中,而不是模块层定义。它的第一个参数不是特殊的值,不能访问实例变量,可以通过类名访问类变量。可以通过类名的方式调用静态方法:

Person.say_hello()

>>>

Hello

类方法只能访问类变量,不能访问实例变量。它的第一个参数是类本身。但在调用类方法时不需要为该参数传递值:

print(Person.get_num())

>>>

0

实例方法可以访问实例变量,它的第一个参数是当前对象。我们可以通过对象名调用实例方法,也可以通过类来调用,但需要显式传递 self 参数:

p = Person('Alice', '20')

Person.introduce(p)

p.introduce()

>>>

My name is Alice

My name is Alice

实例对象

实例对象的属性信息也存储于 dict 中:

p = Person('Alice', '20')

for k, v in p.__dict__.items():

print(f'{k}:{v}')

>>>

name:Alice

age:20

通过实例对象访问属性时,默认先在实例对象的属性空间字典中查找;再到类型对象的属性空间查找:

class Person:

name = 'person'

num = 0

def __init__(self, name):

self.name = name

p = Person('Alice')

print(p.name)

print(p.num)

>>>

Alice

0

属性和方法可见度

Python的属性/方法可见度只有public和private两种。

在定义属性或方法时,在属性/方法名前面加了2个下划线'__'。则表明该属性/方法是私有属性/方法。不能在对象上通过 . 操作符直接访问:

class Person:

def __init__(self, name, age):

self.name = name

self.__age = age

def __get_age():

return self.__age()

p = Person('Alice', '20')

p.__age

p.__get_age()

>>>

AttributeError: 'Person' object has no attribute '__age'

AttributeError: 'Person' object has no attribute '__get_age'

但子类之所以无法直接访问私有属性/方法,是因为私有属性/方法名被更换成了 _类名__属性名,与我们所访问的属性/方法名不同所致。Python并未从语法上严格保证其私有性:

print(p.__dict__)

print(p._Person__age)

print(p._Person__get_age())

>>>

{'name': 'Alice', '_Person__age': '20'}

20

20

因此Python中也可以用单下划线_开头的字段,通过约定表示不希望类的用户直接访问该属性。

@property和setter方法

通过@property和setter方法可以在对属性的访问和设置时设置自定义的行为:

class Person:

def __init__(self, name, age, height):

self.name = name

self.age = age

self.height = height

@property

def age(self):

return self.__age

@age.setter

def age(self, value):

if value > 0:

self.__age = value

else:

raise ValueError('Age must > 0')

p = Person('Alice', 20, 160)

print(p.age)

p.age = -10

>>>

Querying age

20

ValueError: Age must > 0

@property和setter方法的一个缺点是不能复用,例如我们想对height属性实现同样的方法,则需要再创建两个方法。描述符可以解决这个问题。

描述符

描述符是实现了描述符协议的类。描述符协议方法有:

__get__(),在设置属性时调用该方法;

__set__() ,在读取属性时调用该方法;

__delete__() ,在删除属性时调用该方法;

类里实现了上述其中一个方法,就称该类为描述符。使用描述符能够对多个属性运用相同的自定义存取逻辑:

class PositiveNumber: # 描述符类

def __init__(self, attr_name):

self.attr_name = attr_name # attr_name为托管实例中存储值的属性的名称。

def __set__(self, instance, value): # instance是托管实例,self是描述符实例。

if value > 0:

instance.__dict__[self.attr_name] = value # 此处必须直接存入__dict__,否则会调用setattr函数会导致无限递归。

else:

raise ValueError(f'{self.attr_name} must > 0')

def __get__(self, instance, owner): # instance是托管实例,owner是托管类

print(f'Querying {self.attr_name}')

return instance.__dict__[self.attr_name]

class Person: # 托管类

age = PositiveNumber('age')

height = PositiveNumber('height')

def __init__(self, name, age, height):

self.name = name

self.age = age

self.height = height

p = Person('Alice', 20, 160)

print(p.age)

p.height = -10

>>>

Querying age

20

ValueError: age must > 0

如果一个类同时定义了__get__方法和__set__方法,则称之为数据描述符,如果只有__get__方法,则称之为非数据描述符。

每次使用类名.属性名,或者 getattr(类名,属性名)的调用方式访问属性时,属性查找优先级顺序为:数据描述符 > 实例的__dict__ > 非数据描述符 > 类的__dict__

__getattribut__, __setattr__和__getattr__,

程序每次访问属性时都会调用__getattribut__。每次设置属性时都会调用__setattr__。

如果类定义了__getattr__,在查找不到该属性时,将会调用这个方法:

class Person:

def __init__(self, name, age):

self.name = name

self.age = age

def __getattr__(self, item):

print(f'Person do not have field {item}')

p = Person('Alice', 20)

p.height

>>>

Person do not have attr height

继承

加入继承关系后的完整属性查找优先级顺序为:

数据描述符 > 实例的__dict__ > 非数据描述符 > 类的__dict__ > 父类 - >父类的父类 ->Object->调用类的__getattr__->若仍不存在,会引发一个 AttributeError 异常

一个问题

class Parent(object):

x = 1

class Child1(Parent):

pass

class Child2(Parent):

pass

print Parent.x, Child1.x, Child2.x

Child1.x = 2

print Parent.x, Child1.x, Child2.x

Parent.x = 3

print Parent.x, Child1.x, Child2.x

>>>

1 1 1

1 2 1

3 2 3

这个答案的关键是,在 Python 中,类变量在内部是作为字典处理的。如果一个变量的名字没有在当前类的字典中发现,将搜索祖先类(比如父类)直到被引用的变量名被找到。

因此,在父类中设置 x = 1 会使得类变量 X 在引用该类和其任何子类中的值为 1。这就是因为第一个 print 语句的输出是1 1 1。

随后,如果任何它的子类重写了该值(例如,我们执行语句 Child1.x = 2),然后,该值仅仅在子类中被改变。这就是为什么第二个 print 语句的输出是 1 2 1

最后,如果该值在父类中被改变(例如,我们执行语句 Parent.x = 3),这个改变会影响到任何未重写该值的子类当中的值(在这个示例中被影响的子类是 Child2)。这就是为什么第三个 print 输出是 3 2 3。

3. 函数:作用域,闭包,装饰器

作用域

Python作用域分为:

local,局部作用域,即当前函数作用域

enclosing,嵌套的父级函数的局部作用域,即包含此函数的上级函数的局部作用域

global,全局作用域,即代码所在模块的作用域

built-in,内置作用域,系统固定模块的作用域

在表达式中引用变量时,Python解释器会按照上述顺序遍历各作用域以解释该引用,如果都没有定义过名称相符的变量,将抛出NameError异常。

闭包

闭包是一种定义在某个作用域中的函数,这种函数引用了那个作用域里面的变量:

def func_outer(prefix):

def func_inner(name):

return prefix + ' ' + name

return func_inner

func_inner = func_outer('Hello')

print(func_inner('A'))

>>>

Hello A

nonlocal关键字可以让相关变量赋值时从local作用域延伸到enclosing作用域中查找该变量:

def func_outer(prefix):

count = 0

def func_inner(name):

nonlocal count

count += 1

return '{} {}, id {}'.format(prefix, name, count)

return func_inner

func_inner = func_outer('Hello')

print(func_inner('A'))

print(func_inner('B'))

>>>

Hello A, id 1

Hello B, id 2

global关键字 可以让相关变量赋值时从enclosing作用域延伸到global作用域中查找该变量:

a = 1

def func():

global a

a = 2

func()

print(a)

>>>

2

装饰器

装饰器一般用来修饰函数。能够在执行受到封装的原函数执行之前和执行完毕后运行一些附加代码,实现公共功能,达到代码复用的目的:

# 定义一个装饰器

def trace(func):

def wrapper(*args, **kwargs):

res = func(*args, **kwargs)

print(f'function: {func.__name__}, parameter: {args},{kwargs}, result: {res}')

return res

return wrapper

# 使用@修饰函数,其效果等于以该函数为参数调用修饰器,

# 再把修饰器所返回的结果赋给同一个作用域中与原函数同名的变量,

# 即: fibonacci = trace(fibonacci)

@trace

def fibonacci(n):

if n in (0, 1):

return n

return fibonacci(n - 2) + fibonacci(n - 1)

>>>

function: fibonacci, parameter: (1,),{}, result: 1

function: fibonacci, parameter: (0,),{}, result: 0

function: fibonacci, parameter: (1,),{}, result: 1

function: fibonacci, parameter: (2,),{}, result: 1

function: fibonacci, parameter: (3,),{}, result: 2

4. 列表推导式,生成器,迭代器

列表推导式

列表推导式用于生成一个list

>>> ['x' for n in range(5)]

['x', 'x', 'x', 'x', 'x']

>>> a=[1,2,3,4,5]

>>> [x for x in a if x % 2 == 0]

[2, 4]

列表推导式在推导过程中,对于输入序列中的每个值可能都要创建仅含一项元素的全新列表。输入数据非常多时,会消耗大量内存。

生成器,迭代器

将列表推导式所用的方括号变成圆括号,就构成了生成器。它们区别在于,生成器表达式运行时不会把整个输出序列呈现出来,而是返回一个迭代器,这个迭代器每次根据生成器表达式产生一项数据。

>>> a=[1,2,3,4,5]

>>> it = (x for x in a if x % 2 == 0)

>>> next(it)

2

>>> next(it)

4

>>> next(it)

Traceback (most recent call last):

File "", line 1, in

StopIteration

enumerate可用将各种迭代器(也包括序列和支持迭代的对象)包装为生成器:

a = [1, 2, 3, 4, 5]

it = (x for x in a if x % 2 == 0)

for index, value in enumerate(it):

print('{}:{}'.format(index, value))

for index, value in enumerate(it):

print('{}:{}'.format(index, value))

>>>

0:2

1:4

需注意,迭代器只能产生一轮结果,继续执行第二轮将不会输出,也不会报错

zip函数可用于同时把两个或两个以上的迭代器封装为生成器,将值汇聚成元组(tuple)。当其中一个遍历结束时,zip将不再产生元组:

a = [1, 2, 3, 4, 5]

it1 = (x for x in a if x % 2 == 0)

it2 = (x for x in a if x % 2 != 0)

for v1, v2 in zip(it1, it2):

print('{},{}'.format(v1, v2))

>>>

2,1

4,3

使用yield关键字的函数也称为生成器。每次在返回的迭代器上调用next函数时,迭代器会把生成器推进到下一个yield关键字处:

def func():

for i in range(1000):

yield [i] * 1000

gen = func()

for item in gen:

print(item)

5. 并发与并行

6. 模块加载

import关键字用于导入模块

引入模块时,Python会按照深度优先的顺序执行下列操作:

1)在由sys.path所指定的路径中,搜寻待引入的模块。

2)从模块中加载代码,并保证这段代码能够正确编译。

3)创建与该模块对应的空对象。

4)把这个空的模块对象,添加到sys.modules里

5)运行模块对象中的代码,以定义其内容。

需注意循环依赖的问题。解决方法包括:1)调整引入顺序 2)动态引入,即在函数或方法内部使用import,会等到真正运行相关代码时,才触发模块的引入操作。但多次调用会带来更多的import开销

7. 内存管理

引用计数

Python使用引用计数 ,为每个对象维护引用次数,并据此回收不再需要的垃圾对象。当引用次数变为 0 时就将资源释放:

sys.getrefcount可输出变量的引用计数。

>>> import sys

>>> s = '123'

>>> sys.getrefcount(s)

2

>>> n1 = s

>>> sys.getrefcount(s)

3

>>> l = [s]

>>> sys.getrefcount(s)

4

>>> del n1

>>> sys.getrefcount(s)

3

>>> del l

>>> sys.getrefcount(s)

2

当一个对象作为参数传个函数后,它的引用计数将加一;当函数返回,局部名字空间销毁后,对象引用计数又减一。

标记清除法

Python还采用 标记清除法 来回收存在循环引用的垃圾对象。

将程序内部对象跟踪起来,是实现垃圾回收的第一步。一个对象是否需要跟踪,取决于它会不会形成循环引用。按照引用特征,Python 对象可以分为两类:

1)内向型对象 ,例如 int 、float 、 str 等,这类对象不会引用其他对象,因此无法形成循环引用,无须跟踪;

2)外向型对象 ,例如 tuple 、 list 、 dict 等容器对象,以及函数、类实例等复杂对象,这类对象一般都会引用其他对象,存在形成循环引用的风险,因此是垃圾回收算法关注的重点;

Python 为外向型对象分配内存时,对象头部之前预留了一些内存空间,以便垃圾回收模块用链表将它们跟踪起来。

标记清除法首先找出 根对象 ( root object )集合。所谓根对象,就是指被全局引用或者在栈中引用的对象,这部对象是不能被删除的。根对象集合不难确定:我们只需遍历每个对象引用的对象,将它们的引用计数减一,最后计数不为零的就是根对象。

根对象本身是 可达的 ( reachable ),不能删除;被根对象引用的对象也是可达的,同样不能删除;以此类推。沿着引用关系遍历,遍历到的所有对象都是可达的,不能删除。 不可达 ( unreachable )的垃圾对象,就可以被安全回收。

分代回收机制

如果每次执行标记清除法时,都需要遍历所有对象,会影响程序性能。为了提高垃圾回收效率,Python 还引入了分代回收机制 对象分为若干“代”( generation ),每次只处理某个代中的对象,因此 GC 卡顿时间更短。

一个对象存活的时间越长,它下一刻被释放的概率就越低,可以适当降低回收频率。因此,Python根据对象的存活时间进行分代,分为:初生代、中生代和老生代

回收策略:

1)每新增 701 个需要 GC 的对象,触发一次新生代 GC ;

2)每执行 11 次新生代 GC ,触发一次中生代 GC ;

3)每执行 11 次中生代 GC ,触发一次老生代 GC (老生代 GC 还受其他策略影响,频率更低);

4)执行某个生代 GC 前,年轻生代对象链表也移入该代,一起 GC ;

5)一个对象创建后,随着时间推移将被逐步移入老生代,回收频率逐渐降低;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值