【Python】【元编程】【二】【描述符】

"""

#描述符实例是托管类的类属性;此外,托管类还有自己实例的同名属性

#20.1.1 LineItem类第三版:一个简单的描述符#栗子20-1 dulkfood_v3.py 使用 Quantity 描述符管理 LineItem 的属性class Quantity:# 描述符基于协议实现,无需创建子类。    def __init__(self,storage_name):        self.storage_name = storage_name    def __set__(self, instance, value):  # instance是托管类实例,不用self是为了不和描述符实例冲突        if value > 0 :            instance.__dict__[self.storage_name] = value   #这里,必须直接处理托管实例的 __dict__ 属性;如果使用内置的 setattr 函数,会再次触发 __set__ 方法,导致无限递归。        else:            raise ValueError(‘value must be > 0‘)class LineItem:    weight = Quantity(‘weight‘)    price = Quantity(‘price‘)    def __init__(self,description,weight,price):        self.description = description        self.weight = weight        self.price = price    def subtotal(self):        return self.weight * self.price

truffle = LineItem(‘White truffle‘,100,0)  #ValueError: value must be > 0

#20.1.2 LineItem第四版:自动获取储存属性的名称#栗子20-2 bulkfood_v4.py:每个 Quantity 描述符都有独一无二的 storage_name‘‘‘为了生成 storage_name,我们以 ‘_Quantity#‘ 为前缀,然后在后面拼接一个整数:Quantity.__counter 类属性的当前值,每次把一个新的 Quantity 描述符实例依附到类上,都会递增这个值。在前缀中使用井号能避免 storage_name 与用户使用点号创建的属性冲突,因为 nutmeg._Quantity#0 是无效的 Python 句法。但是,内置的 getattr和 setattr 函数可以使用这种“无效的”标识符获取和设置属性,此外也可以直接处理实例属性 __dict__‘‘‘class Quantity:    __counter = 0

def __init__(self):        cls = self.__class__        prefix = cls.__name__        index = cls.__counter        self.storage_name = ‘_{}#{}‘.format(prefix,index)  #        cls.__counter += 1    def __get__(self, instance, owner):        return getattr(instance,self.storage_name) #这里可以使用内置的高阶函数 getattr 和 setattr 存取值,无需使用instance.__dict__,因为托管属性和储存属性的名称不同,所以把储存属性传给getattr 函数不会触发描述符,不会像示例 20-1 那样出现无限递归    def __set__(self, instance, value):        if value > 0 :            setattr(instance,self.storage_name,value)        else:            raise ValueError(‘value must be > 0‘)

class LineItem:    weight = Quantity()    price = Quantity()    def __init__(self,description,weight,price):        self.description = description        self.weight = weight        self.price = price    def subtotal(self):        return self.weight * self.price

cocounts = LineItem(‘Brazilian cocount‘,20,17.95)print(getattr(cocounts,‘_Quantity#0‘),getattr(cocounts,‘_Quantity#1‘))  #20 17.95print(getattr(cocounts,‘weight‘),getattr(cocounts,‘price‘))   #20 17.95print(cocounts.weight,cocounts.price)  #20 17.95#print(cocounts._Quantity#0)   #SyntaxError: unexpected EOF while parsing‘‘‘, __get__ 方法有三个参数: self、 instance 和 owner。 owner 参数是托管类(如LineItem)的引用,通过描述符从托管类中获取属性时用得到。如果使用LineItem.weight 从类中获取托管属性(以 weight 为例),描述符的 __get__ 方法接本文档由Linux公社 www.linuxidc.com 整理收到的 instance 参数值是 None。因此,下述控制台会话才会抛出 AttributeError 异常抛出 AttributeError 异常是实现 __get__ 方法的方式之一,如果选择这么做,应该修改错误消息,去掉令人困惑的 NoneType 和 _Quantity#0,这是实现细节。把错误消息改成"‘LineItem‘ class has no such attribute" 更好。最好能给出缺少的属性名,但是在这个示例中,描述符不知道托管属性的名称,因此目前只能做到这样‘‘‘#print(LineItem.weight)  #AttributeError: ‘NoneType‘ object has no attribute ‘_Quantity#0‘

#示例 20-3 bulkfood_v4b.py(只列出部分代码):通过托管类调用时, __get__ 方法返回描述符的引用class Quantity:    __counter = 0

def __init__(self):        cls = self.__class__        prefix = cls.__name__        index = cls.__counter        self.storage_name = ‘_{}#{}‘.format(prefix,index)  #        cls.__counter += 1    def __get__(self, instance, owner):        if instance is None:            return self  #如果不是通过实例调用,返回描述符自身        else:            return getattr(instance, self.storage_name)         #这里可以使用内置的高阶函数 getattr 和 setattr 存取值,无需使用instance.__dict__,因为托管属性和储存属性的名称不同,所以把储存属性传给getattr 函数不会触发描述符,不会像示例 20-1 那样出现无限递归    def __set__(self, instance, value):        if value > 0 :            setattr(instance,self.storage_name,value)        else:            raise ValueError(‘value must be > 0‘)

class LineItem:    weight = Quantity()    price = Quantity()    def __init__(self,description,weight,price):        self.description = description        self.weight = weight        self.price = price    def subtotal(self):        return self.weight * self.price

print(LineItem.weight)  #<__main__.Quantity object at 0x0000000001ECA710>br_nuts = LineItem(‘Brazil nuts‘,10,34.95)print(br_nuts.price)  #34.95

‘‘‘特性工厂函数与描述符类比较特性工厂函数若想实现示例 20-2 中增强的描述符类并不难,只需在示例 19-24 的基础上添加几行代码。 __counter 变量的实现方式是个难点,不过我们可以把它定义本文档由Linux公社 www.linuxidc.com 整理成工厂函数对象的属性,以便在多次调用之间持续存在,如示例 20-5 所示。示例 20-5 bulkfood_v4prop.py:使用特性工厂函数实现与示例 20-2 中的描述符类相同的功能def quantity(): ?try:quantity.counter += 1 ?except AttributeError:quantity.counter = 0 ?storage_name = ‘_{}:{}‘.format(‘quantity‘, quantity.counter) ?def qty_getter(instance): ?return getattr(instance, storage_name)def qty_setter(instance, value):if value > 0:setattr(instance, storage_name, value)else:raise ValueError(‘value must be > 0‘)return property(qty_getter, qty_setter)? 没有 storage_name 参数。? 不能依靠类属性在多次调用之间共享 counter,因此把它定义为 quantity 函数自身的属性。? 如果 quantity.counter 属性未定义,把值设为 0。? 我们也没有实例变量,因此创建一个局部变量 storage_name,借助闭包保持它的值,供后面的 qty_getter 和 qty_setter 函数使用。? 余下的代码与示例 19-24 一样,不过这里可以使用内置的 getattr 和 setattr 函数,而不用处理 instance.__dict__ 属性。那么,你喜欢哪个?示例 20-2 还是示例 20-5 ?我喜欢描述符类那种方式,主要有下列两个原因。描述符类可以使用子类扩展;若想重用工厂函数中的代码,除了复制粘贴,很难有其他方法。与示例 20-5 中使用函数属性和闭包保持状态相比,在类属性和实例属性中保持状态更易于理解。此外,解说示例 20-5 时,我没有画机器和小怪兽的动力。特性工厂函数的代码不依赖奇怪的对象关系,而描述符的方法中有名为 self 和 instance 的参数,表明里面涉及奇怪的对象关系。本文档由Linux公社 www.linuxidc.com 整理总之,从某种程度上来讲,特性工厂函数模式较简单,可是描述符类方式更易扩展,而且应用也更广泛。‘‘‘

#20.1.3 LineItem类第5版:一种新型描述符  [避免商品信息为空,导致无法下单]#几个描述符类的层次结构。 AutoStorage 基类负责自动存储属性; Validated 类做验证,把职责委托给抽象方法 validate; Quantity 和NonBlank 是 Validated 的具体子类

import abc

class AutoStorage:    __counter = 0

def __init__(self):        cls = self.__class__        prefix = cls.__name__        index = cls.__counter        self.storage_name = ‘_{}#{}‘.format(prefix,index)        cls.__counter += 1

def __get__(self, instance, owner):        if instance is None:            return self        else:            return getattr(instance,self.storage_name)

def __set__(self, instance, value):        setattr(instance,self.storage_name,value)

class Validated(abc.ABC,AutoStorage):    def __set__(self, instance, value):        value = self.validate(instance,value)        super().__set__(instance,value)

@abc.abstractmethod    def validate(self,instance,value):        ‘‘‘return validated value or raise ValueError‘‘‘

class Quantity(Validated):    ‘‘‘a number greater than zero‘‘‘    def validate(self,instance,value):        if value <= 0:            raise ValueError(‘value must be > 0‘)        return value

class NonBlank(Validated):    ‘‘‘a string with at least one non-space character‘‘‘

def validate(self,instance,value):        value = value.strip()        if len(value) == 0:            raise ValueError(‘value cannot be empty or blank‘)        return value

class LineItem:    description = NonBlank()    weight = Quantity()    price = Quantity()

def __init__(self,description,weight,price):        self.description = description        self.weight = weight        self.price = price

def subtotal(self):        return self.weight * self.price

#20.2 覆盖型与非覆盖型描述符对比‘‘‘#Python 贡献者和作者讨论这些概念时会使用不同的术语。覆盖型描述符也叫数据描述符或强制描述符。非覆盖型描述符也叫非数据描述符或遮盖型描述符#依附在类上的描述符无法控制为类属性赋值的操作。其实,这意味着为类属性赋值能覆盖描述符属性‘‘‘

### 辅助函数,仅用于显示 ###def cls_name(obj_or_cls):    cls = type(obj_or_cls)    if cls is type:        cls = obj_or_cls    return cls.__name__.split(‘.‘)[-1]

def display(obj):    cls = type(obj)    if cls is type:        return ‘<class {}>‘.format(obj.__name__)    elif cls in [type(None), int]:        return repr(obj)    else:        return ‘<{} object>‘.format(cls_name(obj))

def print_args(name, *args):    pseudo_args = ‘, ‘.join(display(x) for x in args)    print(‘-> {}.__{}__({})‘.format(cls_name(args[0]), name, pseudo_args))

### 对这个示例重要的类 ###class Overriding:    ‘‘‘也称数据描述符或强制描述符‘‘‘

def __get__(self, instance, owner):        print_args(‘get‘, self, instance, owner)

def __set__(self, instance, value):        print_args(‘set‘, self, instance, value)

class OverridingNoGet:    ‘‘‘没有``__get__``方法的覆盖型描述符‘‘‘

def __set__(self, instance, value):        print_args(‘set‘, self, instance, value)

class NonOverriding:    ‘‘‘也称非数据描述符或遮盖型描述符‘‘‘

def __get__(self, instance, owner):        print_args(‘get‘, self, instance, owner)

class Managed:    over = Overriding()    over_no_get = OverridingNoGet()    non_over = NonOverriding()

def spam(self):        print(‘-> Managed.spam({})‘.format(display(self)))#覆盖型描述符obj = Managed()print(obj.over)   #-> Overriding.__get__(<Overriding object>, <Managed object>, <class Managed>)print(Managed.over) #-> Overriding.__get__(<Overriding object>, None, <class Managed>)  【解析】因为没有实例obj.over = 7   #-> Overriding.__set__(<Overriding object>, <Managed object>, 7)print(obj.over) #-> Overriding.__get__(<Overriding object>, <Managed object>, <class Managed>)obj.__dict__[‘over‘] = 8  #跳过描述符,直接通过 obj.__dict__ 属性设值,所以不打印任何内容print(vars(obj))  #{‘over‘: 8}  【解析】确认值在 obj.__dict__ 属性中,在 over 键名下print(obj.over)  #-> Overriding.__get__(<Overriding object>, <Managed object>, <class Managed>) 【解析】然而,即使是名为 over 的实例属性, Managed.over 描述符仍会覆盖读取 obj.over这个操作

#没有 __get__ 方法的覆盖型描述符print(obj.over_no_get) #<__main__.OverridingNoGet object at 0x000000000385F860> 【解析】这个覆盖型描述符没有 __get__ 方法,因此, obj.over_no_get 从类中获取描述符实例print(Managed.over_no_get)   #<__main__.OverridingNoGet object at 0x0000000001EE3A58>    【解析】直接从托管类中读取描述符实例也是如此obj.over_no_get = 7   #-> OverridingNoGet.__set__(<OverridingNoGet object>, <Managed object>, 7)print(obj.over_no_get) #<__main__.OverridingNoGet object at 0x0000000002203A58> 【解析】因为 __set__ 方法没有修改属性,所以在此读取 obj.over_no_get 获取的仍是托管类中的描述符实例obj.__dict__[‘over_no_get‘] = 9print(obj.over_no_get)  #9 【解析】现在, over_no_get 实例属性会遮盖描述符,但是只有读操作是如此obj.over_no_get = 7  #-> OverridingNoGet.__set__(<OverridingNoGet object>, <Managed object>, 7)print(obj.over_no_get)  #9 【解析】但是读取时,只要有同名的实例属性,描述符就会被遮盖

# 非覆盖型描述符obj = Managed()print(obj.non_over)  #-> NonOverriding.__get__(<NonOverriding object>, <Managed object>, <class Managed>)obj.non_over = 7print(obj.non_over)  #7print(Managed.non_over)  #-> NonOverriding.__get__(<NonOverriding object>, None, <class Managed>)del obj.non_overprint(obj.non_over)  #-> NonOverriding.__get__(<NonOverriding object>, <Managed object>, <class Managed>)

#通过类可以覆盖任何描述符obj = Managed()Managed.over = 1Managed.over_no_get = 2Managed.non_over = 3print(Managed.over,Managed.over_no_get,Managed.non_over) #1 2 3  【解析】揭示了读写属性的另一种不对等:读类属性的操作可以由依附在托管类上定义有 __get__ 方法的描述符处理,但是写类属性的操作不会由依附在托管类上定义有__set__ 方法的描述符处理                                                         #...若想控制设置类属性的操作,要把描述符依附在类的类上,即依附在元类上。默认情况下,对用户定义的类来说,其元类是 type,而我们不能为 type 添加属性。不过在第 21 章,我们会自己创建元类

#方法是描述符#方法是非覆盖性描述符obj = Managed()print(obj.spam)  #<bound method Managed.spam of <__main__.Managed object at 0x000000000385F860>>  【解析】 obj.spam 获取的是绑定方法对象print(Managed.spam) #<function Managed.spam at 0x00000000038D7B70>  【解析】但是 Managed.spam 获取的是函数obj.spam = 7print(obj.spam)  #7  【解析】 函数没有实现 __set__ 方法,因此是非覆盖型描述符‘‘‘obj.spam 和 Managed.spam 获取的是不同的对象。与描述符一样,通过托管类访问时,函数的 __get__ 方法会返回自身的引用。但是,通过实例访问时,函数的 __get__ 方法返回的是绑定方法对象:一种可调用的对象,里面包装着函数,并把托管实例(例如 obj)绑定给函数的第一个参数(即self),这与 functools.partial 函数的行为一致‘‘‘

#20.3 方法是描述符

import collectionsclass Text(collections.UserString):    def __str__(self):        return ‘Text({!r})‘.format(self.data)

def reverse(self):        return self[::-1]

word = Text(‘forward‘)print(word)  #Text(‘forward‘)print(word.reverse())  #Text(‘drawrof‘)print(Text.reverse(word))#Text(‘drawrof‘) 【解析】在类上调用方法相当于调用函数print(type(Text.reverse),type(word.reverse)) #<class ‘function‘> <class ‘method‘>print(list(map(Text.reverse,[‘repaid‘,(10,20,30),Text(‘stressed‘)])))  #[‘diaper‘, (30, 20, 10), ‘desserts‘]print(Text.reverse.__get__(word))  #<bound method Text.reverse of ‘forward‘>  【解析】 函数都是非覆盖型描述符。在函数上调用 __get__ 方法时传入实例,得到的是绑定到那个实例上的方法print(Text.reverse.__get__(None,word)) #<function Text.reverse at 0x0000000001E8CD08> 【解析】调用函数的 __get__ 方法时,如果 instance 参数的值是 None,那么得到的是函数本身。print(word.reverse)  #<bound method Text.reverse of ‘forward‘>print(word.reverse.__self__)  #Text(‘forward‘)print(word.reverse.__func__ is Text.reverse)  #True‘‘‘绑定方法的 __func__ 属性是依附在托管类上那个原始函数的引用。绑定方法对象还有个 __call__ 方法,用于处理真正的调用过程。这个方法会调用__func__ 属性引用的原始函数,把函数的第一个参数设为绑定方法的 __self__ 属性。这就是形参 self 的隐式绑定方式。函数会变成绑定方法,这是 Python 语言底层使用描述符的最好例证。‘‘‘

#20.4 描述符用法建议‘‘‘下面根据刚刚论述的描述符特征给出一些实用的结论。使用特性以保持简单  内置的 property 类创建的其实是覆盖型描述符, __set__ 方法和 __get__ 方法都实现了,即便不定义设值方法也是如此。特性的 __set__ 方法默认抛出AttributeError: can‘t set attribute,因此创建只读属性最简单的方式是使用特性,这能避免下一条所述的问题。只读描述符必须有 __set__ 方法  如果使用描述符类实现只读属性,要记住, __get__ 和 __set__ 两个方法必须都定义,否则,实例的同名属性会遮盖描述符。只读属性的 __set__ 方法只需抛出AttributeError 异常,并提供合适的错误消息。Python 为此类异常提供的错误消息不一致。如果试图修改 complex 的 c.real 属性,那么得到的错误消息是AttributeError: read-only attribute;但是,如果试图修改 c.conjugat(e complex 对象的方法),那么得到的错误消息是 AttributeError: ‘complex‘ object attribute ‘conjugate‘ is read-only。用于验证的描述符可以只有 __set__ 方法  对仅用于验证的描述符来说, __set__ 方法应该检查 value 参数获得的值,如果有效,使用描述符实例的名称为键,直接在实例的 __dict__ 属性中设置。这样,从实例中读取同名属性的速度很快,因为不用经过 __get__ 方法处理。参见示例 20-1 中的代码。仅有 __get__ 方法的描述符可以实现高效缓存  如果只编写了 __get__ 方法,那么创建的是非覆盖型描述符。这种描述符可用于执行某些耗费资源的计算,然后为实例设置同名属性,缓存结果。同名实例属性会遮盖描述符,因此后续访问会直接从实例的 __dict__ 属性中获取值,而不会再触发描述符的__get__ 方法。非特殊的方法可以被实例属性遮盖  由于函数和方法只实现了 __get__ 方法,它们不会处理同名实例属性的赋值操作。因此,像 my_obj.the_method = 7 这样简单赋值之后,后续通过该实例访问the_method 得到的是数字 7——但是不影响类或其他实例。然而,特殊方法不受这个问题的影响。解释器只会在类中寻找特殊的方法,也就是说, repr(x) 执行的其实是x.__class__.__repr__(x),因此 x 的 __repr__ 属性对 repr(x) 方法调用没有影响。出于同样的原因,实例的 __getattr__ 属性不会破坏常规的属性访问规则。实例的非特殊方法可以被轻松地覆盖,这听起来不可靠且容易出错,可是在我使用 Python的 15 年中从未受此困扰。然而,如果要创建大量动态属性,属性名称从不受自己控制的数据中获取(像本章前面那样),那么你应该知道这种行为;或许你还可以实现某种机制,过滤或转义动态属性的名称,以维持数据的健全性。 示例 19-6 中的 FrozenJSON 类不会出现实例属性遮盖方法的问题,因为那个类只有几个特殊方法和一个 build 类方法。只要通过类访问,类方法就是安全的,在示例 19-6 中我就是这么调用 FrozenJSON.build 方法的——在示例 19-7 中替换成__new__ 方法了。 Record 类(见示例 19-9 和示例 19-11)及其子类也是安全的,因为只用到了特殊的方法、类方法、静态方法和特性。特性是数据描述符,因此不能被实例属性覆盖。讨论特性时讲了两个功能,这里讨论的描述符还未涉及,结束本章之前我们来讲讲:文档和对删除托管属性的处理‘‘‘

"""
时间: 2024-10-08 13:04:35

【Python】【元编程】【二】【描述符】的相关文章

python高级编程之描述符与属性02

# -*- coding: utf-8 -*- # python:2.x __author__ = 'Administrator' #元描述符 #特点是:使用宿主类的一个或者多个方法来执行一个任务,可能对降低使用提供步骤的类所需要的代码量很有用,比如一个链式描述符可以调用类的一系统方法以返回一组结果,它可以在失败的时候停止,并且配备一个回调机制以获得对过程更多控制,如下 class Chainer(object): def __init__(self,a,b=NotImplementedErro

python高级编程之描述符与属性03

# -*- coding: utf-8 -*- # python:2.x __author__ = 'Administrator' #属性Property #提供了一个内建描述符类型,它知道如何将一个特性链接到一组方法上,采用fget参数和3个可选的参数-fset,fdel,doc最后一个参数可以提供用来定义一个后链接到特性的docstring,就像个方法,如下 class MyClass(object): def __init__(self): self._my_seceret_ting=1

Python元编程

简单定义"元编程是一种编写计算机程序的技术,这些程序可以将自己看做数据,因此你可以在运行时对它进行内审.生成和/或修改",本博参考<<Python高级编程>>将对元编程内容进行详细描述,若有不正确之处希望大家指出. 1. 概述 Python元编程有两种方法,一是采用类似"装饰器"的工具对基本元素(例如函数.类.类型)内审和对其进行实时创建和修改,二是运用类型"元类"的方式对类实例的创建过程进行修改,甚至于允许重新设计Pyt

python元编程之使用动态属性实现定制类--特殊方法__setattr__,__getattribute__篇

问题:实现一个类,要求行为如同namedtuple:只存在给定名称的属性,不允许动态添加实例属性. 主要知识点在于: __setattr__,__getattr__,getattribute__,__delattr__特殊方法的实现使用. 代码如下: 1 """ 2 运行环境 3 python 3.7+ 4 """ 5 from collections OrderedDict, namedtuple 6 #以下为要包装的对象:1个命名元组,用于存

Python魔法方法之描述符

1 class myDesc(object): 2 def __init__(self,value=None): 3 self.value = float(value) 4 5 def __get__(self,instance,owner): 6 print "__get__: instance is % owner is %" % (instance,opwner) 7 return self.value 8 9 def __set__(self,instance,value):

换个角度理解python元编程

元编程这个概念本身不新,只是没有发现原来很早就在用这个东西,所以python等下再聊,先理一理怎么理解编程这个事情.我仔细思考,其实是在做一件设计想法,纪录想法,实现想法的过程.怎么样设计想法?应该需要一些图形,文字通过一定格式纪录下来,反复修改,最好是有一个规范或者工具让其他人也能明白和理解,方便交流.这个问题在编程这里也就是进入到编程语言的选择上面来,也可以自己制定一个规范,不管你用lex+yacc语法制导还是设计LLVM的AST,反正结果是要一种编程语言来设计你的想法.选定了设计想法的规范

Python 为什么要使用描述符?

学习 Python 这么久了,说起 Python 的优雅之处,能让我脱口而出的, Descriptor(描述符)特性可以排得上号. 描述符 是Python 语言独有的特性,它不仅在应用层使用,在语言的基础设施中也有涉及. 我可以大胆地猜测,你对于描述符的了解是始于诸如 Django ORM 和 SQLAlchemy 中的字段对象,是的,它们都是描述符.你的它的认识,可能也止步于此,如果你没有去深究,它为何要如此设计?也就加体会不到 Python 给我们带来的便利与优雅. 由于 描述符的内容较多,

Linux系统编程--文件描述符的复制dup()和dup2()【转】

本文转载自:http://blog.csdn.net/tennysonsky/article/details/45870459 dup() 和 dup2() 是两个非常有用的系统调用,都是用来复制一个文件的描述符,使新的文件描述符也标识旧的文件描述符所标识的文件. 这个过程类似于现实生活中的配钥匙,钥匙相当于文件描述符,锁相当于文件,本来一个钥匙开一把锁,相当于,一个文件描述符对应一个文件,现在,我们去配钥匙,通过旧的钥匙复制了一把新的钥匙,这样的话,旧的钥匙和新的钥匙都能开启这把锁.对比于 d

c++模板元编程二:用enum做数值计算

2.1 用enum做数值计算 下面两篇文章都介绍了模板元编程,enum是其最重要的基本工具 http://www.codeproject.com/Articles/3743/A-gentle-introduction-to-Template-Metaprogramming https://www10.informatik.uni-erlangen.de/~pflaum/pflaum/ProSeminar/meta-art.html 因此可以得道以下结论: enum的值由编译器在编译期计算 利用模

Linux系统编程——文件描述符的复制:dup()和dup2()

dup() 和 dup2() 是两个非常有用的系统调用,都是用来复制一个文件的描述符,使新的文件描述符也标识旧的文件描述符所标识的文件. 这个过程类似于现实生活中的配钥匙,钥匙相当于文件描述符,锁相当于文件,本来一个钥匙开一把锁,相当于,一个文件描述符对应一个文件,现在,我们去配钥匙,通过旧的钥匙复制了一把新的钥匙,这样的话,旧的钥匙和新的钥匙都能开启这把锁.对比于 dup(), dup2() 也一样,通过原来的文件描述符复制出一个新的文件描述符,这样的话,原来的文件描述符和新的文件描述符都指向