魔術方法 反射

魔術方法 反射

反射(reflection):指的是運行時獲取類型定義信息。一個對象能夠在運行時像照鏡子一樣反射出其類型信息;也就是說能夠通過一個對象,找到自己的type、class、attribute、或method的能力,稱為反射或者自省。 具有反射能力的函數:type、isinstance、callable、dir、getattr。 運行時和編譯時不同,指的是程序倍加在到內存中執行的時候。

反射相關的函數和方法

內建函數 意義
getattr(object,name[,default]) 通過name返回object的屬性值。當屬性不存在將使用default返回,如果沒有default,則返回異常。name必須使字符串
setattr(object,name,value) object的屬性存在則覆蓋,反之就增加
hasattr(object,name) 判斷對象是否有這個名字的屬性,name必須是字符串
class Point:
    def __init__(self,x,y):
        self.x = x
        self.y = y

    def __repr__(self):
        return 'Point({},{})'.format(self.x,self.y)

    def show(self):
        print(self)

p1 = Point(5,9)
p2 = Point(7,3)
print(repr(p1),repr(p2),sep ='\n')
print(p1.__dict__)
setattr(p1,'y',16)
setattr(p1,'z',10)
print(getattr(p1,'__dict__'))
#動態調用方法
if hasattr(p1,'show'):
    getattr(p1,'show')()
#動態增加方法,為類增加方法
if not hasattr(Point,'add'):
    setattr(Point,'add',lambda self,other: Point(self.x+other.x,self.y+other.y))

print(Point.add)
print(p1.add)
print(p1.add(p2))
#為實例增加方法,未綁定
if not hasattr(p1,'sub'):
    setattr(p1,'sub',lambda self,other: Point(self.x-other.x,self.y-other.y))
print(p1.sub(p1,p2))
print(p1.sub)
print(p1.__dict__)
print(Point.__dict__)

這種動態增加屬性的方式使運行時改變類或者實例的方式,但是裝飾器或Mixin都是定義時就決定了,一次反射能力具有更大的靈活性。 上面代碼中通過dict訪問對象的屬性,本質上也是利用的反射的能力。

  • 練習 運用對象方式來實現分發器:
class Dispather:
    def __init__(self):
        self._run()

    def cmd1(self):
        print("I'm a command")

    def cmd2(self):
        print("I'm command2")
    def _run(self):
        while True:
            cmd = input('enter a command: ')
            if cmd.strip() == 'quit':
                break
            getattr(self,cmd,lambda :print('Unknown this command {}'.format(cmd)))()
Dispather()

反射相關的魔術方法

  1. __getatte__()
class Base:
    n = 0

class Point(Base):
    z = 6
    def __init__(self,x,y):
        self.x = x
        self.y = y
    def show(self):
        print(self.x,self.y)
        
    def __getattr__(self, item):
        return "misssing {}".format(item)
p1 = Point(7,9)
print(p1.x)
print(p1.z,p1.n)
print(p1.t)

一個累的屬性會按照集成關系查找,如果找不到就會執行魔法方法getattr,如果沒有這個方法就會返回AttributeError異常,表示找不到屬性 查找順序:對象字典 ——> 類字典 ——> 類祖先的字典 ——>調用getattr

  1. __setattr__()
class Base:
    n = 0


class Point(Base):
    z = 6

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def show(self):
        print(self.x, self.y)

    def __getattr__(self, item):
        return "misssing {}".format(item)

    def __setattr__(self, key, value):
         print('setattr {} = {}'.format(key,value))


p1 = Point(7, 9)
print(p1.x) #missing,實例通過“.”設置屬性,如同self.x = x,就會調用setattr,屬性要加到實例的字典中就要自己完成
print(p1.z, p1.n)
print(p1.t)
p1.x =50
print(p1.__dict__)
p1.__dict__['x'] = 60
print(p1.x)
print(p1.__dict__)

魔法方法setattr可以攔截對實例屬性的增加修改操作如果要設置生效,需要自己操作實例的字典。

  1. __delattr__()
class Point:
    Z = 99
    def __init__(self,x,y):
        self.x = x
        self.y = y
    def __delattr__(self, item):
        print('can not del {}'.format(item))
p = Point(14,5)
del p.x
p.z =15
del p.z
del p.Z
print(Point.__dict__)
print(p.__dict__)
del Point.Z
print(Point.__dict__)

可以阻止通過實例刪除屬性的操作。但是通過類異??梢詣h除屬性。

  1. __getattribute__
class Base:
    n=84
class Point(Base):
    z = 99
    def __init__(self,x,y):
        self.x = x
        self.y = y
    def __getattribute__(self, item):
        return "missint {}".format(item)

    def __delattr__(self, item):
        print('can not del {}'.format(item))
p = Point(14,5)
print(p.__dict__)
print(p.x)
print(p.z)
print(p.n)
print(p.t)
print(Point.__dict__)
print(Point.z)

魔法方法getattribute中為了避免無線遞歸,他的視線應該永遠調用基類的同名方法以訪問需要的任何屬性,例如object.getattribute(self,name). 注意:除非明確知道getattribute用來做什么,否則不要使用

總結:

魔法方法 意義
__getattrbute__() 實例所有的屬性調用都從這個方法開始
__getattr__() 當通過搜索實例、實例的類及祖先類查不到屬性,就會調用此方法
__setattr__() 通過“.”訪問實例屬性,進行增加、修改都要調用它
__delattr__() 當通過實例來刪除屬性時調用此方法

屬性查找順序 實例調用getattribute ——> 實例字典 ——> 類的字典 ——> 祖先類的字典 ——> 調用getattr

描述器Desciptors

描述器的表現

用到三個魔術方法:__get__、__set__、__delete__ self指代當前實例;instance時owner的實例;owner是屬性實例所屬的類。

  1. object.__get__(self,instance,owner)
  2. object.__set__(self,instance,value)
  3. object.__delete__(self,instance)
class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')
class B:
    x =A()
    def __init__(self):
        print('b.init')
print('-'*20)
print(B.x.a1)
print('========================')
b = B()
print(b.x.a1)

根據運行結果來看,類加載的時候,類變量需要先生成,而類B的屬性是類A的實例,所以先初始化類A,然后依次執行。

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

    def __get__(self, instance, owner):
        print('A.get {} {} {}'.format(self,instance,owner))

class B:
    x =A()
    def __init__(self):
        print('b.init')

print('-'*20)
print(B.x)
#print(B.x.a1)
print('========================')
b = B()
print(b.x)
#print(b.x.a1)

因為定義了__get__方法,類A就變成了描述器,對類B或者類B的實例的x屬性讀取,稱為對類A的實例的訪問,就會調用__get__方法。__get__方法的三個參數分別是:self是類A的實例,owner是類B,instance是類B的實例。根據上面的結果得到給__get__一個返回值self就可以解決報錯的問題。

描述器定義

Python中,一個類實現了__set__、__delete__、__get__三個方法1中的任意一個就是描述符。如果僅實現了__get__方法就是非數據描述器;同時實現了__get__、__set__就是數據描述符。如果一個類的類屬性設置為描述器,那么它被稱為owner屬主。

屬性的訪問順序

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

    def __get__(self, instance, owner):
        print('A.get {} {} {}'.format(self,instance,owner))
        return self

    def __set__(self, instance, value):
        print('A.set {} {} {}'.format(self,instance,value))
        self.data = value

class B:
    x =A()
    def __init__(self):
        print('b.init')
        self.x = 'b.x'


print('-'*20)
print(B.x)
print(B.x.a1)
print('========================')
b = B()
print(b.x)
print(b.x.a1)

當在類B初始化時增加x屬性后,拋出異常類型str沒有a1屬性,這是因為類B的勢力中的x屬性覆蓋了類的屬性x,我們在類A上增加set魔法方法后返回變成了a1。 總結出屬性查找順序:實例的字典優先于費數據描述器;數據描述器優先于實例的字典。 b.x = 500,這是調用數據描述器的set方法,或者調用非數據描述器的實例覆蓋。 B.x = 500,賦值即定義,這是覆蓋類屬性

本質(進階)

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

    def __get__(self, instance, owner):
        print('A.get {} {} {}'.format(self,instance,owner))
        return self

    # def __set__(self, instance, value):
    #     self.data = value

class B:
    x =A()
    def __init__(self):
        print('b.init')
        self.x = 'b.x'
        self.y = 'b.y'

b=B()
print(b.x)
print(b.y)
#print(b.x.a1)
print('字典')
print(b.__dict__)
print(B.__dict__)

根據上述代碼中禁用set方法前后字典的不同結果發現,數據描述器只是沒有把B初始化函數時的x屬性添加到實例字典中,造成了該屬性如果是數據描述器優先訪問的假象。其實屬性訪問順序從沒變過。

Python中的描述器

Python中的靜態方法和類方法都是非數據描述器的實現。因此實例可以重新定義和覆蓋方法。這允許單個實例獲取與同一類的其他勢力不同的行為。 property()函數是一個數據描述器的實現。因此實例不能覆蓋屬性的行為。

  • 練習
  1. 實現staticmathod
class StaticMethod:
    def __init__(self,fn):
        self._fn = fn

    def __get__(self, instance, owner):
        return self._fn
class A:
    @StaticMethod
    def stmd():
        print('Static method')

A.stmd()
A().stmd()
  1. 實現classmethod
from functools import partial
class ClassMethod:
    def __init__(self,fn):
        self._fn = fn

    def __get__(self, instance, owner):
        print(self,instance,owner)
        return partial(self._fn,owner)
class A:
    @ClassMethod
    def stmd(cls):
        print('Static method')

A.stmd()
A().stmd()
  1. 對實例的數據進行校驗,使用描述器實現
class Person:
    def __init__(self,name:str,age:int):
        params = ((name,str),(age,int))
        if not self.checkdata(params):
            raise TypeError
        self.name =name
        self.age =age
    def checkdata(self,params):
        for p,t in params:
            if not isinstance(p,t):
                return False
        return True
### 上述寫法代碼耦合太高,里邊有硬編碼,而且不標準,較為丑陋。使用描述器接和裝飾器讓代碼和類表現的像內置類型一樣。

import inspect

class CheckType:
    def __init__(self,name,type):
        self.name = name
        self.type = type

    def __get__(self, instance, owner):
        if instance is not None:
            return instance.__dict__[self.name]
        return self
	#此處判斷傳進的參數是否符合注解要求
    def __set__(self, instance, value):
        if not isinstance(value,self.type):
            raise TypeError
        instance.__dict__[self.name] = value

def typeassert(cls):
    params = inspect.signature(cls).parameters
    for name,param in params.items():
        if param.annotation != param.empty:
	        #相當于是cls.name = CheckType(name,param.annotation)
            setattr(cls,name,CheckType(name,param.annotation))
    return cls

@typeassert    #裝飾器是為了給Person類加兩個CheckType實例的類屬性
class Person:
    def __init__(self,name:str,age:int):
        self.name = name
        self.age = age

p = Person('jerry',20)
print(p)
print(p.__dict__)
print(Person.__dict__)

本文來自投稿,不代表Linux運維部落立場,如若轉載,請注明出處:http://www.www58058.com/89095

(0)
KX_ilKX_il
上一篇 2017-11-29
下一篇 2017-11-30

相關推薦

  • N26-第二周

    1、Linux上的文件管理類命令都有哪些,其常用的使用方法及相關示例演示;   1)alias:命令別名;     # alias ;獲取所有可用別名的定義;     # alias NAME=‘COMMAND’:定義別名;    &nbs…

    Linux干貨 2017-02-11
  • Linux常用命令

    一、Linux常用命令(總) 二、Linux常用命令(分) 1、pwd – print name of current/working directory 2、cd – change directory 3、ls – list,dieplay directory content 4、date – pr…

    Linux干貨 2016-09-19
  • Centos 系列bind搭建DNS服務加固

        在centos系列版本上運用bind搭建dns服務教程已經有很多,先感謝前人做出的貢獻,引用兩篇博文,講解的非常詳細。 地址是: 主dns搭建:http://blog.csdn.net/reblue520/article/details/52537014 從dns搭建:http://blog.csdn.net/reblue520/…

    Linux干貨 2017-04-16
  • N26-第五周

    1、顯示當前系統上root、fedora或user1用戶的默認shell;[root@localhost ~]# grep -E ‘^(root|fedora|user1)\>’ /etc/passwdroot:x:0:0:root:/root:/bin/bashfedora:x:4002:4002:Fedora Core:/h…

    Linux干貨 2017-03-13
  • date初步認識

    linux命令date的初步認識

    Linux干貨 2017-11-11
  • yum初步入門

                             yum工具是為提高RPM軟件安裝性而開發的一種軟件包管理器,是由pyt…

    Linux干貨 2015-04-01
欧美性久久久久