我眼中一个好的Pythoneer应该具备的品质(二)

知道python中的几种字符串拼接方式与效率对比。

  • 使用+拼接
  • 使用%拼接
  • 使用join
  • 使用f''f-string方式
    f-string方式出自PEP 498(Literal String Interpolation,字面字符串插值),从Python3.6版本引入。其特点是在字符串前加 f 标识,字符串中间则用花括号{}包裹其它字符串变量。 这种方式在可读性上秒杀format()方式,处理长字符串的拼接时,速度与join()方法相当。

知道鸭子类型(duck typing)的含义与其在python中的表现形式。

如果一个生物走起来像鸭子,叫起来像鸭子,他就是鸭子。对于Python编程来说,解释器不管你这个对象是什么类型,只管这个对象有没有对应的方法和属性。所以你希望他是什么类型,你就调用什么方法就行了,如果类型错误解释器会告诉你,你所调用的方法不存在。而可以使用"dir(obj)"这样的命令查看这个对象有什么方法和属性,当然也可以通过"type(obj)"来查看这个对象当前的类型。

知道函数和方法的区别,知道绑定方法(bound-method)与未绑定方法(unbound-method)的关系。

bound和unbound方法是个很简单的概念。在许多语言当中,类似于a.b()这样的调用方法是一个整体,但在Python中,它其实是两部分:获取属性a.b,调用()。所以也可以写成:

c = a.b
c()

跟直接调用a.b()是等效的。当a是某个类的实例,b是这个类的方法的时候,a.b的返回值就是bound method,也就是绑定方法。它实际上是个bound method对象,这个对象提前将self参数进行了绑定。实际演示一下就很容易懂了:

>>> class A(object):
...     def b(self):
...         pass
...
>>> a = A()
>>> a.b
>
>>> A.b

>>>

相应的unbound method是没有绑定self的对象。在Python 3中,它就是普通的函数,在Python 2中则是unbound method类型,不过区别不大。

我们知道像A.b这样的方法实际上跟一个普通定义的函数没有本质区别,这个函数有一个参数self,所以实际上完全可以用A.b(a)的方式来调用,也就是手工将self参数指定为a。这也就是unbound method的用法。

而相应的,bound method是一个实现了call的对象,它自动将调用这个对象的过程重定向到A.b(a)上面,相当于通过functools.partial绑定了第一个参数的效果,所以叫做bound method。

知道asyncio的使用方式和使用场景。

协程

知道StringIO和BytesIO的用途。

StringIO 写

>>> from io import StringIO
>>> f = StringIO()
>>> f.write('hello')
5
>>> f.write(' ')
1
>>> f.write('world!')
6
>>> print(f.getvalue())
hello world!

StringIO 读

>>> from io import StringIO
>>> f = StringIO('Hello!\nHi!\nGoodbye!')
>>> while True:
...     s = f.readline()
...     if s == '':
...         break
...     print(s.strip())
...
Hello!
Hi!
Goodbye!

BytesIO 写

>>> from io import StringIO
>>> f = StringIO('Hello!\nHi!\nGoodbye!')
>>> while True:
...     s = f.readline()
...     if s == '':
...         break
...     print(s.strip())
...
Hello!
Hi!
Goodbye!

BytesIO 读

>>> from io import BytesIO
>>> f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
>>> f.read()
b'\xe4\xb8\xad\xe6\x96\x87'

作用在于,提供给某些只支持文件的api,比如gzip

知道以单下划线开头、双下划线开头和双下划线包围的变量分别代表着什么含义。

  • 在python中单下划线代表私有,但也仅仅是名义上的私有,只是一种规范,告诉人们不要在外部使用它。但实际上python没有真正意义上的私有,我们一样可以在外部去调用私有方法或属性。
  • 双下划线使用来避免父类方法被子类方法覆盖的。双下划线方法的本质是在方法前加了类名,我们可以使用对象.类名__方法名(),来在外部调用它。
  • 前后双下滑线方法是python自己定义出来,供自己调用的。这些方法会在特定的条件下被触发执行。比如 __init__

知道__init__和__new__方法在class和type中分别的作用是什么。

__init__ 是初始化实例的方法,__new__是创建实例的方法,先执行__new__再执行__init__ 。

知道类变量和实例变量的区别。

  • 类变量
    类变量指的是在类中,但在各个类方法外定义的变量。举个例子:
class CLanguage :
    # 下面定义了2个类变量
    name = "C语言中文网"
    add = "http://c.biancheng.net"
    # 下面定义了一个say实例方法
    def say(self, content):
        print(content)
  • 实例变量
    实例变量指的是在任意类方法内部,以“self.变量名”的方式定义的变量,其特点是只作用于调用方法的对象。另外,实例变量只能通过对象名访问,无法通过类名访问。
class CLanguage :
    def __init__(self):
        self.name = "C语言中文网"
        self.add = "http://c.biancheng.net"
    # 下面定义了一个say实例方法
    def say(self):
        self.catalog = 13

知道dict在类中的含义,以及类属性和方法与dict的关系。

见__slots__

知道Mixin模式以及在python中的用途。

多继承

知道python中生成器的实现以及其使用场景。

首先有两个概念,生成器函数生成器表达式
生成器的思想是使用的时候再实时计算出来,优点在于能节约内存开销。以计算时间换内存空间

生成器函数

求N以内的偶数
一般的写法 先算出100以内的偶数,放到nums里,然后遍历出来

def even_number(N):
    nums = []
    for i in range(N):
        if not (i%2):
            nums.append(i)
    return nums
nums = even_number(100)
for i in nums:
    print(i)

生成器写法

def even_number(N):
    for i in range(N):
        if not (i%2):
            yield i
nums = even_number(100)
for i in nums:
    print(i)

注意观察even_number没有return 多了个yield
yield表示返回一个值之后 挂起该函数,等待下一次执行。

知道python中抽象类的实现方式,以及其抽象基类模块,知道如何用python类实现一个抽象容器类型。

一、

继承有两种用途:

"""
 一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)
 二:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)
 且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
 三、接口隔离原则:使用多个专门的接口,而不使用单一的总接口。即客户端不应该依赖那些不需要的接口
"""
"""
接口类:基于同一个接口实现的类  刚好满足接口隔离原则 面向对象开发的思想 规范
接口类,python 原生不支持  在python中,并没有接口类这种东西,即便不通过专门的模块定义接口,我们也应该有一些基本的概念
"""

一、接口类单继承

我们来看一段代码去了解为什么需要接口类

class Alipay:
    def pay(self,money):
        print('支付宝支付了')
class Apppay:
    def pay(self,money):
        print('苹果支付了')
class Weicht:
    def pay(self,money):
        print('微信支付了')
def pay(payment,money):       # 支付函数,总体负责支付,对应支付的对象和要支付的金额
    payment.pay(money)
p=Alipay()
pay(p,200)      #支付宝支付了

这段代码,实现了一个有趣的功能,就是通过一个总体的支付函数,实现了不同种类的支付方式,不同是支付方式作为对象,传入函数中
但是开发中容易出现一些问题,那就是类中的函数名不一致,就会导致调用的时候找不到类中对应方法,例题如下:

class Alipay:
    def paying(self,money):    #这里类的方法可能由于程序员的疏忽,写的不是一致的pay,导致后面调用的时候找不到pay
        print('支付宝支付了')
class Apppay:
    def pay(self,money):
        print('苹果支付了')
class Weicht:
    def pay(self,money):
        print('微信支付了')
def pay(payment,money):       # 支付函数,总体负责支付,对应支付的对象和要支付的金额
    payment.pay(money)
p=Alipay()   #不报错
pay(p,200)      #调用执行就会报错,'Alipay' object has no attribute 'pay'

这时候怎么办呢?可以手动抛异常:NotImplementedError来解决开发中遇到的问题

class payment:
    def pay(self):
        raise NotImplementedError    #手动抛异常
class Alipay:
    def paying(self, money):  # 这里类的方法不是一致的pay,导致后面调用的时候找不到pay
        print('支付宝支付了')
def pay(payment, money):  # 支付函数,总体负责支付,对应支付的对象和要支付的金额
    payment.pay(money)

p = Alipay()  # 不报错
pay(p, 200)  #调用的时候才会报错  'Alipay' object has no attribute 'pay'

也可以借用abc模块来处理这种错误

from abc import abstractmethod, ABCMeta     #接口类中定义了一些接口名:Pay,且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
class Payment(metaclass=ABCMeta):    #抽象出的共同功能Pay
    @abstractmethod
    def pay(self,money):pass    #这里面的pay 来源于下面类中的方法pay,意思把这个方法规范为统一的标准,另外建一个规范类Payment
class Alipay(Payment):
    def paying(self, money):    #这里出现paying和我们规范的pay不一样,那么在实例化 Alipay的时候就会报错
        print('支付宝支付了')
class Weicht(Payment):
    def pay(self,money):
        print('微信支付了')
def pay(pay_obj,money):
    pay_obj.pay(money)
p=Alipay()   #实例化的时候就会报错  Can't instantiate abstract class Alipay with abstract methods pay 之前两个例子都是在执行的时候报错,这里不一样的是实例化就会知道是哪里发生错误了
"""
总结:用abc模块装饰后,在实例化的时候就会报错,那么当我们代码很长的时候,就可以早一点预知错误,所以以后在接口类类似问题中用这个模块
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,
可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
"""

二、接口类多继承

from abc import abstractmethod,ABCMeta
class Walk_animal(meteaclass=ABCMeta):
    @abstractmethod
    def walk(self):
        print('walk')
class Swim_animal(meteaclass=ABCMeta):
    @abstractmethod
    def swim(self):pass
class Fly_animal(metaclass=ABCMeta)
    @abstractmethod
    def fly(self):pass
#如果正常一个老虎有跑和跑的方法的话,我们会这么做
class Tiger:
    def walk(self):pass
    def swim(self):pass
#但是我们使用接口类多继承的话就简单多了,并且规范了相同功能
class Tiger(Walk_animal,Swim_animal):pass
#如果此时再有一个天鹅swan,会飞,走,游泳 那么我们这么做
class Swan(Walk_animal,Swim_animal, Fly_animal):pass
# 这就是接口多继承

为什么需要接口类

三、抽象类

#抽象类
# 抽象类的本质还是类,
# 指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性
"""
1.抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
2.在继承抽象类的过程中,我们应该尽量避免多继承;
3.而在继承接口的时候,我们反而鼓励你来多继承接口
# 一般情况下 单继承 能实现的功能都是一样的,所以在父类中可以有一些简单的基础实现
# 多继承的情况 由于功能比较复杂,所以不容易抽象出相同的功能的具体实现写在父类中

"""

为什么要有抽象类

从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。

从实现角度来看,抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的

#一切皆文件
import abc #利用abc模块实现抽象类

class All_file(metaclass=abc.ABCMeta):
    all_type='file'
    @abc.abstractmethod #定义抽象方法,无需实现功能
    def read(self):
        '子类必须定义读功能'
        pass

    @abc.abstractmethod #定义抽象方法,无需实现功能
    def write(self):
        '子类必须定义写功能'
        pass

# class Txt(All_file):
#     pass
#
# t1=Txt() #报错,子类没有定义抽象方法

class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('文本数据的读取方法')

    def write(self):
        print('文本数据的读取方法')

class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('硬盘数据的读取方法')

    def write(self):
        print('硬盘数据的读取方法')

class Process(All_file): #子类继承抽象类,但是必须定义read和write方法
    def read(self):
        print('进程数据的读取方法')

    def write(self):
        print('进程数据的读取方法')

wenbenwenjian=Txt()

yingpanwenjian=Sata()

jinchengwenjian=Process()

#这样大家都是被归一化了,也就是一切皆文件的思想
wenbenwenjian.read()
yingpanwenjian.write()
jinchengwenjian.read()

print(wenbenwenjian.all_type)
print(yingpanwenjian.all_type)
print(jinchengwenjian.all_type)

四、扩展:

不管是抽象类还是接口类 : 面向对象的开发规范 所有的接口类和抽象类都不能实例化
java :
java里的所有类的继承都是单继承,所以抽象类完美的解决了单继承需求中的规范问题
但对于多继承的需求,由于java本身语法的不支持,所以创建了接口Interface这个概念来解决多继承的规范问题
python:
python中没有接口类  :
   python中自带多继承 所以我们直接用class来实现了接口类
python中支持抽象类  : 一般情况下 单继承  不能实例化
   且可以实现python代码

五、注意

"""
1.多继承问题
在继承抽象类的过程中,我们应该尽量避免多继承;
而在继承接口的时候,我们反而鼓励你来多继承接口

2.方法的实现
在抽象类中,我们可以对一些抽象方法做出基础实现;
而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现
"""

知道普通方法,classmethod和staticmethod的区别。

Python面向对象编程中,类中定义的方法可以是 @classmethod 装饰的类方法,也可以是 @staticmethod 装饰的静态方法,用的最多的还是不带装饰器的实例方法,如果把这几个方法放一块,对初学者来说无疑是一头雾水,那我们该如何正确地使用它们呢?

先来看一个简单示例:

class A(object):
    def m1(self, n):
        print("self:", self)

    @classmethod
    def m2(cls, n):
        print("cls:", cls)

    @staticmethod
    def m3(n):
        pass

a = A()
a.m1(1) # self: <__main__.A object at 0x000001E596E41A90>
A.m2(1) # cls: 
A.m3(1)

我在类中一共定义了3个方法,m1 是实例方法,第一个参数必须是 self(约定俗成的)。m2 是类方法,第一个参数必须是cls(同样是约定俗成),m3 是静态方法,参数根据业务需求定,可有可无。当程序运行时,大概发生了这么几件事(结合下面的图来看)。

  • 第一步:代码从第一行开始执行 class 命令,此时会创建一个类 A 对象(没错,类也是对象,一切皆对象嘛)同时初始化类里面的属性和方法,记住,此刻实例对象还没创建出来。
  • 第二、三步:接着执行 a=A(),系统自动调用类的构造器,构造出实例对象 a
  • 第四步:接着调用 a.m1(1) ,m1 是实例方法,内部会自动把实例对象传递给 self 参数进行绑定,也就是说, self 和 a 指向的都是同一个实例对象。
  • 第五步:调用A.m2(1)时,python内部隐式地把类对象传递给 cls 参数,cls 和 A 都指向类对象。

严格意义上来说,左边的都是变量名,是对象的引用,右边才是真正的对像,为了描述方便,我直接把 a 称为对象,你应该明白我说对象其实是它所引用右边的那个真正的对象。

再来看看每个方法各有什么特性

实例方法

print(A.m1)
# A.m1在py2中显示为


print(a.m1)
>

A.m1是一个还没有绑定实例对象的方法,对于未绑定方法,调用 A.m1 时必须显示地传入一个实例对象进去,而 a.m1是已经绑定了实例的方法,python隐式地把对象传递给了self参数,所以不再手动传递参数,这是调用实例方法的过程。

A.m1(a, 1)
# 等价  
a.m1(1)

如果未绑定的方法 A.m1 不传实例对象给 self 时,就会报参数缺失错误,在 py3 与 py2 中,两者报的错误不一致,python2 要求第一个参数self是实例对象,而python3中可以是任意对象。

A.m1(1)
TypeError: m1() missing 1 required positional argument: 'n'

类方法

print(A.m2)
>

print(a.m2)
>

m2是类方法,不管是 A.m2 还是 a.m2,都是已经自动绑定了类对象A的方法,对于后者,因为python可以通过实例对象a找到它所属的类是A,找到A之后自动绑定到 cls。

A.m2(1) 
 # 等价
 a.m2(1)

这使得我们可以在实例方法中通过使用 self.m2()这种方式来调用类方法和静态方法。

def m1(self, n):
    print("self:", self)
    self.m2(n)

静态方法

print(A.m3)


print(a.m3)

m3是类里面的一个静态方法,跟普通函数没什么区别,与类和实例都没有所谓的绑定关系,它只不过是碰巧存在类中的一个函数而已。不论是通过类还是实例都可以引用该方法。

A.m3(1) 
 # 等价
 a.m3(1)

以上就是几个方法的基本介绍。现在把几个基本的概念理清楚了,那么现在来说说几个方法之间的使用场景以及他们之间的优缺点。

应用场景

静态方法的使用场景:

如果在方法中不需要访问任何实例方法和属性,纯粹地通过传入参数并返回数据的功能性方法,那么它就适合用静态方法来定义,它节省了实例化对象的开销成本,往往这种方法放在类外面的模块层作为一个函数存在也是没问题的,而放在类中,仅为这个类服务。例如下面是微信公众号开发中验证微信签名的一个例子,它没有引用任何类或者实例相关的属性和方法。

from hashlib import sha1
import tornado.web

class SignatureHandler(tornado.web.RequestHandler):
    def get(self):
        """
         根据签名判断请求是否来自微信
        """
        signature = self.get_query_argument("signature", None)
        echostr = self.get_query_argument("echostr", None)
        timestamp = self.get_query_argument("timestamp", None)
        nonce = self.get_query_argument("nonce", None)
        if self._check_sign(TOKEN, timestamp, nonce, signature):
            logger.info("微信签名校验成功")
            self.write(echostr)
        else:
            self.write("你不是微信发过来的请求")

    @staticmethod
    def _check_sign(token, timestamp, nonce, signature):
        sign = [token, timestamp, nonce]
        sign.sort()
        sign = "".join(sign)
        sign = sha1(sign).hexdigest()
        return sign == signature

类方法的使用场景有:

作为工厂方法创建实例对象,例如内置模块 datetime.date 类中就有大量使用类方法作为工厂方法,以此来创建date对象。

class date:

    def __new__(cls, year, month=None, day=None):
        self = object.__new__(cls)
        self._year = year
        self._month = month
        self._day = day
        return self

    @classmethod
    def fromtimestamp(cls, t):
        y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t)
        return cls(y, m, d)

    @classmethod
    def today(cls):
        t = _time.time()
        return cls.fromtimestamp(t)

如果希望在方法裡面调用静态类,那么把方法定义成类方法是合适的,因为要是定义成静态方法,那么你就要显示地引用类A,这对继承来说可不是一件好事情。

class A:

    @staticmethod
    def m1()
        pass

    @staticmethod
    def m2():
        A.m1() # bad

    @classmethod
    def m3(cls):
        cls.m1() # good

知道python中==与is的区别。

== 是判断值是否相等 is 是判断指针是否是同一个变量

知道装饰器中添加functools.wraps的含义与作用。

functools.wraps 旨在消除装饰器对原函数造成的影响,即对原函数的相关属性进行拷贝,已达到装饰器不修改原函数的目的。

知道getattrgetattribute的作用以及其顺序关系。

在阅读很多优秀的python框架代码时,getattr(), __getattr__(), __getattribute__()和__get__()这几个方法都是很常见的,它们都是在什么时候 被调用呢,用处又是什么,然后它们之前有哪些关联呢。下面来通过例子分析一下。getattr()和另外三个方法都是魔法函数不同的是,getattr()是python内置的一个函数,它可以用来获取对象的属性和方法。例子如下:

class A():
    a = 5
    def __init__(self, x):
        self.x = x

    def hello(self):
        return 'hello func'

a = A(10)

print(getattr(a, 'x'))  #相当于a.x
print(getattr(a, 'y', 20))  #相当于a.y,因为a.y并不存在,所以返回第三个参数作为默认值
print(getattr(a, 'hello')())  # 相当于a.hello()

print(getattr(A, 'a'))  # 相当于A.a这段代码的输出结果是:10
20
hello func
5

可以看出,getattr()可以用来获取对像的属性和方法,需要注意的是,如果通过getattr()来尝试获取对象里并不存在的属性时没有添加第三个默认值,代码会 报错,如下所示:

print(getattr(a, 'y'))

运行会报异常提示找不到属于y:

Traceback (most recent call last):
  File "app/test.py", line 32, in 
    print(getattr(a, 'y'))
AttributeError: 'A' object has no attribute 'y'

__getattr()与__getattribute()这两个是类对象的魔法函数,在访问对象属性的时候会被调用,但是两者之间也有一点区别, 我们通过代码来看一下:

class A(object):
  def __init__(self, x):
    self.x = x

  def hello(self):
    return 'hello func'

  def __getattr__(self, item):
    print('in __getattr__')
    return 100

  def __getattribute__(self, item):
    print('in __getattribute__')
    return super(A, self).__getattribute__(item)

a = A(10)
print(a.x)
print(a.y)运行代码,得到下面输出:in __getattribute__
10
in __getattribute__
in __getattr__
100

可以看出,在获到对象属性时,__getattribute__()是一定会被调用的,无论属性存不存在,首先都会调用这个魔法方法。 如果调用像a.y这种不存在的对象时,调用__getattribute__()找不到y这个属性,就会再调用__getattr__()这个魔法方法,

可以通过在这个方法里实 来设置属性不存在时的默认值。使用上面的getattr()方法获取属性时,也是同样的调用关系,只不过只有在getattr()带第三个参数作为默认值时,才会调用 __getattr__()方法。

__get__()
__get__()方法是描述符方法之一,和他经常配套使用的是__set__()方法,通过描述符,可以将访问对象属性转变为调用描述符方法。这在ORM中被经常使用, 可以通过描述符方法进行参数格式验证。

import random

class Die(object):
    def __init__(self, sides=6):
        self.sides = sides

    def __get__(self, instance, owner):
        print('Die __get__()')
        return int(random.random() * self.sides) + 1

    def __set__(self, instance, value):
        print('Die __set__()')

class Game(object):
    d6 = Die()
    d10 = Die(sides=10)
    d20 = Die(sides=20)

game = Game()
print(game.d6)

game.d6 = 10

这段代码的输出结果是:

Die __get__()
5
Die __set__()

这就是描述符的作用, 使用描述符可以让我们在获取或者给对象赋值时对数据值进行一些特殊的加工和处理。

python里经常使用的@property装饰器其实就是 通过描述符的方式实现的。 当然关于描述符,我们还需要知道,

如果一个类仅仅实现了__get__()方法,那么这个类被称为非数据描述符;如果一个类实现在__get__()并且还实现在 __set__()和__del__()中的一个,这个类就被称为数据描述符。

知道python中自省的使用方式,知道inspect库的常见用法。

我们用自省最重要的几个目的就是,让python回答我们:对象名称是什么?对象能做什么?对象是什么类型?对象的一些基础信息是什么?好了,废话不多说,看代码就知道我指的函数是什么函数。代码:

s_obj = "hello world"

# 检查对象类型
print(type(s_obj))

# 检查对象ID
print(id(s_obj))

# 检查对象是否包含某个属性
print(hasattr(s_obj, '__doc__'))
print(hasattr(dir, '__doc__'))

# 获取某个对象的属性值
print(getattr(s_obj, '__doc__'))

type和id函数之前都有用过就不讲了,hasattr是用来检测对象是否包含某个属性的,如果是就返回True否则返回False。getattr是用来获取属性值的。python的自省远远不止这些,比如前面章节讲到的成员运算符isinstance()也是一个python自省的函数。不仅如此,还可以检测某个对象是否属于某个类、或者某个类是否是别的类的字类等等。这些会在我后面讲述自定义类的时候讲到。

inspect:

1.对类,模块的操作,成员,类,模块类型的判断

2.获取源码

3.获取类或函数的参数信息

4.解析堆栈

知道python中弱引用的使用方式,知道python中gc的回收算法方式以及回收规则。

和许多其它的高级语言一样,Python使用了垃圾回收器来自动销毁那些不再使用的对象。每个对象都有一个引用计数,当这个引用计数为0时Python能够安全地销毁这个对象。

引用计数会记录给定对象的引用个数,并在引用个数为零时收集该对象。由于一次仅能有一个对象被回收,引用计数无法回收循环引用的对象。

一组相互引用的对象若没有被其它对象直接引用,并且不可访问,则会永久存活下来。一个应用程序如果持续地产生这种不可访问的对象群组,就会发生内存泄漏。

在对象群组内部使用弱引用(即不会在引用计数中被计数的引用)有时能避免出现引用环,因此弱引用可用于解决循环引用的问题。

在计算机程序设计中,弱引用,与强引用相对,是指不能确保其引用的对象不会被垃圾回收器回收的引用。一个对象若只被弱引用所引用,则可能在任何时刻被回收。弱引用的主要作用就是减少循环引用,减少内存中不必要的对象存在的数量。

使用weakref模块,你可以创建到对象的弱引用,Python在对象的引用计数为0或只存在对象的弱引用时将回收这个对象。

创建弱引用
你可以通过调用weakref模块的ref(obj[,callback])来创建一个弱引用,obj是你想弱引用的对象,callback是一个可选的函数,当因没有引用导致Python要销毁这个对象时调用。回调函数callback要求单个参数(弱引用的对象)。

一旦你有了一个对象的弱引用,你就能通过调用弱引用来获取被弱引用的对象。

>>>> import sys
>>> import weakref
>>> class Man:
  def __init__(self,name):
    print self.name = name
    
>>> o = Man('Jim')
>>> sys.getrefcount(o)   
2
>>> r = weakref.ref(o) # 创建一个弱引用
>>> sys.getrefcount(o) # 引用计数并没有改变
2
>>> r
 # 弱引用所指向的对象信息
>>> o2 = r() # 获取弱引用所指向的对象
>>> o is o2
True
>>> sys.getrefcount(o)
3
>>> o = None
>>> o2 = None
>>> r # 当对象引用计数为零时,弱引用失效。
de>

上面的代码中,我们使用sys包中的getrefcount()来查看某个对象的引用计数。需要注意的是,当使用某个引用作为参数,传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,getrefcount()所得到的结果,会比期望的多1。

一旦没有了对这个对象的其它的引用,调用弱引用将返回None,因为Python已经销毁了这个对象。 注意:大部分的对象不能通过弱引用来访问。

weakref模块中的getweakrefcount(obj)和getweakrefs(obj)分别返回弱引用数和关于所给对象的引用列表。

弱引用对于创建对象(这些对象很费资源)的缓存是有用的。

知道global,local和nonlocal关键字在python中的含义和其使用场景。

默认变量都是local
global
global 把局部变量设置为全局变量

name = '张三'
def fun():
  global name
  name = '李四'
def fun2():
  print(name)
>>> fun2()
张三
>>> fun()
>>> fun2()
李四
>>> 

nonlocal
nonlocal 把局部变量,“下放”到上层空间用

name = '张二'
def fun():
 name = '张三'
 def fun2():
    def fun3():
      nonlocal name
      name = '李四'
    print(name)
    fun3()
    print(name)
  print(name)
  fun2()
  print(name)
fun()
print(name)
>>> fun()
张三
张三
李四
李四
>>> name
'张二'

知道for-else,try-else的含义和用途。

  • for-else
    for 循环里没有遇到break,自然循环结束后执行else里的语句
  • try-else
    try 里的语句正常执行,没有遇到except,正常执行完成以后执行else

知道.pyc文件的含义,清楚python代码大概的执行过程。

将.py形式的程序编译成中间式文件(byte-compiled)的.pyc文件,这么做的目的就是为了加快下次执行文件的速度。

运行python文件的时候,就会自动首先查看是否具有.pyc文件,如果有的话,而且.py文件的修改时间和.pyc的修改时间一样,就会读取.pyc文件,否则,Python就会读原来的.py文件。

执行过程

  • 完成模块的加载和链接;
  • 将源代码翻译为PyCodeObject对象(这货就是字节码),并将其写入内存当中(方便CPU读取,起到加速程序运行的作用);
  • 从上述内存空间中读取指令并执行;
  • 程序结束后,根据命令行调用情况(即运行程序的方式)决定是否将PyCodeObject写回硬盘当中(也就是直接复制到.pyc或.pyo文件中);
  • 之后若再次执行该脚本,则先检查本地是否有上述字节码文件。有则执行,否则重复上述步骤。

知道python中常见的魔术方法和其使用方式。

魔法方法就是可以给你的类增加魔力的特殊方法,如果你的对象实现了这些方法中的某一个,那么这个方法就会在特殊的情况下被 Python 所调用,你可以定义自己想要的行为,而这一切都是自动发生的。

它们经常是两个下划线包围来命名的(比如 init/new等等),Python的魔法方法是非常强大的。如果你学习过Java,那你会发现Python中的魔法方法像是Java中的重载,Python中的魔法方法可以理解为:对类中的内置方法的重载,注意这里不是重写。

举个例子,Python中有个比较操作符==用来比较两个变量的大小,而这个操作符是通过内置函数eq来实现的,所以我们只需要通过改变这个内置函数代码,就可以改变重新定义这个操作符的行为。

我们定义一个类Word,继承自str类,现需要重新定义该类的操作符==,使这个操作符用来判断两个字符串长度是否相等,而不是通过字母顺序判断两个字符串是否相等。注意该变化只适用于Word类,而不适用于其它类。

class Word(str):
    def __eq__(self, other):
        return len(self) == len(other)
a = Word('asd')
b = Word('ips')
print(a == b)

python中常用魔术方法有


image.png

你可能感兴趣的:(我眼中一个好的Pythoneer应该具备的品质(二))