面向对象之方法调用和属性访问属性装饰器

类的定义: class 类名称 ():
class ClassName:
       语句块
类对象及类属性
class MyClass:           # 定义类
    """axf"""            #类注解  也是属性
    x = """xabc"""    #类属性
  
    def fo(self):            #类方法  类方法也属于类属性
        print("abcdef")
  • 类对象,类的定义执行后会生成一个类对象
  • 类的属性,类定义中的变量和类中定义的方法都是类的属性
  • 类变量,上例中x是类MyClass的变量

fo是方法method,本质上就是普通的函数对象function,它一般要求至少有一个参数。第一个形式参数可以是self(self只是个惯用标识符,可以换名字),这个参数位置就留给了self(类中所有的普通函数都应该有个self参数)。self 指代当前实例本身

实例生成 : 实例名 = 类名()

class Person:    
    def __init__(self, name, age):
        self.name = name    # 把name 赋给实例slef
        self.age = age      # 把age 赋给实例slef

tom = Person(“tom”,18) #实例化对象1
jerry = Person(“Jerry”,18) #实例化对象2
每次实例化的实例是不同的实例
Python类实例化后,会自动调用__init__方法。这个方法第一个形式参数必须留给self

__init__方法
  • MyClass()实际上调用的是__init__(self)方法,可以不定义,如果没有定义会在实例化后隐式调用。作用:对实例进行初始化
  • init()方法不能有返回值,也就是只能是return None
  • 初始化函数可以多个参数,请注意第一个位置必须是self,例如__init__(self, name, age)
tom = Person("tom",30)   # 实例化对象
tom.age   #通过对象调用类的属性 __init__方法,进行初始化age

tom.age+=1  #可以对实例对象进行属性操作,当不会改变类的属性(操作对象属性不会改变类属性)

实例变量和类变量

class Person:  
    age = 25                   #类变量(属性)
    def __init__(self, name):
        self.name = name       
        
Person.age = 26              #类变量(属性),被更改

tom.age >>>26 # 通过实例本身的属性来访问
tom.class.age >>>26 通过类属性来访问

  • 实例对象instance
  • 类实例化后一定会获得一个类的实例,就是实例对象
  • 实例变量是每一个实例自己的变量,是自己独有的;类变量是类的变量,是类的所有实例共享的属性和方法(类变量更改后,新生成的实例都会以新的变量属性生成实例)
  • 类属性保存在类的__dict__中,实例属性保存在实例的__dict__中,如果从实例访问类的属性,也可以借助__class__找到所属的类,再通过类来访问类属性

  • 总结

    • 属性是类的,也是这个类所有实例的,其实例都可以访问到;
    • 是实例的,就是这个属性是实例自己的,通过类访问不到。类变量是属于类的变量,这个类的所有实例可以共享这个变量
    • 对象(实例或类)可以动态的给自己增加一个属性(赋值即定义一个新属性)。实例.__dict__[变量名]和实例.变量名都可以访问到实例自己的属性(注意这两种访问是有本质区别的)。实例的同名变量会隐藏掉类变量,或者说是覆盖了这个类变量。但是注意类变量还在那里,并没有真正被覆盖
  • 实例属性的查找顺序

    • 指的是实例使用.点号来访问属性,会先找自己的__dict__,如果没有,然后通过属性__class__找到自己的类,再去类的__dict__中找
    • 注意:如果实例使用__dict__[变量名]访问变量,将不会按照上面的查找顺序找变量了,这是指明使用字典的key查找,不是属性查找
    • 一般来说,类变量可使用全大写来命名
特殊属性含义
__name __对象名
__class __对象的类型
__dict __对象的属性的字典
__qualname __类的限定名
class Person:    
    def normal_method(): 
        print('normal')
Person.normal_method()     # 当做普通函数调用
# Person().normal_method() #这句运行不了
print(Person.__dict__)
    
class Person:
    def  normal_method(self): 
        print('normal')

Person().normal_method() 

类方法和静态方法

__init__方法,方法本身都是类的属性,第一个参数必须是self,而self必须指向一个对象,也就是类实例化之后,由实例来调用这个方法

  • 普通函数
    Person.normal_method() 可以放在类中定义,因为这个方法只是被Person这个名词空间管理的一个普通的方法,normal_method是Person的一个属性而已。
    由于normal_method在定义的时候没有指定self,所以不能完成实例对象的绑定,不能用Person().normal_method()调用

  • 类方法

    • 在类定义中,使用@classmethod装饰器修饰的方法,用类调用时,会把类作为参数传入方法中,用实例调用,则会把实例的类抽出作为参数传入到调用的方法中

    • 必须至少有一个参数,且第一个参数留给了cls,cls指代调用者即类对象自身

    • cls这个标识符可以是任意合法名称,但是为了易读,请不要修改

    • 通过cls可以直接操作类的属性

  • 静态方法

    • 在类定义中,使用@staticmethod装饰器修饰的方法
    • 调用时,不会隐式的传入参数静态方法,只是表明这个方法属于这个名词空间。函数归在一起,方便组织管理
  • 方法的调用

    • 类几乎可以调用所有内部定义的方法,但是调用普通的方法时会报错原因是第一参数必须是类的实例。实例也几乎可以调用所有的方法,普通的函数的调用一般不可能出现,因为不允许这么定义。
class Person:   
    def method(self):
        print("{}'s method".format(self))
tom = Person()
tom.method() 
# Person.method() # 不可以,因为类调用普通方法是,需要一个类对象,即一个实例
Person.method(tom) 
tom.__class__.method(tom)
--------------------------------------------------------------------------
<__main__.Person object at 0x000001F6EE21CFD0>'s method
<__main__.Person object at 0x000001F6EE21CFD0>'s method
<__main__.Person object at 0x000001F6EE21CFD0>'s method
  • 总结:
    • 类除了普通方法都可以调用,普通方法需要对象的实例作为第一参数。例如:Person.method(Person())
    • 实例可以调用所有类中定义的方法(包括类方法、静态方法),普通方法传入实例自身(Person.method(Person())),静态方法(不需要传入参数,指定义静态函数时的参数)和类方法(自动会传入实例的类,定义类方法时的参数会自动传入cls)需要找到实例的类
class Person:

    def method(self):
        print("{}'s method".format(self))

    @classmethod
    def class_method(cls):  # cls就是类   类方法,至少有一个参数
        print('class = {0.__name__} ({0})'.format(cls))
        cls.HEIGHT = 170

    @staticmethod
    def static_methd():     #无需写入第一参数self
        print(Person.HEIGHT)

tom = Person()
tom.method()         # 普通方法        >>>  <__main__.Person object at 0x000001D3DC0885F8>'s method
tom.class_method()   #类方法           >>>  class = Person (<class '__main__.Person'>)
tom.static_methd()   #静态方法         >>>  170

访问控制

  • 使用双下划线开头的属性名,就是私有属性
class Person:    
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age        # 加双下划线后,属性变为私有的
    def growup(self, i=1):
        if i > 0 and i < 150: # 控制逻辑
            self.__age += i         # 加双下划线后,属性变为私有的
    def getage(self):
        return self.__age
    
tom = Person('tom')
# tom.age             #不能访问,语法错误
# print(tom.age)      # 因为私有属性,被保护的,不能再被访问
# print(tom.__age)    # 因为私有属性,被保护的,不能再被访问

print(tom.getage())   
print(tom.__dict__)
tom.age = 20         # 赋值即定义
print(tom.__dict__)  # 注意 实例属性字典增加了age键值对,此键值对可以更改

tom._Person__age = 100   # 赋值即定义,更改类属性    ==修改私有变量==
print(tom.age)
print(tom.getage())
----------------------------------------------------------------

18
{'name': 'tom', '_Person__age': 18}
{'name': 'tom', '_Person__age': 18, 'age': 20}
20
100
  • 类定义的时候,如果声明一个实例变量的时候,使用双下划线,Python解释器会将其改名,转换名称为_类名__变量 名的名称,所以用原来的名字访问不到了

保护变量

  • 在变量名前使用一个下划线,称为保护变量
class Person:    
    def __init__(self, name, age=   18):
        self.name = name
        self._age = age
        
tom = Person('Tom')
print(tom.__dict__)
---------------------------------------
{'name': 'Tom', '_age': 18}  ## 双下划线 输出为:{'name': 'Tom', '_Person__age': 18}
  • 私有方法的本质
    • 单下划线的方法只是开发者之间的约定,解释器不做任何改变。
    • 双下划线的方法,是私有方法,解释器会改名,改名策略和私有变量相同,_类名__方法名。
    • 方法变量都在类的__dict__中可以找到

私有成员的总结
在Python中使用 _单下划线或者 __ 双下划线来标识一个成员被保护或者被私有化隐藏起来。但是,不管使用什么样的访问控制,都不能真正的阻止用户修改类的成员。
Python中没有绝对的安全的保护成员或者私有成员。因此,前导的下划线只是一种警告或者提醒,请遵守这个约定。除非真有必要,不要修改或者使用保护成员或者私有成员,更不要修改它们

补丁

可以通过修改或者替换类的成员。使用者调用的方式没有改变,但是,类提供的功能可能已经改变了。
猴子补丁(Monkey Patch):在运行时,对属性、方法、函数等进行动态替换。其目的往往是为了通过替换、修改来增强、扩展原有代码的能力(偷梁换柱)

## test1.py
from test2 import Person
from test3 import get_score

def monkeypatch4Person(cls):
    Person.get_score = get_score
monkeypatch4Person(Person)                # 打补丁

if __name__ == "__main__":
    tom = Person()
    print(Person().get_score())

# test2.py
class Person:
    def get_score(self):# connect to mysql
        ret = {'English':70, 'Chinese':88, 'History':80}
        return ret
        
# test3.py        
def get_score(self):
    return dict(name=self.__class__.__name__,English=90, Chinese=98, History=95)

属性装饰器

class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age

print(Person.__dict__)
tom = Person('tom')
# print(tom.age)                 # 私有属性不能访问
# print(tom.__age)               # 私有属性不能访问
print(tom.__dict__)      
tom.age = 30                   #实例属性动态赋值,私有属性并未改变,只是增加实例的属性,赋值即定义
print(tom.__dict__)
print(tom.age)
tom.weight = 100               #实例属性动态赋值
print(tom.weight)
print(tom.__dict__)
print(Person.__dict__)         # 类属性并未改变
--------------------------------------------------
{'__module__': '__main__', '__init__': <function Person.__init__ at 0x00000215C0D459D8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
{'name': 'tom', '_Person__age': 18}
{'name': 'tom', '_Person__age': 18, 'age': 30}
30
100
{'name': 'tom', '_Person__age': 18, 'age': 30, 'weight': 100}
{'__module__': '__main__', '__init__': <function Person.__init__ at 0x00000215C0D459D8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
  • 要访问或者改变或者删除私有属性可以通过装饰器
    • property (就是getter)
    • setter
    • deleter
class Person:    
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age
        
    @property
    def age(self):
        return self.__age 
    
    @age.setter
    def age(self,age):
        self.__age = age    
    
    @age.deleter
    def age(self):
#         del self.age
        print('is del')
-------------------------------------------
tom = Person('tom')
print(tom.age)         >>>  18
tom.age = 25
print(tom.age)        >>>  25
del tom.age           >>> is del
  • 特别注意:使用property装饰器的时候这三个方法同名

  • property装饰器后面跟的函数名就是以后的属性名。它就是getter。这个必须有,有了它至少是只读属性

  • setter装饰器与属性名同名,且接收2个参数,第一个是self,第二个是将要赋值的值。有了它,属性可写

  • deleter装饰器可以控制是否删除属性。很少用

  • property装饰器必须在前,setter、deleter装饰器在后

  • property装饰器能通过简单的方式,把对方法的操作变成对属性的访问,并起到了一定隐藏效果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值